feat(servicing): 添加客户语音通知功能

- 新增 CustomerSpeechManager 对象,用于处理文本转语音功能
- 添加 AppForegroundListener 接口和 BaseActivityLifecycleCallbacks 类,用于监听应用前后台切换- 更新 BaseActivity,使其支持推送消息
- 新增 ServicePeopleConfirmActivity 活动
- 优化订单处理逻辑,过滤掉已接受的订单
- 更新版本号至 1.0.1.9.9.12
This commit is contained in:
songzhiling
2025-04-27 17:49:05 +08:00
parent b0c2f7352d
commit 863329d107
32 changed files with 1505 additions and 223 deletions

View File

@ -27,7 +27,7 @@ class MainActivity : ComponentActivity() {
.fillMaxSize()
.clickable {
val uri =
"zd.assist://app?taskCode=ZD250425100361&driverName=宋志领&driverPhone=17630035658&rescueVehicle=沪88888".toUri()
"zd.assist://app?taskCode=ZD250427100009&driverName=宋志领&driverPhone=17630035658&rescueVehicle=沪88888".toUri()
val intent = Intent(Intent.ACTION_VIEW, uri)
startActivity(intent)
}

View File

@ -73,7 +73,7 @@ publishing {
release(MavenPublication) {
groupId = 'io.github.szl9'
artifactId = 'zd_servicing'
version = "1.0.1.9.9.4"
version = "1.0.1.9.9.12"
pom {
packaging = "aar"

View File

@ -80,6 +80,11 @@
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
tools:targetApi="24">
<activity
android:name="com.za.ui.servicing.inservice_people_confirm.ServicePeopleConfirmActivity"
android:exported="true"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.main.ServiceLauncherActivity"
android:exported="true"

View File

@ -0,0 +1,14 @@
package com.za.base
interface AppForegroundListener {
/**
* APP处于 前台
*/
fun onAppForeground()
/**
* APP处于 后台
*/
fun onAppBackground()
}

View File

@ -3,7 +3,6 @@ package com.za.base
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
import com.tencent.smtt.sdk.QbSdk
import com.za.base.BaseVm.Companion.showTipDialog
@ -12,7 +11,7 @@ import com.za.base.view.CommonDialog
import com.za.base.view.LoadingManager
import com.za.common.log.LogUtil
abstract class BaseActivity : AppCompatActivity() {
abstract class BaseActivity : PushMessageActivity() {
protected val TAG by lazy { javaClass.simpleName }
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -0,0 +1,95 @@
package com.za.base
import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle
import android.view.WindowManager
import com.za.call.CallLogManager
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import java.util.concurrent.atomic.AtomicBoolean
open class BaseActivityLifecycleCallbacks : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity : Activity, savedInstanceState : Bundle?) {
LogUtil.print("onActivityCreated", activity.javaClass.simpleName)
keepScreenOn(activity)
}
override fun onActivityStarted(activity : Activity) {
LogUtil.print("onActivityStarted", activity.javaClass.simpleName)
synchronized(this) {
GlobalData.activityCount ++
if (GlobalData.activityCount == 1) {
isBackground.set(false)
appForegroundListener?.onAppForeground()
LogUtil.print("AppState", "切换到前台")
}
}
}
override fun onActivityResumed(activity : Activity) {
LogUtil.print("onActivityResumed", activity.javaClass.simpleName)
currentActivity = activity
CallLogManager.uploadCallLog(activity.applicationContext)
}
override fun onActivityPaused(activity : Activity) {
LogUtil.print("onActivityPaused", activity.javaClass.simpleName)
if (currentActivity == activity) {
currentActivity = null
}
}
override fun onActivityStopped(activity : Activity) {
LogUtil.print("onActivityStopped", activity.javaClass.simpleName)
synchronized(this) {
GlobalData.activityCount --
if (GlobalData.activityCount == 0) {
isBackground.set(true)
appForegroundListener?.onAppBackground()
LogUtil.print("AppState", "切换到后台")
}
}
}
override fun onActivitySaveInstanceState(activity : Activity, outState : Bundle) {
LogUtil.print("onActivitySaveInstanceState", activity.javaClass.simpleName)
}
override fun onActivityDestroyed(activity : Activity) {
LogUtil.print("onActivityDestroyed", activity.javaClass.simpleName)
}
private fun keepScreenOn(activity : Activity) { //保持屏幕不息屏
activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
companion object {
var appForegroundListener : AppForegroundListener? = null
// 使用 AtomicBoolean 保证线程安全
private val isBackground = AtomicBoolean(true)
// 当前显示的Activity
private var currentActivity : Activity? = null
// 判断应用是否在后台
fun isBackground() : Boolean = isBackground.get()
// 判断应用是否在前台
fun isForeground() : Boolean = ! isBackground.get()
// 获取当前Activity
fun getCurrentActivity() : Activity? = currentActivity
// 重置状态
fun reset() {
synchronized(BaseActivityLifecycleCallbacks::class.java) {
isBackground.set(true)
currentActivity = null
appForegroundListener = null
}
}
}
}

View File

@ -0,0 +1,207 @@
package com.za.base
import android.app.Activity
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.DialogInterface
import android.media.RingtoneManager
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat
import com.google.gson.Gson
import com.za.bean.JpushBean
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.common.speech.SpeechManager
import com.za.servicing.R
import com.za.ui.servicing.order_give_up.OrderGiveUpActivity
import com.za.ui.view.CommonDialogFragment
open class PushMessageActivity : AppCompatActivity() {
private var currentDialog : AlertDialog? = null
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
if (! GlobalData.isMaster) {
PushMessageLiveData.pushMessage.observe(this) { message -> // 处理推送消息
LogUtil.print("PushMessageActivity", "Received push message: $message")
handlePushMessage(msg = message)
}
}
}
override fun onPause() {
super.onPause()
dismissCurrentDialog()
}
private fun handlePushMessage(msg : String) {
if (msg.startsWith("broadcast:")) {
handleBroadcast(msg)
return
}
try {
val jpushOrderInfoBean = Gson().fromJson(msg, JpushBean::class.java)
when (jpushOrderInfoBean.pushType) {
1 -> handleTypeOneMessage(jpushOrderInfoBean)
3 -> handleImportantTip(jpushOrderInfoBean)
else -> LogUtil.print("JpushMessage",
"Unknown push type: ${jpushOrderInfoBean.pushType}")
}
} catch (e : Exception) {
if (msg.startsWith("broadcast:")) {
handleBroadcast(msg)
}
LogUtil.print("JpushMessage", "Error handling message: ${e.message}")
}
}
private fun handleTypeOneMessage(jpushOrderBean : JpushBean) {
when (jpushOrderBean.typeDesc) {
"giveUp" -> handleGiveUpOrder(jpushOrderBean)
"revoke" -> handleRevokeOrder()
"reDispatch" -> handleReDispatchOrder(jpushOrderBean)
else -> LogUtil.print("JpushMessage", "Unknown typeDesc: ${jpushOrderBean.typeDesc}")
}
}
// Handle broadcast messages
private fun handleBroadcast(msg : String) {
try {
val content = msg.substring(10)
sendNotification(GlobalData.application, content)
LogUtil.print("JpushMessage", "Broadcast content: $content")
} catch (e : Exception) {
LogUtil.print("JpushMessage", "Broadcast failed: ${e.message}")
}
}
private fun dismissCurrentDialog() {
try {
currentDialog?.dismiss()
currentDialog = null
} catch (e : Exception) {
LogUtil.print("PushActivityLifecycleCallbacks", "关闭对话框失败: ${e.message}")
}
}
private fun handleGiveUpOrder(jpushBean : JpushBean) { // 播放提示音
playNotificationSound(this)
if (GlobalData.currentOrder != null && GlobalData.currentOrder?.taskId == jpushBean.taskId) {
SpeechManager.playCurrentOrderCanceled()
CommonDialogFragment(title = "订单放弃",
message = buildGiveUpMessage(jpushBean),
confirmText = "去拍照",
cancelText = "我已了解",
confirm = {
OrderGiveUpActivity.goOrderGiveUpActivity(this,
giveUpType = GIVE_UP_TYPE_NORMAL,
taskId = jpushBean.taskId)
}).show(this.supportFragmentManager, DIALOG_TAG_GIVE_UP)
} else {
SpeechManager.playOrderCanceled()
}
}
private fun handleRevokeOrder() {
playNotificationSound(this)
SpeechManager.speech("订单被撤回") // 获取当前Activity进行处理
this.finish()
}
private fun handleReDispatchOrder(jpushBean : JpushBean) {
playNotificationSound(this)
currentDialog = AlertDialog.Builder(this).setTitle("订单重新派发")
.setMessage(buildReDispatchMessage(jpushBean)).setCancelable(false)
.setPositiveButton("确定") { dialog, _ ->
dialog.dismiss()
}.show()
}
private fun handleImportantTip(jpushBean : JpushBean) {
playNotificationSound(this)
SpeechManager.speech("重要提醒:${jpushBean.tipContent ?: ""}")
currentDialog =
AlertDialog.Builder(this).setTitle("重要提醒").setMessage(jpushBean.tipContent)
.setNegativeButton("我已了解") { dialog : DialogInterface, _ : Int -> dialog.dismiss() }
.show()
}
private fun buildGiveUpMessage(jpushBean : JpushBean) : String {
return buildString {
append("该工单已放弃")
jpushBean.taskCode?.let { append("\n\n订单号:$it") }
jpushBean.address?.let { append("\n地址:$it") }
append("\n\n是否需要拍放空照片?")
}
}
private fun buildReDispatchMessage(jpushBean : JpushBean) : String {
return buildString {
append("该订单已重新派发")
jpushBean.taskCode?.let { append("\n\n订单号:$it") }
jpushBean.address?.let { append("\n地址:$it") }
jpushBean.addressRemark?.let {
if (it.isNotBlank()) {
append("\n\n备注:$it")
}
}
}
}
private fun playNotificationSound(activity : Activity) {
try {
val notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
RingtoneManager.getRingtone(activity, notification)?.play()
} catch (e : Exception) {
LogUtil.print("PushActivityLifecycleCallbacks", "播放提示音失败: ${e.message}")
}
}
private val CHANNEL_ID = "ImportantMessagesChannel"
private val NOTIFICATION_ID = 1003
// Initialize notification channel
private fun createNotificationChannel(context : Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(CHANNEL_ID,
"订单通知",
NotificationManager.IMPORTANCE_HIGH).apply {
description = "用于接收重要消息通知"
setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
Notification.AUDIO_ATTRIBUTES_DEFAULT)
enableVibration(true)
}
val notificationManager =
context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
// Send notification
private fun sendNotification(context : Context, message : String) {
createNotificationChannel(context)
val notification =
NotificationCompat.Builder(context, CHANNEL_ID).setContentTitle("重要通知")
.setContentText(message).setSmallIcon(R.mipmap.ic_launcher) // 替换为你的应用图标
.setPriority(NotificationCompat.PRIORITY_HIGH).setAutoCancel(true) // 点击后自动取消通知
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setVibrate(longArrayOf(0, 100, 200, 300)).build()
val notificationManager =
context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(NOTIFICATION_ID, notification)
}
companion object {
internal const val GIVE_UP_TYPE_NORMAL = 1
internal const val DIALOG_TAG_GIVE_UP = "giveUp"
}
}

View File

@ -0,0 +1,59 @@
package com.za.base
import android.util.Log
import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import cn.jpush.android.api.JPushMessage
import java.util.concurrent.atomic.AtomicBoolean
object PushMessageLiveData {
// 使用 SingleLiveEvent 或类似机制避免粘性事件问题
private val _pushMessage = SingleLiveEvent<String>()
// 对外暴露不可变的 LiveData
val pushMessage : LiveData<String> = _pushMessage
// 发送推送消息
fun postPushMessage(message : String) {
_pushMessage.postValue(message)
}
}
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
@MainThread
override fun observe(owner : LifecycleOwner, observer : Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "多个观察者注册了 SingleLiveEvent但只有一个会收到更新通知")
}
// 观察 LiveData 内部值
super.observe(owner) { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
@MainThread
override fun setValue(t : T?) {
pending.set(true)
super.setValue(t)
}
/**
* 用于主线程外调用
*/
override fun postValue(value : T) {
pending.set(true)
super.postValue(value)
}
companion object {
private const val TAG = "SingleLiveEvent"
}
}

View File

@ -117,6 +117,7 @@ object GlobalData : GlobalLocalData() {
currentLocation = null
driverInfoBean = null
loginTime = null
isLoginRecognition = null
}
fun clearAllOrderCache() {

View File

@ -5,6 +5,7 @@ import com.tencent.bugly.Bugly
import com.tencent.mmkv.MMKV
import com.za.base.AppConfig
import com.za.common.log.LogUtil
import com.za.common.speech.SpeechManager
import com.za.room.RoomHelper
import com.za.service.location.ZdLocationManager
@ -28,5 +29,6 @@ object ZDManager {
LogUtil.init(application)
RoomHelper.init(application)
ZdLocationManager.init(application)
SpeechManager.init(application)
}
}

View File

@ -0,0 +1,122 @@
package com.za.common.speech
import android.media.MediaPlayer
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.za.common.log.LogUtil
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.POST
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
interface SpeechApiService {
@POST("v1/audio/speech")
fun textToSpeech(@Body request: JsonObject): Call<ResponseBody>
}
object CustomerSpeechManager {
private const val BASE_URL = "http://192.168.3.129:8880/"
private val gson: Gson by lazy {
Gson().newBuilder()
.setLenient()
.create()
}
private val retrofit: Retrofit by lazy {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
}
private val speechApiService: SpeechApiService by lazy {
retrofit.create(SpeechApiService::class.java)
}
fun textToSpeech(input: String, destinationFile: File): Boolean {
val requestBody = JsonObject().apply {
addProperty("input", input)
addProperty("voice", "zf_xiaoxiao")
addProperty("response_format", "mp3")
addProperty("stream", true)
addProperty("speed", 1)
addProperty("return_download_link", false) // Set to false to get the stream directly
addProperty("lang_code", "z")
}
val call = speechApiService.textToSpeech(requestBody)
try {
val response = call.execute()
LogUtil.print("CustomerSpeechManager", "response: $response")
if (response.isSuccessful) {
val responseBody = response.body()
if (responseBody != null) {
saveToFile(responseBody, destinationFile)
playAudio(destinationFile)
return true
}
} else {
LogUtil.print("CustomerSpeechManager", "Request failed: ${response.code()}")
}
} catch (e: IOException) {
e.printStackTrace()
}
return false
}
private fun saveToFile(body: ResponseBody, destinationFile: File) {
body.byteStream().use {
FileOutputStream(destinationFile).buffered().use { outputStream ->
val buffer = ByteArray(4096)
var bytesRead: Int
while (it.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
}
}
}
private fun playAudio(file: File) {
val mediaPlayer = MediaPlayer().apply {
try {
setDataSource(file.absolutePath)
prepare()
start()
} catch (e: IOException) {
e.printStackTrace()
release()
}
}
mediaPlayer.setOnCompletionListener {
it.release()
}
mediaPlayer.setOnErrorListener { mp, what, extra ->
mp.release()
false
}
}
}

View File

@ -0,0 +1,221 @@
package com.za.common.speech
import android.app.Application
import android.media.AudioManager
import android.media.MediaPlayer
import androidx.core.content.ContextCompat
import com.blankj.utilcode.util.ThreadUtils
import com.za.base.AppConfig
import com.za.bean.request.AppNewOrderVoiceRequest
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.net.BaseObserver
import com.za.net.RetrofitHelper
import com.za.room.RoomHelper
import com.za.room.db.user.LocalResourceBean
import com.za.servicing.R
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
object SpeechManager {
private var mContext : Application? = null
fun init(context : Application?) {
mContext = context //语音合成初始化|
TTSManager.init(context, object : OnTTSListener {
override fun onTTSInitialized() { // TTS初始化成功可以开始播放语音
LogUtil.print("TTS", "TTS initialized successfully")
}
override fun onTTSSuccess() {
LogUtil.print("TTS", "TTS speech completed successfully")
resetAudioVolume()
}
override fun onTTSFailed(errorMessage : String?) {
resetAudioVolume() // TTS初始化失败或语音播放失败使用科大讯飞进行播放
LogUtil.print("TTS", "TTS failed: $errorMessage")
if (lastFailedText != null && ! lastFailedText.isNullOrBlank()) {
speechKDXF(lastFailedText)
lastFailedText = null
}
}
})
}
private var lastFailedText : String? = null
fun speech(msg : String?) {
setMaxAudioVolume()
lastFailedText = msg
TTSManager.speak(msg)
}
private fun speechKDXF(msg : String?) {
}
private var originVolume = 0
private fun setMaxAudioVolume() {
try {
if (! AppConfig.isRelease) {
return
}
val audioManager =
ContextCompat.getSystemService(GlobalData.application, AudioManager::class.java)
val maxVolume = audioManager?.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC,
maxVolume ?: 1,
AudioManager.FLAG_PLAY_SOUND)
} catch (e : Exception) {
LogUtil.print("setMaxAudioVolume", e)
}
}
private fun resetAudioVolume() {
try {
if (! AppConfig.isRelease) {
return
}
val audioManager =
ContextCompat.getSystemService(GlobalData.application, AudioManager::class.java)
audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC,
originVolume,
AudioManager.FLAG_PLAY_SOUND)
} catch (e : Exception) {
LogUtil.print("resetAudioVolume", e)
}
}
fun stopSpeech() {
TTSManager.stop()
lastFailedText = null
}
private var mediaPlayer : MediaPlayer? = null
// 当前订单被取消
fun playCurrentOrderCanceled() {
mediaPlayer = MediaPlayer.create(mContext, R.raw.current_order_cancel)
mediaPlayer?.start()
}
// 订单被取消
fun playOrderCanceled() {
mediaPlayer = MediaPlayer.create(mContext, R.raw.cancel_order)
mediaPlayer?.start()
}
// 面部居中
fun playFaceCenter() {
mediaPlayer = MediaPlayer.create(mContext, R.raw.face_center)
mediaPlayer?.start()
}
// 面部居中
fun playFaceLeft() {
mediaPlayer = MediaPlayer.create(mContext, R.raw.face_left)
mediaPlayer?.start()
}
// 面部居中
fun playFaceRight() {
mediaPlayer = MediaPlayer.create(mContext, R.raw.face_right)
mediaPlayer?.start()
}
private fun playNewOrder() {
stopPlayMedia()
setMaxAudioVolume()
mediaPlayer = MediaPlayer.create(mContext, R.raw.neworder)
mediaPlayer?.start()
mediaPlayer?.setOnCompletionListener {
resetAudioVolume()
}
}
private fun playNewOrderFromNet(url : String) {
try {
stopPlayMedia()
setMaxAudioVolume()
mediaPlayer = MediaPlayer()
mediaPlayer?.setAudioStreamType(AudioManager.STREAM_MUSIC) // 设置音频流类型
mediaPlayer?.setDataSource(url) // 设置音频文件的 URL
mediaPlayer?.prepareAsync() // 异步准备音频
// 准备完成后开始播放
mediaPlayer?.setOnPreparedListener { obj : MediaPlayer -> obj.start() }
mediaPlayer?.setOnErrorListener { mp : MediaPlayer?, what : Int, extra : Int ->
playNewOrder()
false
}
mediaPlayer?.setOnCompletionListener {
resetAudioVolume()
}
} catch (e : Exception) {
playNewOrder()
LogUtil.print("播放新订单失败", e)
}
}
fun stopPlayMedia() {
ThreadUtils.runOnUiThread {
if (null != mediaPlayer) {
mediaPlayer?.stop()
mediaPlayer?.release()
mediaPlayer = null
}
}
}
fun speechNewOrderSound(content : String?) {
if (content.isNullOrBlank()) {
speechNewOrderLooper("您有新的中道救援订单,请尽快接单!") { playNewOrder() }
return
}
val localResourceDao = RoomHelper.db?.localResourceDao()
val localUrlResource = localResourceDao?.getLocalResourceByName(content)
if (localUrlResource != null && ! localUrlResource.resourceUrl.isNullOrBlank()) {
speechNewOrderLooper(content) {
playNewOrderFromNet(localUrlResource.resourceUrl ?: "")
}
LogUtil.print("handlerNewOrderVoice", "播放本地语音");
return
}
val appNewOrderVoiceRequest = AppNewOrderVoiceRequest(content)
RetrofitHelper.getDefaultService().getVoiceUrl(appNewOrderVoiceRequest)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<String>() {
override fun doSuccess(it : String?) {
if (it == null) {
speechNewOrderLooper(content) { playNewOrder() }
return
}
localResourceDao?.insert(LocalResourceBean(resourceName = content,
resourceType = 1,
resourceUrl = it))
speechNewOrderLooper(content) { playNewOrderFromNet(it) }
}
override fun doFailure(code : Int, msg : String?) {
speechNewOrderLooper("您有新的中道救援订单,请尽快接单!") { playNewOrder() }
}
})
}
private fun speechNewOrderLooper(content : String, play : () -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
val startTime = System.currentTimeMillis()
while (System.currentTimeMillis() - startTime < 1000 * 60 * 3 && GlobalData.isHandlerNewOrder == false) {
play()
delay(250L * content.length)
}
}
}
}

View File

@ -0,0 +1,81 @@
package com.za.common.speech
import android.annotation.SuppressLint
import android.content.Context
import android.speech.tts.TextToSpeech
import android.speech.tts.UtteranceProgressListener
import com.za.common.log.LogUtil
import java.util.Locale
@SuppressLint("StaticFieldLeak")
object TTSManager {
private var tts: TextToSpeech? = null
private var context: Context? = null
private var listener: OnTTSListener? = null
fun init(context: Context?, onTTSListener: OnTTSListener) {
this.context = context
this.listener = onTTSListener
initTTS()
}
private fun initTTS() {
tts = TextToSpeech(context) { status ->
if (status == TextToSpeech.SUCCESS) {
val result = tts?.setLanguage(Locale.getDefault())
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
LogUtil.print("TTS", "Language not supported")
listener?.onTTSFailed("Language not supported")
} else {
LogUtil.print("TTS", "TTS initialized successfully")
listener?.onTTSInitialized()
}
} else {
LogUtil.print("TTS", "Initialization failed")
listener?.onTTSFailed("Initialization failed")
}
}
tts?.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
override fun onStart(utteranceId: String) {
LogUtil.print("TTS", "Speech started")
}
override fun onDone(utteranceId: String) {
LogUtil.print("TTS", "Speech completed")
listener?.onTTSSuccess()
}
override fun onError(utteranceId: String) {
LogUtil.print("TTS", "Speech error")
listener?.onTTSFailed("Speech error")
}
})
}
fun speak(text: String?) {
if (tts != null && tts?.isSpeaking == false) {
tts?.speak(text, TextToSpeech.QUEUE_FLUSH, null, "uniqueId")
}
}
fun stop() {
tts?.takeIf { it.isSpeaking }?.stop()
}
fun shutdown() {
tts?.apply {
stop()
shutdown()
}
tts = null
}
}
interface OnTTSListener {
fun onTTSInitialized()
fun onTTSSuccess()
fun onTTSFailed(errorMessage: String?)
}

View File

@ -106,8 +106,8 @@ abstract class BaseObserver<T> : Observer<BaseResponse<T>> {
ThreadUtils.runOnUiThread {
try {
ToastUtils.showShort("登陆信息已过期,请重新登录")
GlobalData.clearUserCache()
ZdLocationManager.stopContinuousLocation()
GlobalData.clearUserCache()
ActivityUtils.startLauncherActivity()
} catch (e : Exception) {
LogUtil.print("handlerTokenExpired", e)

View File

@ -160,31 +160,34 @@ object CommonMethod {
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<JpushBean>() {
override fun doSuccess(order : JpushBean?) {
if (order == null) {
return
}
NewOrderActivity.goNewOrderActivity(context,
jpushBean = JpushBean(taskId = order?.taskId,
taskCode = order?.taskCode,
customerName = order?.customerName,
customerPhone = order?.customerPhone,
carBrand = order?.carBrand,
carModel = order?.carModel,
carNo = order?.carNo,
taskState = order?.taskState,
address = order?.address,
addressProperty = order?.addressProperty,
hotline = order?.hotline,
expectArriveTime = order?.expectArriveTime,
serviceTypeName = order?.serviceTypeName,
dispatchTime = order?.dispatchTime,
lat = order?.lat,
lng = order?.lng,
distLat = order?.distLat,
distLng = order?.distLng,
importantTip = order?.importantTip,
hasReplaceBatteryCapable = order?.hasReplaceBatteryCapable,
distAddress = order?.distAddress,
distAddressRemark = order?.distAddressRemark,
addressRemark = order?.addressRemark,
voiceType = order?.voiceType))
jpushBean = JpushBean(taskId = order.taskId,
taskCode = order.taskCode,
customerName = order.customerName,
customerPhone = order.customerPhone,
carBrand = order.carBrand,
carModel = order.carModel,
carNo = order.carNo,
taskState = order.taskState,
address = order.address,
addressProperty = order.addressProperty,
hotline = order.hotline,
expectArriveTime = order.expectArriveTime,
serviceTypeName = order.serviceTypeName,
dispatchTime = order.dispatchTime,
lat = order.lat,
lng = order.lng,
distLat = order.distLat,
distLng = order.distLng,
importantTip = order.importantTip,
hasReplaceBatteryCapable = order.hasReplaceBatteryCapable,
distAddress = order.distAddress,
distAddressRemark = order.distAddressRemark,
addressRemark = order.addressRemark,
voiceType = order.voiceType))
}
override fun doFailure(code : Int, msg : String?) {
@ -211,7 +214,8 @@ object CommonMethod {
}
val inServicingOrder = it.find { it.isCurrent == true }
val waitServiceOrders = it.filter { it.isCurrent == false }
val waitServiceOrders =
it.filter { it.isCurrent == false && it.taskState != "ACCEPT" }
if (inServicingOrder == null) {
success(null, waitServiceOrders)

View File

@ -10,6 +10,7 @@ import androidx.core.app.NotificationCompat
import cn.jiguang.api.utils.JCollectionAuth
import cn.jpush.android.api.JPushInterface
import com.google.gson.Gson
import com.za.base.PushMessageLiveData
import com.za.bean.JpushBean
import com.za.common.GlobalData
import com.za.common.log.LogUtil
@ -17,19 +18,9 @@ import com.za.common.util.DeviceUtil
import com.za.service.mqtt.MyMqttClient
import com.za.servicing.R
interface PushListener {
fun newOrderMsg(jpushBean : JpushBean)
fun giveUpOrder(jpushBean : JpushBean)
fun revokeOrder(jpushBean : JpushBean)
fun reDispatchOrder(jpushBean : JpushBean)
fun broadcast(string : String)
fun importantTip(jpushBean : JpushBean)
}
data class LastJPushBean(val msg : Int, val time : Long = System.nanoTime())
object ServiceManager {
private var pushListener : PushListener? = null
private var lastJPushBean : LastJPushBean? = null
private const val DUPLICATE_MSG_THRESHOLD = 3000L // 3秒
@ -53,13 +44,6 @@ object ServiceManager {
}
}
// Register push listener
fun registerPushListener(listener : PushListener?) {
this.pushListener = listener
LogUtil.print("ServiceManager",
"Registered push listener: ${pushListener?.javaClass?.simpleName}")
}
// Handle incoming push messages
@Synchronized
fun handlerPushMsg(msg : String) {
@ -75,93 +59,22 @@ object ServiceManager {
if (msg.startsWith("broadcast:")) {
lastJPushBean = LastJPushBean(msg = msg.hashCode())
handleBroadcast(msg)
PushMessageLiveData.postPushMessage(msg)
return
}
try {
lastJPushBean = LastJPushBean(msg = msg.hashCode())
val jpushOrderInfoBean = Gson().fromJson(msg, JpushBean::class.java)
sendSystemNotificationFromMessage(jpushOrderInfoBean)
when (jpushOrderInfoBean.pushType) {
0 -> newOrderMsg(jpushOrderInfoBean)
1 -> handleTypeOneMessage(jpushOrderInfoBean)
3 -> importantTip(jpushOrderInfoBean)
else -> LogUtil.print("JpushMessage",
"Unknown push type: ${jpushOrderInfoBean.pushType}")
}
PushMessageLiveData.postPushMessage(msg)
} catch (e : Exception) {
if (msg.startsWith("broadcast:")) {
handleBroadcast(msg)
PushMessageLiveData.postPushMessage(msg)
}
LogUtil.print("JpushMessage", "Error handling message: ${e.message}")
}
}
// Handle broadcast messages
private fun handleBroadcast(msg : String) {
try {
val content = msg.substring(10)
pushListener?.broadcast(content)
sendNotification(GlobalData.application, content)
LogUtil.print("JpushMessage", "Broadcast content: $content")
} catch (e : Exception) {
LogUtil.print("JpushMessage", "Broadcast failed: ${e.message}")
}
}
// Handle type one messages
private fun handleTypeOneMessage(jpushOrderBean : JpushBean) {
when (jpushOrderBean.typeDesc) {
"giveUp" -> giveUpOrder(jpushOrderBean)
"revoke" -> revokeOrder(jpushOrderBean)
"reDispatch" -> reDispatchOrder(jpushOrderBean)
else -> LogUtil.print("JpushMessage", "Unknown typeDesc: ${jpushOrderBean.typeDesc}")
}
}
// Handle new order messages
private fun newOrderMsg(jpushOrderBean : JpushBean) {
try {
LogUtil.print("JpushMessage",
"Handling new order message: $pushListener ${pushListener?.javaClass?.simpleName}")
pushListener?.newOrderMsg(jpushOrderBean)
} catch (e : Exception) {
LogUtil.print("JpushMessage", "Failed to handle new order message: ${e.message}")
}
}
// Handle give up order messages
private fun giveUpOrder(jpushOrderBean : JpushBean) {
pushListener?.giveUpOrder(jpushOrderBean)
}
// Handle revoke order messages
private fun revokeOrder(jpushOrderBean : JpushBean) {
pushListener?.revokeOrder(jpushOrderBean)
}
// Handle re-dispatch order messages
private fun reDispatchOrder(jpushOrderBean : JpushBean) {
pushListener?.reDispatchOrder(jpushOrderBean)
}
// Handle important tip messages
private fun importantTip(jpushOrderBean : JpushBean) {
pushListener?.importantTip(jpushOrderBean)
}
// Disconnect from JPush and MQTT
fun disconnect(context : Context) {
try {
JPushInterface.stopPush(context) // Stop JPush
MyMqttClient.disconnect() // Disconnect MQTT
LogUtil.print("ServiceManager", "Disconnected from JPush and MQTT successfully")
} catch (e : Exception) {
LogUtil.print("ServiceManager", "Error during disconnection: ${e.message}")
}
}
private const val CHANNEL_ID = "ImportantMessagesChannel"
private const val NOTIFICATION_ID = 1003
@ -224,5 +137,15 @@ object ServiceManager {
notificationManager.notify(NOTIFICATION_ID, notification)
}
// Disconnect from JPush and MQTT
fun disconnect(context : Context) {
try {
JPushInterface.stopPush(context) // Stop JPush
MyMqttClient.disconnect() // Disconnect MQTT
LogUtil.print("ServiceManager", "Disconnected from JPush and MQTT successfully")
} catch (e : Exception) {
LogUtil.print("ServiceManager", "Error during disconnection: ${e.message}")
}
}
}

View File

@ -98,6 +98,9 @@ object ZdLocationManager : AMapLocationListener {
fun stopContinuousLocation() {
try {
aMapLocationClient?.stopLocation()
aMapLocationClient?.onDestroy()
aMapLocationClient?.unRegisterLocationListener(this)
aMapLocationClient = null
LogUtil.print(TAG, "关闭持续定位成功")
} catch (e : Exception) {
LogUtil.print(TAG, "关闭持续定位失败: ${e.message}")
@ -134,6 +137,10 @@ object ZdLocationManager : AMapLocationListener {
}
private fun uploadGps(uploadGpsRequest : UploadGpsRequest) {
if (GlobalData.token.isNullOrBlank()) {
LogUtil.print(TAG, "定位上传失败: token is null,request=$uploadGpsRequest")
return
}
CommonMethod.uploadGps(uploadGpsRequest, success = {
LogUtil.print(TAG, "定位上传成功: ${uploadGpsRequest.toJson()}")
MyMqttClient.publishMessage() // if (ActivityUtils.getTopActivity()==null) {

View File

@ -0,0 +1,13 @@
package com.za.ui.servicing.inservice_people_confirm
import androidx.compose.runtime.Composable
import com.za.base.BaseActivity
class ServicePeopleConfirmActivity : BaseActivity() {
@Composable
override fun ContentView() {
ServicePeopleConfirmScreen()
}
}

View File

@ -0,0 +1,138 @@
package com.za.ui.servicing.inservice_people_confirm
import com.blankj.utilcode.util.ToastUtils
import com.za.base.IServicingVm
import com.za.base.view.LoadingManager
import com.za.bean.db.order.OrderInfo
import com.za.bean.request.DriverFaceCompareRequest
import com.za.bean.request.IaiCompareFaceBean
import com.za.bean.request.UpdateTaskBean
import com.za.bean.request.UpdateTaskRequest
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.ext.toJson
import com.za.net.BaseObserver
import com.za.net.CommonMethod
import com.za.net.RetrofitHelper
import com.za.service.location.ZdLocationManager
import com.za.ui.servicing.inservice_people_confirm.InServicePeopleConfirmVm.Action
import com.za.ui.servicing.inservice_people_confirm.InServicePeopleConfirmVm.Action.CompilePeople
import com.za.ui.servicing.inservice_people_confirm.InServicePeopleConfirmVm.UiState
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.io.File
class InServicePeopleConfirmVm : IServicingVm<Action, UiState>() {
private val _uiState = MutableStateFlow(UiState())
val uiState get() = _uiState as StateFlow<UiState>
override fun updateState(uiState : UiState) {
_uiState.value = uiState
}
override fun dispatch(action : Action) {
when (action) {
is Action.Init -> init()
is Action.UpdateTask -> updateTask()
is CompilePeople -> comparePeople(action.url)
}
}
private fun updateTask() {
LoadingManager.showLoading()
ZdLocationManager.getSingleLocation(success = {
LoadingManager.hideLoading()
val taskRequest = UpdateTaskRequest(type = "START",
taskId = getCurrentOrder()?.taskId,
userId = GlobalData.driverInfoBean?.userId,
vehicleId = GlobalData.driverInfoBean?.vehicleId,
currentState = getCurrentOrder()?.taskState,
offlineMode = 0,
operateTime = System.currentTimeMillis().toString(),
lat = it.latitude,
lng = it.longitude)
doUploadTask(request = taskRequest)
}, failed = {
LoadingManager.hideLoading()
ToastUtils.showShort(it)
})
}
private fun doUploadTask(request : UpdateTaskRequest) {
LoadingManager.showLoading()
CommonMethod.updateTask(request, success = { data ->
LoadingManager.hideLoading()
updateOrder(getCurrentOrder()?.copy(taskState = data?.nextState))
updateState(uiState.value.copy(goNextPage = data, orderInfo = getCurrentOrder()))
}, failed = { msg, _ ->
LoadingManager.hideLoading()
ToastUtils.showShort(msg)
LogUtil.print("$tag doUploadTask", "状态更新失败==${request.toJson()} msg==$msg")
})
}
private fun comparePeople(url : String?) {
if (url.isNullOrBlank()) {
ToastUtils.showLong("图片路径为空!")
return
}
LoadingManager.showLoading()
CommonMethod.uploadImage(file = File(url), success = { it ->
LoadingManager.hideLoading()
doComparePeople(it)
}, failed = {
LoadingManager.hideLoading()
updateState(uiState.value.copy(showCompareFailedDialog = "$it", compareResult = false))
})
}
private fun doComparePeople(url : String?) {
val driverFaceCompareRequest =
DriverFaceCompareRequest(vehicleId = GlobalData.driverInfoBean?.vehicleId,
driverId = GlobalData.driverInfoBean?.userId,
photoUrl = url)
LoadingManager.showLoading()
RetrofitHelper.getDefaultService().iaiCompareFace(driverFaceCompareRequest)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<IaiCompareFaceBean>() {
override fun doSuccess(it : IaiCompareFaceBean?) {
LoadingManager.hideLoading()
if (it?.compareResult == true) {
updateState(uiState.value.copy(compareResult = true))
LogUtil.print("face", "人脸对比成功:$it")
return
}
updateState(uiState.value.copy(showCompareFailedDialog = "人脸对比失败",
compareResult = false))
}
override fun doFailure(code : Int, msg : String?) {
LoadingManager.hideLoading()
updateState(uiState.value.copy(showCompareFailedDialog = "$msg",
compareResult = false))
LogUtil.print("face", "人脸对比失败:$msg")
}
})
}
private fun init() {
}
sealed class Action {
object Init : Action()
data class CompilePeople(val url : String? = null) : Action()
object UpdateTask : Action()
}
data class UiState(
val orderInfo : OrderInfo? = null,
val goNextPage : UpdateTaskBean? = null,
val compareResult : Boolean? = null,
val showCompareFailedDialog : String? = null,
)
}

View File

@ -0,0 +1,165 @@
package com.za.ui.servicing.inservice_people_confirm
import android.app.Activity
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import com.blankj.utilcode.util.ToastUtils
import com.za.base.view.CommonButton
import com.za.base.view.CommonDialog
import com.za.base.view.HeadView
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.ext.goNextPage
import com.za.servicing.R
import com.za.ui.camera.ZdCameraXActivity
@Composable
fun ServicePeopleConfirmScreen(vm : InServicePeopleConfirmVm = viewModel(),
success : () -> Unit = {}) {
val context = LocalContext.current
val uiState = vm.uiState.collectAsStateWithLifecycle()
val getResult =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) { it ->
if (it.resultCode == Activity.RESULT_OK) {
val value = it.data?.getStringExtra("path")
LogUtil.print("takePhoto", "path==$value")
if (value.isNullOrBlank()) {
ToastUtils.showLong("照片路径为空,请重新拍摄!")
return@rememberLauncherForActivityResult
}
vm.dispatch(InServicePeopleConfirmVm.Action.CompilePeople(value))
}
}
if (uiState.value.compareResult == true) {
vm.updateState(uiState.value.copy(compareResult = null))
if (GlobalData.isMaster) {
success()
} else {
vm.dispatch(InServicePeopleConfirmVm.Action.UpdateTask)
}
}
if (uiState.value.goNextPage != null) {
goNextPage(uiState.value.goNextPage?.nextState, context)
vm.updateState(uiState.value.copy(goNextPage = null, compareResult = null))
}
if (! uiState.value.showCompareFailedDialog.isNullOrBlank()) {
CommonDialog(title = uiState.value.showCompareFailedDialog,
confirmText = "重新认证",
confirm = {
vm.updateState(uiState.value.copy(showCompareFailedDialog = null))
val intent = Intent(context, ZdCameraXActivity::class.java)
intent.putExtra("isBack", false)
getResult.launch(intent)
},
cancelText = "关闭",
cancel = {
vm.updateState(uiState.value.copy(showCompareFailedDialog = null))
},
dismiss = {
vm.updateState(uiState.value.copy(showCompareFailedDialog = null))
})
}
Scaffold(topBar = { HeadView(title = "身份验证") }) {
Column(modifier = Modifier
.fillMaxSize()
.background(color = Color.White)
.padding(it),
horizontalAlignment = Alignment.CenterHorizontally) {
Spacer(modifier = Modifier.height(60.dp))
Box {
Text(text = "为了确保服务技师信息的准确",
fontWeight = FontWeight.SemiBold,
color = Color.DarkGray,
fontSize = 18.sp)
}
Spacer(modifier = Modifier.height(10.dp))
Box {
Text(text = "需要进行人脸核验",
color = Color.Gray,
fontWeight = FontWeight.Normal,
fontSize = 14.sp)
}
Spacer(modifier = Modifier.height(30.dp))
AsyncImage(model = R.drawable.sv_facea_verify,
contentDescription = "",
modifier = Modifier
.background(color = Color.White)
.padding(30.dp))
Spacer(modifier = Modifier.height(30.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
AsyncImage(model = R.drawable.sv_face_light,
contentDescription = "",
modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.height(5.dp))
Text(text = "保持光线充足", fontSize = 10.sp, color = Color.Gray)
}
Spacer(modifier = Modifier.width(30.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
AsyncImage(model = R.drawable.sv_face_phone,
contentDescription = "",
modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.height(5.dp))
Text(text = "需正对手机", fontSize = 10.sp, color = Color.Gray)
}
Spacer(modifier = Modifier.width(30.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
AsyncImage(model = R.drawable.sv_face_face,
contentDescription = "",
modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.height(5.dp))
Text(text = "确保面部无遮挡", fontSize = 10.sp, color = Color.Gray)
}
}
Spacer(modifier = Modifier.height(60.dp))
CommonButton(text = "开始核验", onClick = {
val intent = Intent(context, ZdCameraXActivity::class.java)
intent.putExtra("isBack", false)
getResult.launch(intent)
})
}
}
}

View File

@ -1,12 +1,8 @@
package com.za.ui.servicing.wait_to_start
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@ -63,14 +59,14 @@ import com.za.base.BaseActivity
import com.za.base.theme.headBgColor
import com.za.base.view.CommonDialog
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.common.util.ImageUtil
import com.za.common.util.ServicingSpeechManager
import com.za.ext.copy
import com.za.ext.finish
import com.za.ext.goNextPage
import com.za.ext.navigationActivity
import com.za.servicing.R
import com.za.ui.camera.ZdCameraXActivity
import com.za.ui.servicing.inservice_people_confirm.ServicePeopleConfirmActivity
import com.za.ui.servicing.view.InServicingHeadView
@ -89,19 +85,6 @@ fun WaitToStartScreen(vm : WaitToStartVm = viewModel()) {
val lifecycleOwner = LocalLifecycleOwner.current
val mapView = remember { MapView(context) }
val getResult =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) { it ->
if (it.resultCode == Activity.RESULT_OK) {
val value = it.data?.getStringExtra("path")
LogUtil.print("takePhoto", "path==$value")
if (value.isNullOrBlank()) {
vm.updateState(uiState.value.copy(comparableFailedStr = "照片路径为空,请重新认证!"))
return@rememberLauncherForActivityResult
}
vm.dispatch(WaitToStartVm.Action.CompareServicePeople(value))
}
}
// 添加 BottomSheet 状态
val bottomSheetState = rememberStandardBottomSheetState(initialValue = SheetValue.Expanded)
val scaffoldState = rememberBottomSheetScaffoldState(bottomSheetState = bottomSheetState)
@ -166,27 +149,7 @@ fun WaitToStartScreen(vm : WaitToStartVm = viewModel()) {
confirm = {
vm.dispatch(WaitToStartVm.Action.UpdateState(uiState.value.copy(
showServicePeopleConfirmDialog = false)))
val intent = Intent(context, ZdCameraXActivity::class.java)
intent.putExtra("isBack", false)
getResult.launch(intent)
})
}
if (! uiState.value.comparableFailedStr.isNullOrBlank()) {
CommonDialog(title = uiState.value.comparableFailedStr,
confirmText = "重新核验",
confirm = {
vm.dispatch(WaitToStartVm.Action.UpdateState(uiState.value.copy(comparableFailedStr = null)))
val intent = Intent(context, ZdCameraXActivity::class.java)
intent.putExtra("isBack", false)
getResult.launch(intent)
},
cancelText = "关闭",
cancel = {
vm.dispatch(WaitToStartVm.Action.UpdateState(uiState.value.copy(comparableFailedStr = null)))
},
dismiss = {
vm.dispatch(WaitToStartVm.Action.UpdateState(uiState.value.copy(comparableFailedStr = null)))
context.navigationActivity(ServicePeopleConfirmActivity::class.java, true)
})
}

View File

@ -1,6 +1,5 @@
package com.za.ui.servicing.wait_to_start
import androidx.lifecycle.viewModelScope
import com.amap.api.maps.AMapUtils
import com.amap.api.maps.model.BitmapDescriptorFactory
import com.amap.api.maps.model.LatLng
@ -15,27 +14,16 @@ import com.blankj.utilcode.util.ToastUtils
import com.za.base.IServicingVm
import com.za.base.view.LoadingManager
import com.za.bean.db.order.OrderInfo
import com.za.bean.request.DriverFaceCompareRequest
import com.za.bean.request.IaiCompareFaceBean
import com.za.bean.request.UpdateTaskBean
import com.za.bean.request.UpdateTaskRequest
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.ext.toJson
import com.za.net.BaseObserver
import com.za.net.CommonMethod
import com.za.net.RetrofitHelper
import com.za.service.location.ZdLocationManager
import com.za.servicing.R
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.io.File
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
@ -55,7 +43,6 @@ class WaitToStartVm : IServicingVm<WaitToStartVm.Action, WaitToStartVm.UiState>(
is Action.UpdateState -> updateState(action.uiState)
is Action.StartTimer -> startTimer()
is Action.UpdateTimer -> updateTimer()
is Action.CompareServicePeople -> compareServicePeople(action.localPath)
}
}
@ -231,52 +218,6 @@ class WaitToStartVm : IServicingVm<WaitToStartVm.Action, WaitToStartVm.UiState>(
private fun updateTimer() { // 在这里处理倒计时更新逻辑
}
private fun compareServicePeople(localPath : String) {
LoadingManager.showLoading()
CommonMethod.uploadImage(file = File(localPath), success = { it ->
compilePeople(it, success = {
LoadingManager.hideLoading()
updateTask()
}, failed = {
LoadingManager.hideLoading()
updateState(uiState.value.copy(comparableFailedStr = it))
})
}, failed = {
LoadingManager.hideLoading()
updateState(uiState.value.copy(comparableFailedStr = it))
})
}
private fun compilePeople(url : String?,
success : (IaiCompareFaceBean?) -> Unit,
failed : (String) -> Unit) {
if (url.isNullOrBlank()) {
ToastUtils.showLong("图片路径为空!")
return
}
val driverFaceCompareRequest =
DriverFaceCompareRequest(vehicleId = GlobalData.driverInfoBean?.vehicleId,
driverId = GlobalData.driverInfoBean?.userId,
photoUrl = url)
RetrofitHelper.getDefaultService().iaiCompareFace(driverFaceCompareRequest)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<IaiCompareFaceBean>() {
override fun doSuccess(it : IaiCompareFaceBean?) {
if (it?.compareResult == true) {
success(it)
return
}
failed("人脸对比失败")
LogUtil.print("face", "人脸对比成功:$it")
}
override fun doFailure(code : Int, msg : String?) {
failed(msg ?: "人脸对比失败")
LogUtil.print("face", "人脸对比失败:$msg")
}
})
}
override fun onCleared() {
super.onCleared()
timerJob?.cancel()
@ -288,7 +229,6 @@ class WaitToStartVm : IServicingVm<WaitToStartVm.Action, WaitToStartVm.UiState>(
data class UpdateState(val uiState : UiState) : Action()
data object StartTimer : Action()
data object UpdateTimer : Action()
data class CompareServicePeople(val localPath : String) : Action()
}
data class UiState(val orderInfo : OrderInfo? = null,
@ -299,6 +239,5 @@ class WaitToStartVm : IServicingVm<WaitToStartVm.Action, WaitToStartVm.UiState>(
val routePoints : List<LatLng>? = null,
val remainingDistance : Float = 0f,
val showServicePeopleConfirmDialog : Boolean? = false,
val comparableFailedStr : String? = null,
val estimatedArrivalTime : String = "")
}

View File

@ -0,0 +1,25 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19.824,19.321C23.983,15.161 23.982,8.418 19.823,4.26C15.664,0.101 8.921,0.101 4.762,4.26C0.603,8.418 0.603,15.161 4.762,19.321C6.759,21.318 9.468,22.44 12.293,22.44C15.118,22.44 17.827,21.318 19.824,19.321L19.824,19.321Z"
android:strokeWidth="1.3"
android:fillColor="#00000000"
android:fillType="nonZero"
android:strokeColor="#2C2C2C"/>
<path
android:pathData="M7.585,14.291C7.821,14.716 9.15,16.728 12.291,16.728C15.432,16.728 16.704,14.746 17.1,14.291"
android:strokeWidth="1.3"
android:fillColor="#00000000"
android:strokeColor="#FF4200"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M9.62,9.759C9.62,10.421 9.085,10.957 8.423,10.957C7.76,10.957 7.224,10.424 7.224,9.759C7.224,9.093 7.76,8.561 8.423,8.561C9.085,8.561 9.62,9.097 9.62,9.759ZM17.573,9.759C17.573,10.421 17.037,10.957 16.375,10.957C15.713,10.957 15.176,10.421 15.176,9.759C15.176,9.096 15.712,8.561 16.375,8.561C17.039,8.561 17.573,9.097 17.573,9.759Z"
android:strokeWidth="1"
android:fillColor="#352B2B"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,54 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="25dp"
android:viewportWidth="24"
android:viewportHeight="25">
<path
android:pathData="M7.329,17.2L16.982,17.2L7.329,17.2Z"
android:strokeWidth="1.3"
android:fillColor="#00000000"
android:strokeColor="#2C2C2C"
android:fillType="evenOdd"/>
<path
android:pathData="M8.359,23.798L15.776,23.798"
android:strokeWidth="1.2"
android:fillColor="#00000000"
android:strokeColor="#979797"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M12.362,10.031l0.291,6.447l-1.3,0.001l0.202,-6.448z"
android:strokeWidth="1"
android:fillColor="#FF4200"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M12.003,1.7C16.604,1.7 20.334,5.43 20.334,10.031C20.334,13.079 18.698,15.744 16.256,17.197L16.982,17.196L16.982,19.612C16.982,20.662 16.131,21.512 15.082,21.512L9.229,21.512C8.18,21.512 7.329,20.662 7.329,19.612L7.329,17.196L7.75,17.197C5.308,15.744 3.672,13.079 3.672,10.031C3.672,5.43 7.402,1.7 12.003,1.7Z"
android:strokeWidth="1.3"
android:fillColor="#00000000"
android:strokeColor="#2C2C2C"
android:fillType="evenOdd"/>
<path
android:pathData="M8.359,23.798L15.776,23.798"
android:strokeWidth="1.2"
android:fillColor="#00000000"
android:strokeColor="#2C2C2C"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M8.514,9.366l1.183,2.582l2.304,-3.102"
android:strokeLineJoin="round"
android:strokeWidth="1.3"
android:fillColor="#00000000"
android:strokeColor="#FF4200"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M12,8.846l2.246,3.102l1.267,-2.582"
android:strokeLineJoin="round"
android:strokeWidth="1.3"
android:fillColor="#00000000"
android:strokeColor="#FF4200"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,25 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="25dp"
android:height="24dp"
android:viewportWidth="25"
android:viewportHeight="24">
<path
android:pathData="M8.604,4.595L16.324,4.595C16.877,4.595 17.324,5.043 17.324,5.595L17.324,14.119C17.324,14.672 16.877,15.119 16.324,15.119L8.604,15.119C8.052,15.119 7.604,14.672 7.604,14.119L7.604,5.595C7.604,5.043 8.052,4.595 8.604,4.595Z"
android:strokeWidth="1.2"
android:fillColor="#00000000"
android:strokeColor="#2C2C2C"
android:fillType="evenOdd"/>
<path
android:pathData="M10.822,19.086L14.252,19.086"
android:strokeWidth="1.3"
android:fillColor="#00000000"
android:strokeColor="#FF4200"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M6.672,1.427L18.369,1.427C19.473,1.427 20.369,2.323 20.369,3.427L20.369,20.715C20.369,21.82 19.473,22.715 18.369,22.715L6.672,22.715C5.567,22.715 4.672,21.82 4.672,20.715L4.672,3.427C4.672,2.323 5.567,1.427 6.672,1.427Z"
android:strokeWidth="1.2"
android:fillColor="#00000000"
android:strokeColor="#2C2C2C"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,220 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="215dp"
android:height="215dp"
android:viewportWidth="215"
android:viewportHeight="215">
<path
android:pathData="M215,107.5C215,48.13 166.87,0 107.5,0C48.13,0 0,48.13 0,107.5C0,166.87 48.13,215 107.5,215"
android:strokeAlpha="0.4359189"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#FE4402"
android:fillType="evenOdd"
android:fillAlpha="0.4359189"/>
<group>
<clip-path
android:pathData="M107.5,107.5m-93,0a93,93 0,1 1,186 0a93,93 0,1 1,-186 0"/>
<path
android:pathData="M-1,176.15L216,176.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,148.15L216,148.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,190.15L216,190.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,162.15L216,162.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,121.15L216,121.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,135.15L216,135.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,93.15L216,93.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,107.15L216,107.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,66.15L216,66.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,79.15L216,79.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,39.15L216,39.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,25.15L216,25.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M-1,52.15L216,52.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
</group>
<group>
<clip-path
android:pathData="M107.5,107.5m-93,0a93,93 0,1 1,186 0a93,93 0,1 1,-186 0"/>
<path
android:pathData="M39,-0.85L39,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M67,-0.85L67,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M25,-0.85L25,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M53,-0.85L53,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M94,-0.85L94,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M80,-0.85L80,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M122,-0.85L122,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M108,-0.85L108,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M149,-0.85L149,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M136,-0.85L136,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M176,-0.85L176,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M190,-0.85L190,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
<path
android:pathData="M163,-0.85L163,216.15"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#E7E7E7"
android:fillType="evenOdd"/>
</group>
<path
android:pathData="M107.5,200.5C158.86,200.5 200.5,158.86 200.5,107.5C200.5,56.14 158.86,14.5 107.5,14.5C56.14,14.5 14.5,56.14 14.5,107.5C14.5,158.86 56.14,200.5 107.5,200.5Z"
android:strokeWidth="22"
android:fillColor="#00000000"
android:strokeColor="#F5CBBD"
android:fillType="evenOdd"/>
<path
android:pathData="M107.5,215C166.87,215 215,166.87 215,107.5"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#F5CBBD"
android:fillType="evenOdd"/>
<path
android:pathData="M144,26.96C132.74,21.85 120.23,19 107.04,19C93.82,19 81.28,21.86 70,27M20.65,126.23C23.34,138.3 28.16,150.16 36.1,160.69C44.06,171.25 53.89,179.55 64.78,185.46M195.13,123.77C192.87,135.93 188.46,147.95 180.89,158.75C173.31,169.58 163.69,177.72 153.02,184.01"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#F99571"
android:fillType="evenOdd"/>
<path
android:pathData="M177,107.5A69.5,69.5 0,0 1,107.5 177,69.5 69.5,0 0,1 38,107.5 69.5,69.5 0,0 1,107.5 38,69.5 69.5,0 0,1 177,107.5Z"
android:strokeWidth="1"
android:fillType="evenOdd"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:startX="107.5"
android:startY="38"
android:endX="107.5"
android:endY="177"
android:type="linear">
<item android:offset="0" android:color="#FFF4CCBE"/>
<item android:offset="1" android:color="#FFFE4402"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m109.33,53.5c18.54,0.43 31.25,11.28 35.71,26.49 0.1,0.33 0.19,0.67 0.28,1 1.61,6.2 1.55,12.18 0.44,19.98l-0.3,2.03 0.23,0.23c8.25,8.33 5.48,22.07 -6.01,26.53l-0.08,0.27c-1.31,4.3 -4.27,9.53 -8.88,15.71 -0.55,0.78 -1.13,1.54 -1.74,2.28 -0.45,0.58 -0.92,1.17 -1.4,1.77l-0.1,-0.08c-4.49,4.74 -10.46,8.36 -17.86,10.86l-1.02,0.33 -0.35,-0.06c-7.51,-1.27 -14.4,-4.92 -20.65,-10.92l-0.13,-0.14C81.02,141.81 77.01,135.25 75.42,130.04l-0.08,-0.27C63.84,125.31 61.07,111.57 69.33,103.24l0.23,-0.23 -0.2,-1.32c-1.21,-8.15 -1.32,-14.31 0.34,-20.7 0.09,-0.34 0.18,-0.67 0.28,-1C74.33,65.09 86.64,54.35 104.61,53.54l0.62,-0.02 3.78,-0.02 0.05,-0z"
android:strokeWidth="1"
android:fillColor="#f4f4f4"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.