feat(network): 优化网络异常处理和环境切换

- 新增网络异常统一处理逻辑
- 实现环境切换功能
- 更新 API接口
- 重构部分代码以提高可维护性
This commit is contained in:
songzhiling
2025-05-21 15:20:52 +08:00
parent 502e1cd604
commit f29cac2d73
39 changed files with 2450 additions and 1026 deletions

View File

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

View File

@ -10,6 +10,7 @@ fastjson = "1.2.69"
glide = "4.16.0"
gson = "2.11.0"
jcore = "3.3.2"
faceDetection = "16.1.7"
jpush = "4.8.1"
location = "5.6.1"
loggingInterceptor = "4.11.0"
@ -100,6 +101,7 @@ utilcodex = { module = "com.blankj:utilcodex", version.ref = "utilcodex" }
xdmap = { module = "com.amap.api:3dmap", version.ref = "xdmap" }
xz = { module = "org.tukaani:xz", version.ref = "xz" }
androidx-exifinterface = { group = "androidx.exifinterface", name = "exifinterface", version.ref = "exifinterface" }
face-detection = { module = "com.google.mlkit:face-detection", version.ref = "faceDetection" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

View File

@ -73,7 +73,7 @@ publishing {
release(MavenPublication) {
groupId = 'io.github.szl9'
artifactId = 'zd_servicing'
version = "1.0.1.9.9.68"
version = "1.0.1.9.9.100"
pom {
packaging = "aar"
@ -219,5 +219,7 @@ dependencies {
api libs.org.eclipse.paho.client.mqttv3
api libs.org.eclipse.paho.android.service
api libs.face.detection
}

View File

@ -77,10 +77,19 @@
</queries>
<application
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
tools:targetApi="24">
<activity
android:name="com.za.base.NetworkRouteSelectionActivity"
android:exported="true"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.camera.ServicePeopleRealActivity"
android:exported="true"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.inservice_people_confirm.ServicePeopleConfirmActivity"
android:exported="true"

View File

@ -1,45 +1,81 @@
package com.za.base
import com.za.common.GlobalData
import com.za.net.RetrofitHelper
object AppConfig {
var isRelease = false
// API 相关地址
lateinit var BASE_URL: String // API 主地址
lateinit var IMG_BASE_URL: String // 图片服务器地址
lateinit var Resource_URL: String // 资源服务器地址
lateinit var BASE_URL : String // API 主地址
lateinit var IMG_BASE_URL : String // 图片服务器地址
lateinit var Resource_URL : String // 资源服务器地址
// H5 相关地址
lateinit var TRAIN_URL: String // 培训文档地址
lateinit var DOCMENT_URL: String // 中道资料地址
lateinit var trainUrl : String // 培训文档地址
lateinit var documentUrl : String // 中道资料地址
lateinit var newDriverTrainUrl : String // 新司机培训地址
fun init(isRelease : Boolean? = false) {
val envType = GlobalData.networkEnv
if (isRelease == true) {
when (envType) {
Const.NetEnv.Main -> release()
Const.NetEnv.Review -> review()
}
} else {
when (envType) {
Const.NetEnv.CRM1 -> crm1()
Const.NetEnv.CRM2 -> crm2()
Const.NetEnv.UAT -> uat()
}
}
}
fun changeEnv(envType : Int) {
GlobalData.networkEnv = envType
when (envType) {
Const.NetEnv.Main -> release()
Const.NetEnv.Review -> review()
Const.NetEnv.CRM1 -> crm1()
Const.NetEnv.CRM2 -> crm2()
Const.NetEnv.UAT -> uat()
}
RetrofitHelper.reset()
}
/**
* 正式环境配置
*/
fun release() {
isRelease = true
// API 配置
isRelease = true // API 配置
GlobalData.networkEnv = Const.NetEnv.Main
BASE_URL = "https://api.sinoassist.com"
IMG_BASE_URL = "https://api.sinoassist.com"
Resource_URL = "https://www.sinoassist.com/res"
// H5 配置
TRAIN_URL = "https://www.sinoassist.com/h5/supplier/dispatch/diverTrainDocment"
DOCMENT_URL = "https://www.sinoassist.com/h5/supplier/dispatch/docmentList"
trainUrl = "https://www.sinoassist.com/h5/supplier/dispatch/diverTrainDocment"
documentUrl = "https://www.sinoassist.com/h5/supplier/dispatch/docmentList"
newDriverTrainUrl = "https://www.sinoassist.com/h5/supplier/dispatch/driverTrainingList";
}
/**
* 审核环境配置
*/
fun review() {
isRelease = true
private fun review() {
isRelease = true // API 配置
GlobalData.networkEnv = Const.NetEnv.Review
// API 配置
BASE_URL = "http://interface.review.sino-assist.com"
IMG_BASE_URL = "http://interface.review.sino-assist.com"
Resource_URL = "https://www.sinoassist.com/res"
documentUrl = "http://interface.review.sino-assist.com/h5/supplier/dispatch/docmentList"
trainUrl = "http://interface.review.sino-assist.com/h5/supplier/dispatch/diverTrainDocment"
newDriverTrainUrl =
"http://interface.review.sino-assist.com/h5/supplier/dispatch/driverTrainingList"
}
/**
@ -47,15 +83,16 @@ object AppConfig {
*/
fun crm1() {
isRelease = false
GlobalData.networkEnv = Const.NetEnv.CRM1
// API 配置
BASE_URL = "https://api1.sino-assist.com"
IMG_BASE_URL = "https://api1.sino-assist.com"
Resource_URL = "https://crm1.sino-assist.com/res"
// H5 配置
TRAIN_URL = "https://crm1.sino-assist.com/h5/supplier/dispatch/diverTrainDocment"
DOCMENT_URL = "https://crm1.sino-assist.com/h5/supplier/dispatch/docmentList"
documentUrl = "https://crm1.sino-assist.com/h5/supplier/dispatch/docmentList";
trainUrl = "https://crm1.sino-assist.com/h5/supplier/dispatch/diverTrainDocment";
newDriverTrainUrl = "https://crm1.sino-assist.com/h5/supplier/dispatch/driverTrainingList";
}
/**
@ -63,6 +100,7 @@ object AppConfig {
*/
fun crm2() {
isRelease = false
GlobalData.networkEnv = Const.NetEnv.CRM2
// API 配置
BASE_URL = "https://api2.sino-assist.com"
@ -70,17 +108,33 @@ object AppConfig {
Resource_URL = "https://crm2.sino-assist.com/res"
}
fun uat() {
isRelease = false
GlobalData.networkEnv = Const.NetEnv.UAT
BASE_URL = "https://api-uat.sino-assist.com" //crm2
IMG_BASE_URL = "https://api-uat.sino-assist.com"
Resource_URL = "https://uat.sino-assist.com/res"
documentUrl = "https://uat.sino-assist.com/h5/supplier/dispatch/docmentList"
trainUrl = "https://uat.sino-assist.com/h5/supplier/dispatch/diverTrainDocment"
newDriverTrainUrl = "https://uat.sino-assist.com/h5/supplier/dispatch/driverTrainingList"
documentUrl = "https://uat.sino-assist.com/h5/supplier/dispatch/docmentList";
trainUrl = "https://uat.sino-assist.com/h5/supplier/dispatch/diverTrainDocment";
newDriverTrainUrl = "https://uat.sino-assist.com/h5/supplier/dispatch/driverTrainingList";
}
/**
* 获取培训文档完整地址
* @param driverId 司机ID
* @param keyword 关键字
* @return 完整的培训文档URL
*/
fun getTrainUrl(keyWord: String = ""): String {
fun getTrainUrl(keyWord : String = "") : String {
if (keyWord.isEmpty()) {
return TRAIN_URL + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfoBean?.userId}"
return trainUrl + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfoBean?.userId}"
}
return TRAIN_URL + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfoBean?.userId}&keyword=$keyWord"
return trainUrl + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfoBean?.userId}&keyword=$keyWord"
}
/**
@ -89,10 +143,10 @@ object AppConfig {
* @param keyword 关键字
* @return 完整的中道资料URL
*/
fun getDocmentUrl(keyWord: String = ""): String {
fun getDocmentUrl(keyWord : String = "") : String {
if (keyWord.isEmpty()) {
return DOCMENT_URL + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfoBean?.userId}"
return documentUrl + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfoBean?.userId}"
}
return DOCMENT_URL + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfoBean?.userId}&keyword=$keyWord"
return documentUrl + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfoBean?.userId}&keyword=$keyWord"
}
}

View File

@ -42,4 +42,12 @@ object Const {
const val ORDER_DETAIL = 2 //案件详情
const val ORDER_GIVE_UP = 3 //订单放弃
}
object NetEnv {
const val Main = 0 //正线
const val Review = 1 //正线
const val CRM1 = 2 //测试环境
const val CRM2 = 3 //测试环境
const val UAT = 4 //测试环境
}
}

View File

@ -0,0 +1,442 @@
package com.za.base
import android.Manifest
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import android.telephony.PhoneStateListener
import android.telephony.SignalStrength
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
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.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
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.core.app.ActivityCompat
import com.blankj.utilcode.util.AppUtils
import com.za.base.theme.bgColor
import com.za.base.view.HeadView
import com.za.bean.BaseResponse
import com.za.bean.UpdateVersionBean
import com.za.bean.UpdateVersionRequest
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.ext.finish
import com.za.net.BaseObserver
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.POST
import java.util.concurrent.TimeUnit
data class Route(val name : String,
val url : String,
val id : String = name,
val type : Int) // 添加id字段用于唯一标识
// 定义所有可用线路
val availableRoutes = if (AppConfig.isRelease) {
listOf(
Route("主线路", "https://api.sinoassist.com", "main", type = Const.NetEnv.Main),
Route("备用线路",
"http://interface.review.sino-assist.com",
"review",
type = Const.NetEnv.Review),
)
} else {
listOf(Route("CRM1", "https://api1.sino-assist.com", "crm1", type = Const.NetEnv.CRM1),
Route("CRM2", "https://api2.sino-assist.com", "crm2", type = Const.NetEnv.CRM2),
Route("UAT", "https://api-uat.sino-assist.com", "uat", type = Const.NetEnv.UAT))
}
// 定义检测状态枚举
enum class DetectionStatus {
UNKNOWN, // 未检测
CHECKING, // 检测中
NORMAL, // 正常
ABNORMAL // 异常
}
class NetworkRouteSelectionActivity : BaseActivity() {
@Composable
override fun ContentView() {
NetworkRouteScreen(telephonyManager = getSystemService(TELEPHONY_SERVICE) as TelephonyManager,
connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager)
}
}
@Composable
fun NetworkInfo(telephonyManager : TelephonyManager, connectivityManager : ConnectivityManager) {
val context = LocalContext.current
var networkType by remember { mutableStateOf("") }
var signalStrength by remember { mutableIntStateOf(- 1) }
var operatorName by remember { mutableStateOf("") }
val scope = rememberCoroutineScope()
// 更新网络信息
LaunchedEffect(Unit) {
if (ActivityCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
) { // 获取运营商名称
operatorName = telephonyManager.networkOperatorName
// 使用新的 API 获取网络类型
val networkCapabilities =
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
networkType = when {
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true -> "WiFi"
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
when (telephonyManager.dataNetworkType) {
TelephonyManager.NETWORK_TYPE_LTE -> "4G"
TelephonyManager.NETWORK_TYPE_NR -> "5G"
TelephonyManager.NETWORK_TYPE_UMTS -> "3G"
TelephonyManager.NETWORK_TYPE_EDGE -> "2G"
else -> "未知"
}
} else {
"未知"
}
}
else -> "无网络"
}
// 使用新的 TelephonyCallback API 监听信号强度变化
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val callback =
object : TelephonyCallback(), TelephonyCallback.SignalStrengthsListener {
override fun onSignalStrengthsChanged(signalStrengths : SignalStrength) {
scope.launch(Dispatchers.Main) {
signalStrength = signalStrengths.level
LogUtil.print("NetworkInfo", "signalStrength: $signalStrength")
}
}
}
telephonyManager.registerTelephonyCallback(context.mainExecutor, callback)
} else {
@Suppress("DEPRECATION") telephonyManager.listen(object : PhoneStateListener() {
override fun onSignalStrengthsChanged(signalStrengths : SignalStrength) {
super.onSignalStrengthsChanged(signalStrengths)
scope.launch(Dispatchers.Main) {
signalStrength = signalStrengths.level
LogUtil.print("NetworkInfo", "signalStrength: $signalStrength")
}
}
}, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS)
}
}
}
Card(modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.elevatedCardElevation(4.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "当前手机网络",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFF2E7D32))
Spacer(modifier = Modifier.height(8.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Column {
Text("运营商:$operatorName", fontSize = 16.sp)
Text("网络类型:$networkType", fontSize = 16.sp)
Text(text = "信号强度:${
when (signalStrength) {
0 -> "无信号"
1 -> ""
2 -> "一般"
3 -> "良好"
4 -> "极好"
else -> "未知"
}
}", fontSize = 16.sp)
}
}
}
}
}
@Composable
fun RouteItem(
route : Route,
isSelected : Boolean,
onSelect : () -> Unit,
) {
var detectionStatus by remember { mutableStateOf(DetectionStatus.UNKNOWN) }
var errorMessage by remember { mutableStateOf<String?>(null) }
var isDetecting by remember { mutableStateOf(false) }
var detectionTime by remember { mutableStateOf(0L) }
// 检测函数
fun detectRoute() {
if (isDetecting) return
isDetecting = true
detectionStatus = DetectionStatus.CHECKING
val startTime = System.currentTimeMillis()
try {
val request = UpdateVersionRequest(appVersion = AppUtils.getAppVersionName())
NetWorkRetrofit.getDefaultService(route.url).checkConnection(request)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<UpdateVersionBean>() {
override fun doFailure(code : Int, msg : String?) {
detectionTime = System.currentTimeMillis() - startTime
if (code == 999) {
detectionStatus = DetectionStatus.ABNORMAL
errorMessage = msg
} else {
detectionStatus = DetectionStatus.NORMAL
}
isDetecting = false
}
override fun doSuccess(it : UpdateVersionBean?) {
detectionTime = System.currentTimeMillis() - startTime
detectionStatus = DetectionStatus.NORMAL
isDetecting = false
}
})
} catch (e : Exception) {
detectionTime = System.currentTimeMillis() - startTime
detectionStatus = DetectionStatus.ABNORMAL
errorMessage = e.message ?: "连接失败"
isDetecting = false
}
}
// 初始检测
LaunchedEffect(Unit) {
detectRoute()
}
Column {
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable { onSelect() }
.padding(vertical = 5.dp)
.border(width = if (isSelected) 1.dp else 0.dp,
color = if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent,
shape = RoundedCornerShape(4.dp))
.padding(8.dp)) {
RadioButton(selected = isSelected, onClick = { onSelect() })
Text(text = route.name,
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
modifier = Modifier.padding(start = 8.dp))
Spacer(Modifier.weight(1f))
if (isDetecting) {
CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp)
Spacer(modifier = Modifier.width(8.dp))
}
val statusText = when (detectionStatus) {
DetectionStatus.NORMAL -> "正常 (${detectionTime}ms)"
DetectionStatus.ABNORMAL -> "异常 (${detectionTime}ms)"
DetectionStatus.CHECKING -> "检测中..."
DetectionStatus.UNKNOWN -> ""
}
val statusColor = when (detectionStatus) {
DetectionStatus.NORMAL -> Color(0xFF2E7D32)
DetectionStatus.ABNORMAL -> Color(0xFFC62828)
DetectionStatus.CHECKING -> Color(0xFF1565C0)
DetectionStatus.UNKNOWN -> Color.Gray
}
Text(text = statusText,
color = statusColor,
fontSize = 14.sp,
modifier = Modifier.padding(horizontal = 8.dp))
Box(modifier = Modifier
.clickable(enabled = ! isDetecting) { detectRoute() }
.background(color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(5.dp))
.padding(vertical = 5.dp, horizontal = 10.dp)) {
Text("检测", fontSize = 12.sp, color = Color.White)
}
}
// 显示错误信息(如果有)
if (detectionStatus == DetectionStatus.ABNORMAL && errorMessage != null) {
Text(text = "错误: $errorMessage",
color = Color(0xFFC62828),
fontSize = 12.sp,
modifier = Modifier.padding(start = 40.dp, bottom = 8.dp))
}
}
}
@Composable
fun NetworkRouteScreen(telephonyManager : TelephonyManager,
connectivityManager : ConnectivityManager) {
// 原有的线路选择部分
val context = LocalContext.current
var selectedRoute by remember { mutableStateOf(availableRoutes.find { GlobalData.networkEnv == it.type }) }
var showApplyDialog by remember { mutableStateOf(false) }
// 应用当前选中的线路
fun applySelectedRoute() {
selectedRoute?.let {
showApplyDialog = false
GlobalData.networkEnv = it.type
AppConfig.changeEnv(it.type)
}
}
// 显示确认对话框
if (showApplyDialog) {
AlertDialog(onDismissRequest = { showApplyDialog = false },
title = { Text("确认应用线路") },
text = { Text("确定要将当前线路切换为 ${selectedRoute?.name} 吗?") },
confirmButton = {
Button(onClick = { applySelectedRoute() }) {
Text("确定")
}
},
dismissButton = {
OutlinedButton(onClick = { showApplyDialog = false }) {
Text("取消")
}
})
}
Scaffold(topBar = { HeadView(title = "线路选择", onBack = { context.finish() }) }) {
Column(modifier = Modifier
.fillMaxSize()
.verticalScroll(state = rememberScrollState())
.padding(it)
.background(color = bgColor)
.padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally) { // 标题卡片
NetworkInfo(telephonyManager, connectivityManager)
// 线路选择区域
Card(modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.elevatedCardElevation(4.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Text("选择线路",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 8.dp))
Divider()
availableRoutes.forEachIndexed { index, route ->
RouteItem(route = route,
isSelected = route == selectedRoute,
onSelect = { selectedRoute = route })
if (index < availableRoutes.size - 1) {
Divider()
}
}
}
}
// 应用按钮
Button(onClick = { showApplyDialog = true },
enabled = selectedRoute != null,
modifier = Modifier.fillMaxWidth()) {
Icon(Icons.Default.Check, contentDescription = null)
Spacer(modifier = Modifier.width(4.dp))
Text("应用选中线路")
}
}
}
}
// 定义API接口
private interface RouteCheckApi {
@POST("/driverApp/base/appVersion")
fun checkConnection(@Body versionRequest : UpdateVersionRequest) : Observable<BaseResponse<UpdateVersionBean>>
}
// 在类的外部定义Retrofit管理器
private object NetWorkRetrofit {
private val client =
OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(5, TimeUnit.SECONDS)
.build()
private val retrofitMap = mutableMapOf<String, Retrofit>()
private fun getRetrofit(baseUrl : String) : Retrofit {
return retrofitMap.getOrPut(baseUrl) {
Retrofit.Builder().baseUrl(baseUrl).client(client)
.addConverterFactory(GsonConverterFactory.create()) // Add this line
.addCallAdapterFactory(RxJava3CallAdapterFactory.create()).build()
}
}
fun getDefaultService(baseUrl : String) : RouteCheckApi {
return getRetrofit(baseUrl).create(RouteCheckApi::class.java)
}
fun clearCache() {
retrofitMap.clear()
}
}

View File

@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.shape.CircleShape
@ -31,21 +30,14 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.za.base.NetworkRouteSelectionActivity
import com.za.base.theme.headBgColor
import com.za.base.theme.white95
import com.za.bean.request.ReadTrainingCountRequest
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.net.BaseObserver
import com.za.net.RetrofitHelper
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import com.za.ext.navigationActivity
val warnBean = mutableStateOf<IWarnBean?>(null)
@ -60,59 +52,19 @@ fun AppTipsView() {
when (warnBean) {
is NetWarnBean -> {
// NetTipView(warnBean as NetWarnBean)
}
is ReadTrainingCountBean -> {
// TrainingDocView(warnBean as ReadTrainingCountBean)
NetworkWeakView(warnBean as NetWarnBean)
}
else -> {}
}
}
@Composable
fun NetTipView(tipsBean : NetWarnBean) {
Row(modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.background(color = Color.Yellow)
.padding(top = 20.dp, start = 20.dp, end = 20.dp, bottom = 5.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center) {
Text(tipsBean.message, color = white95)
}
}
@Composable
fun TrainingDocView(readTrainingCountBean : ReadTrainingCountBean) {
val showTrainingDialog = remember { mutableStateOf(false) }
fun NetworkWeakView(netWarnBean : NetWarnBean) {
val context = LocalContext.current
if (showTrainingDialog.value) {
CommonDialog(title = "培训提醒",
message = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, fontSize = 16.sp)) {
append("重要提示:\n")
}
withStyle(style = SpanStyle(fontSize = 14.sp)) {
append("您有未完成的培训任务,为确保工作顺利开展,请尽快完成培训。\n\n")
}
withStyle(style = SpanStyle(color = Color(0xFFE65100),
fontSize = 15.sp,
fontWeight = FontWeight.Medium)) {
append("剩余培训任务:${readTrainingCountBean.mustReadTrainingCount ?: 0}")
}
}.toString(),
confirmText = "立即前往",
cancelText = "稍后提醒",
cancelEnable = true,
confirm = {
showTrainingDialog.value = false
},
dismiss = { showTrainingDialog.value = false })
}
AnimatedVisibility(visible = true,
AnimatedVisibility(context !is NetworkRouteSelectionActivity,
modifier = Modifier.background(color = headBgColor),
enter = fadeIn(animationSpec = tween(300)) + expandVertically(animationSpec = tween(300),
expandFrom = Alignment.Top),
@ -138,11 +90,12 @@ fun TrainingDocView(readTrainingCountBean : ReadTrainingCountBean) {
modifier = Modifier.size(24.dp))
}
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(text = "培训提醒",
Text(text = "提醒",
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
color = Color(0xFF424242),
fontSize = 14.sp)
Text(text = "您有 ${readTrainingCountBean.mustReadTrainingCount ?: 0} 个培训任务待完成",
Text(text = "当前线路异常,是否前往切换线路",
style = MaterialTheme.typography.bodyMedium,
color = Color(0xFF757575),
fontSize = 12.sp)
@ -150,18 +103,20 @@ fun TrainingDocView(readTrainingCountBean : ReadTrainingCountBean) {
}
Box(modifier = Modifier
.clickable {}
.clickable {
context.navigationActivity(NetworkRouteSelectionActivity::class.java)
}
.background(color = Color(0xFFFF9800), shape = RoundedCornerShape(5.dp))
.padding(horizontal = 10.dp, vertical = 5.dp),
contentAlignment = Alignment.Center) {
Text("去完成", color = Color.White, style = MaterialTheme.typography.labelLarge)
}
}
}
}
}
private fun fetchAppTipsData() {
if (GlobalData.currentOrder != null) {
return
@ -169,20 +124,6 @@ private fun fetchAppTipsData() {
if (GlobalData.token == null) {
return
}
val request = ReadTrainingCountRequest(driverId = GlobalData.driverInfoBean?.userId?.toInt(),
userId = GlobalData.driverInfoBean?.userId,
supplierId = GlobalData.driverInfoBean?.supplierId.toString())
RetrofitHelper.getDefaultService().getReadTrainingCount(request).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<ReadTrainingCountBean>() {
override fun doSuccess(it : ReadTrainingCountBean?) {
warnBean.value = it
}
override fun doFailure(code : Int, msg : String?) {
LogUtil.print("fetchAppTipsData ", "doFailure==$msg")
}
})
}
abstract class IWarnBean

View File

@ -33,23 +33,20 @@ import com.za.common.util.MapUtil
import com.za.servicing.R
@Composable
fun CommonDialog(
title: String? = null,
confirmText: String = "确定",
confirm: () -> Unit,
content: @Composable () -> Unit = {},
message: String? = null,
cancelText: String? = "取消",
cancelEnable: Boolean = true,
cancel: () -> Unit = {},
dismiss: () -> Unit
) {
fun CommonDialog(title : String? = null,
confirmText : String = "确定",
confirm : () -> Unit,
content : @Composable () -> Unit = {},
message : String? = null,
cancelText : String? = "取消",
cancelEnable : Boolean = true,
cancel : () -> Unit = {},
dismiss : () -> Unit) {
Dialog(onDismissRequest = { dismiss() },
properties = DialogProperties(
dismissOnBackPress = cancelEnable,
properties = DialogProperties(dismissOnBackPress = cancelEnable,
dismissOnClickOutside = cancelEnable)) {
Box(modifier = Modifier
.background(color = Color.White, shape = RoundedCornerShape(13.dp))) {
Box(modifier = Modifier.background(color = Color.White,
shape = RoundedCornerShape(13.dp))) {
Spacer(modifier = Modifier
.fillMaxWidth()
.height(105.dp)
@ -62,7 +59,10 @@ fun CommonDialog(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
Spacer(modifier = Modifier.height(35.dp))
Text(text = "$title", fontSize = 16.sp, fontWeight = FontWeight.Medium, color = Color(0xFF2A4054))
Text(text = "$title",
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF2A4054))
Spacer(modifier = Modifier.height(16.dp))
if (message == null) {
content()
@ -77,16 +77,23 @@ fun CommonDialog(
.clickable { confirm() }
.background(color = Color(0xFF3A58B1), shape = RoundedCornerShape(23.dp))
.padding(vertical = 12.dp), contentAlignment = Alignment.Center) {
Text(text = confirmText, color = Color.White, fontSize = 15.sp, fontWeight = FontWeight.Medium)
Text(text = confirmText,
color = Color.White,
fontSize = 15.sp,
fontWeight = FontWeight.Medium)
}
Spacer(modifier = Modifier.height(20.dp))
if (!cancelText.isNullOrBlank() && cancelEnable) {
if (! cancelText.isNullOrBlank() && cancelEnable) {
Box(modifier = Modifier
.fillMaxWidth()
.clickable { cancel() }, contentAlignment = Alignment.Center) {
Text(text = cancelText, fontSize = 15.sp, fontWeight = FontWeight.Medium, color = black90)
.clickable { cancel() },
contentAlignment = Alignment.Center) {
Text(text = cancelText,
fontSize = 15.sp,
fontWeight = FontWeight.Medium,
color = black90)
}
}
@ -98,19 +105,17 @@ fun CommonDialog(
@Composable
fun ReTakePhotoDialog(
title: String? = null,
confirm: () -> Unit,
againSubmit: () -> Unit,
path: String? = null,
showAgain: Boolean = false,
cancel: () -> Unit = {},
dismiss: () -> Unit
) {
fun ReTakePhotoDialog(title : String? = null,
confirm : () -> Unit,
againSubmit : () -> Unit,
path : String? = null,
showAgain : Boolean = false,
cancel : () -> Unit = {},
dismiss : () -> Unit) {
Dialog(onDismissRequest = { dismiss() },
properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true)) {
Box(modifier = Modifier
.background(color = Color.White, shape = RoundedCornerShape(13.dp))) {
Box(modifier = Modifier.background(color = Color.White,
shape = RoundedCornerShape(13.dp))) {
Spacer(modifier = Modifier
.fillMaxWidth()
.height(105.dp)
@ -123,11 +128,15 @@ fun ReTakePhotoDialog(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
Spacer(modifier = Modifier.height(35.dp))
Text(text = "$title", fontSize = 16.sp, fontWeight = FontWeight.Medium, color = Color(0xFF2A4054))
Text(text = "$title",
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF2A4054))
Spacer(modifier = Modifier.height(16.dp))
Box(modifier = Modifier
.fillMaxWidth()
.height(300.dp), contentAlignment = Alignment.Center) {
.height(300.dp),
contentAlignment = Alignment.Center) {
AsyncImage(model = path,
contentDescription = "",
modifier = Modifier.fillMaxSize(),
@ -140,7 +149,10 @@ fun ReTakePhotoDialog(
.clickable { confirm() }
.background(color = Color(0xFF3A58B1), shape = RoundedCornerShape(23.dp))
.padding(vertical = 12.dp), contentAlignment = Alignment.Center) {
Text(text = "重拍", color = Color.White, fontSize = 15.sp, fontWeight = FontWeight.Medium)
Text(text = "重拍",
color = Color.White,
fontSize = 15.sp,
fontWeight = FontWeight.Medium)
}
if (showAgain) {
@ -151,7 +163,10 @@ fun ReTakePhotoDialog(
.clickable { againSubmit() }
.background(color = Color.Red, shape = RoundedCornerShape(23.dp))
.padding(vertical = 12.dp), contentAlignment = Alignment.Center) {
Text(text = "再次上传", color = Color.White, fontSize = 15.sp, fontWeight = FontWeight.Medium)
Text(text = "再次上传",
color = Color.White,
fontSize = 15.sp,
fontWeight = FontWeight.Medium)
}
}
@ -159,8 +174,12 @@ fun ReTakePhotoDialog(
Box(modifier = Modifier
.fillMaxWidth()
.clickable { cancel() }, contentAlignment = Alignment.Center) {
Text(text = "取消", fontSize = 15.sp, fontWeight = FontWeight.Medium, color = black90)
.clickable { cancel() },
contentAlignment = Alignment.Center) {
Text(text = "取消",
fontSize = 15.sp,
fontWeight = FontWeight.Medium,
color = black90)
}
Spacer(modifier = Modifier.height(10.dp))
@ -171,26 +190,29 @@ fun ReTakePhotoDialog(
@Composable
fun ChoiceMapDialog(dismiss: () -> Unit,
lat: Double?,
lng: Double?,
address: String?) {
fun ChoiceMapDialog(dismiss : () -> Unit, lat : Double?, lng : Double?, address : String?) {
val context = LocalContext.current
Dialog(onDismissRequest = { dismiss() }) {
Row(modifier = Modifier
.fillMaxWidth()
.height(180.dp)
.background(color = Color.White, shape = RoundedCornerShape(8.dp))
.padding(10.dp), verticalAlignment = Alignment.CenterVertically,
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceAround) {
if (MapUtil.isGdMapInstalled(context)) {
Column(modifier = Modifier.clickable {
MapUtil.startNavigationGd(context, lat = lat, lng = lng, address = address)
dismiss()
}) {
AsyncImage(model = R.drawable.sv_amap_icon, contentDescription = "", modifier = Modifier.size(60.dp))
AsyncImage(model = R.drawable.sv_amap_icon,
contentDescription = "",
modifier = Modifier.size(60.dp))
Spacer(modifier = Modifier.height(5.dp))
Text(text = "高德地图", color = Color.Black, fontWeight = FontWeight.Medium, fontSize = 14.sp)
Text(text = "高德地图",
color = Color.Black,
fontWeight = FontWeight.Medium,
fontSize = 14.sp)
}
}
@ -199,9 +221,14 @@ fun ChoiceMapDialog(dismiss: () -> Unit,
MapUtil.startNavigationBd(context, lat = lat, lng = lng, address = address)
dismiss()
}) {
AsyncImage(model = R.drawable.sv_baidu_icon, contentDescription = "", modifier = Modifier.size(60.dp))
AsyncImage(model = R.drawable.sv_baidu_icon,
contentDescription = "",
modifier = Modifier.size(60.dp))
Spacer(modifier = Modifier.height(5.dp))
Text(text = "百度地图", color = Color.Black, fontWeight = FontWeight.Medium, fontSize = 14.sp)
Text(text = "百度地图",
color = Color.Black,
fontWeight = FontWeight.Medium,
fontSize = 14.sp)
}
}
@ -210,9 +237,14 @@ fun ChoiceMapDialog(dismiss: () -> Unit,
MapUtil.startNavigationTencent(context, lat = lat, lng = lng, address = address)
dismiss()
}) {
AsyncImage(model = R.drawable.sv_tencent_icon, contentDescription = "", modifier = Modifier.size(60.dp))
AsyncImage(model = R.drawable.sv_tencent_icon,
contentDescription = "",
modifier = Modifier.size(60.dp))
Spacer(modifier = Modifier.height(5.dp))
Text(text = "腾讯地图", color = Color.Black, fontWeight = FontWeight.Medium, fontSize = 14.sp)
Text(text = "腾讯地图",
color = Color.Black,
fontWeight = FontWeight.Medium,
fontSize = 14.sp)
}
}
}

View File

@ -23,7 +23,6 @@ import com.za.servicing.R
@Composable
fun HeadView(title : String,
onBack : () -> Unit = {},
warnType : WarnType = WarnType.NULL,
isCanBack : Boolean = true,
action : @Composable () -> Unit = {}) {
Column {
@ -43,10 +42,8 @@ fun HeadView(title : String,
},
actions = { action() })
if (warnType != WarnType.NULL) {
AppTipsView()
}
}
}
@OptIn(ExperimentalMaterial3Api::class)

View File

@ -30,9 +30,9 @@ object LoadingManager {
fun LoadingView() {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
AsyncImage(model = ImageRequest.Builder(LocalContext.current)
.data(R.drawable.gif_loading)
.decoderFactory(GifDecoder.Factory())
.build(), contentDescription = "加载中", modifier = Modifier
.data(R.drawable.gif_loading).decoderFactory(GifDecoder.Factory()).build(),
contentDescription = "加载中",
modifier = Modifier
.size(70.dp)
.align(Alignment.Center))
}
@ -41,6 +41,6 @@ object LoadingManager {
open class LoadingState {
data object Loading : LoadingState()
data object LoadingFailed : LoadingState()
data class LoadingFailed(val msg : String? = "加载失败") : LoadingState()
data object LoadingSuccess : LoadingState()
}

View File

@ -25,3 +25,10 @@ data class ReportHistoryBean(val reportType : String? = null,
val reportTemplate : String? = null,
val createTime : Long? = null,
val state : Int? = null)
data class TaskNotesBean(val taskNotes : String? = null, //救援要求
val feeStandard : String? = null, //收费标准
val customerNotes : String? = null, //客户提醒
val otherNotes : String? = null, //特殊提醒
val modelVinNo : String? = null, //车型
val contract : String? = null) //车型

View File

@ -4,23 +4,23 @@ import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "ele_work_order")
data class EleWorkOrderBean(
@PrimaryKey(autoGenerate = false) val orderId: Int,
val userOrderId: Int? = null,
val date: String? = null, //日期 2023-10-20
val serviceContent: String? = null, //服务须知内容
val orderWorkStatus: Int? = null, //电子工单状态 1车辆损伤照片 2 客户签名页面 3 服务完成 接车人签名和服务人员签名状态
val hasBad: Boolean? = null, //车辆是否有损伤
val carNO: String? = null,//车
val carVin: String? = null,//车架号
val orderType: String? = null, //救援类型
val isSuccess: Int? = null, //服务是否成功 1 成功 其他失败
val localCustomSignPath: String? = null,
val localAcceptCarSignPath: String? = null,//接车人本地路径
val localServicePeopleSignPath: String? = null, //服务人员签名
val serverCustomSignPath: String? = null,
val serverAcceptCarSignPath: String? = null, //远程地址
val serverServicePeopleSignPath: String? = null,
val hasCreatedEleWorkOrderPhoto: Boolean? = null, //是否已经生成电子工单照片
val changeBattery: Boolean? = null //是否更换电瓶
)
data class EleWorkOrderBean(@PrimaryKey(autoGenerate = false) val orderId : Int,
val userOrderId : Int? = null,
val date : String? = null, //日期 2023-10-20
val serviceContent : String? = null, //服务须知内容
val orderWorkStatus : Int? = null, //电子工单状态 1车辆损伤照片 2 客户签名页面 3 服务完成 接车人签名和服务人员签名状态
val hasBad : Boolean? = null, //车辆是否有损伤
val carNO : String? = null, //车牌号
val carVin : String? = null, //车
val orderType : String? = null, //救援类型
val isSuccess : Int? = null, //服务是否成功 1 成功 其他失败
val localCustomSignPath : String? = null,
val localAcceptCarSignPath : String? = null, //接车人本地路径
val localServicePeopleSignPath : String? = null, //服务人员签名
val serverCustomSignPath : String? = null,
val serverAcceptCarSignPath : String? = null, //远程地址
val serverServicePeopleSignPath : String? = null,
val hasCreatedEleWorkOrderPhoto : Boolean? = null, //是否已经生成电子工单照片
val changeBattery : Boolean? = null, //是否更换电瓶
var driverChoiceNoNeedReceiveMoney : Boolean? = null) //师傅是否选择了无需收款

View File

@ -7,78 +7,77 @@ import androidx.room.PrimaryKey
@Entity(tableName = "order_info")
data class OrderInfo(
@PrimaryKey(autoGenerate = false) var taskId: Int? = null, //订单号 "taskId":5313005
var userOrderId: Int? = null, //订单id
var taskCode: String? = null, //订单编码 "taskCode":"ZD20190308009965"
var customerName: String? = null, //客户姓名 "customerName":"越继安"
var customerPhone: String? = null,//客户电话 "customerPhone":"18078815268"
var carBrand: String? = null,//车辆品牌 "carBrand":""
var modelVinNo: String? = null,
var carModel: String? = null, //车辆型号 "carModel":"秦"
var carNo: String? = null, //客户车车牌号 "carNo":"粤AF53918"
var carVin: String? = null,//客户车Vin码 "carVin":"LGXC76C30J0132942"
var taskState: String? = null, //订单状态 "taskState":"GOTO"
var nextState: String? = null,//下一步状态
var electronOrderState: Int? = null, //电子工单状态1检查损伤 2生成车辆检查表签字 3作业完成签字
var address: String? = null, //任务地址 "address":"广东省广州市白云区107国道石井凰岗路342号(白云黄石、同德围地区近庆丰兴隆公园)美景大酒店"
var addressProperty: String? = null, //任务地址类型 "addressProperty":"地面"
var addressRemark: String? = null, //事发地地址补充
var distAddress: String? = null, //目的地地址 "distAddress":"广东省广州市白云区雅岗南大道"
var distAddressRemark: String? = null,//目的地地址补充
var expectArriveTime: String? = null, //预计到达时间 "expectArriveTime":"2019-03-08 05:11:07"
var serviceTypeName: String? = null, //服务类型 "serviceTypeName":"故障--平板拖车"
var orderSource: String? = null, // "orderSource":"中道救援-比亚迪道路救援项目"
var flowType: Int? = null, //流程类型 0 综合服务 1小修 2拖车 3取送件 4在线技术解决 5组合服务 6拖车简化流程 7武汉交管服务
var settleType: Int? = null,//结算类型 1 月结 2 现金
var settleTypeStr: String? = null, //结算类型
var supplierId: Int? = null,
var startTime: String? = null,//发车时间
var operationTime: String? = null, //作业完成时间
var dispatchTime: String? = null,//派单时间 "dispatchTime":"2019-03-08 04:26:07"
var verifyType: Int? = null,//验证类型 "verifyType": 0 跳过验证 1和2 车牌和车架号验证 5 新车验证
var verifyValue: String? = null,//验证内容 "verifyValue":"粤AF53918"
var holdon: Boolean? = null,//是否被挂起 "holdon":false
var isCurrent: Boolean? = null, //是否为正在做的任务
var flow: String? = null, //流程状态 "flow":"START,GOTO,VERIFY,EXAMINE,OPERATION,SENDTO,SETTLEMENT,FINISH"
var externalCode: String? = null, //
var plateNumber: String? = null,
var distaddressProperty: String? = null,
var vehiclePointRemark: String? = null,
var destinationRemark: String? = null,
var lat: Double? = null,
var lng: Double? = null,
var distLat: Double? = null,
var distLng: Double? = null,
var hotline: String? = null,
var createTime: String? = null,
var acceptTime: String? = null,
var arriveTime: String? = null,
var arriveDestTime: String? = null,
var needECDevice: Boolean? = null,
var needDestAddress: Boolean? = null,
var linkToDocs: Boolean? = null,
var linkToDaDianH5: Boolean? = null,
var carFactory: Boolean? = null,
var needWaterMarker: Boolean? = null,
var needShowPhoneBrand: Boolean? = null,
var taskNotes: String? = null, //救援要求
var feeStandard: String? = null, //收费标准
var customerNotes: String? = null, //客户提醒
var otherNotes: String? = null, //特殊提醒
var isNewCar: Boolean? = null, //是否新车
var ECDeviceString: String? = null,//搭电设备照片提示
var customerReportImgs: String? = null, //客户上报照片,多张照片用;分隔
var arriveRemind: String? = null,
var arriveRemindLink: String? = null,
var policyNo: String? = null, // 平安拖车责任险 保单号
var advanceTime: Long? = null,
var importantTip: String? = null,
var hasReplaceBatteryCapable: Int? = null, //是否有更换电瓶的能力 1 搭电可以更换电瓶 2搭电不可以更换电瓶 其他的不展示
var taskSuccessStatus: Int? = null, //作业是否完成 0 完成 1未完成 拖车默认完成
var tyreNumber: Int? = null, //辅助费用
@PrimaryKey(autoGenerate = false) var taskId : Int? = null, //订单号 "taskId":5313005
var userOrderId : Int? = null, //订单id
var taskCode : String? = null, //订单编码 "taskCode":"ZD20190308009965"
var customerName : String? = null, //客户姓名 "customerName":"越继安"
var customerPhone : String? = null, //客户电话 "customerPhone":"18078815268"
var carBrand : String? = null, //车辆品牌 "carBrand":""
var modelVinNo : String? = null,
var carModel : String? = null, //车辆型号 "carModel":"秦"
var carNo : String? = null, //客户车车牌号 "carNo":"粤AF53918"
var carVin : String? = null, //客户车Vin码 "carVin":"LGXC76C30J0132942"
var taskState : String? = null, //订单状态 "taskState":"GOTO"
var nextState : String? = null, //下一步状态
var electronOrderState : Int? = null, //电子工单状态1检查损伤 2生成车辆检查表签字 3作业完成签字
var address : String? = null, //任务地址 "address":"广东省广州市白云区107国道石井凰岗路342号(白云黄石、同德围地区近庆丰兴隆公园)美景大酒店"
var addressProperty : String? = null, //任务地址类型 "addressProperty":"地面"
var addressRemark : String? = null, //事发地地址补充
var distAddress : String? = null, //目的地地址 "distAddress":"广东省广州市白云区雅岗南大道"
var distAddressRemark : String? = null, //目的地地址补充
var expectArriveTime : String? = null, //预计到达时间 "expectArriveTime":"2019-03-08 05:11:07"
var serviceTypeName : String? = null, //服务类型 "serviceTypeName":"故障--平板拖车"
var orderSource : String? = null, // "orderSource":"中道救援-比亚迪道路救援项目"
var flowType : Int? = null, //流程类型 0 综合服务 1小修 2拖车 3取送件 4在线技术解决 5组合服务 6拖车简化流程 7武汉交管服务
var settleType : Int? = null, //结算类型 1 月结 2 现金
var settleTypeStr : String? = null, //结算类型
var supplierId : Int? = null,
var startTime : String? = null, //发车时间
var operationTime : String? = null, //作业完成时间
var dispatchTime : String? = null, //派单时间 "dispatchTime":"2019-03-08 04:26:07"
var verifyType : Int? = null, //验证类型 "verifyType": 0 跳过验证 1和2 车牌和车架号验证 5 新车验证
var verifyValue : String? = null, //验证内容 "verifyValue":"粤AF53918"
var holdon : Boolean? = null, //是否被挂起 "holdon":false
var isCurrent : Boolean? = null, //是否为正在做的任务
var flow : String? = null, //流程状态 "flow":"START,GOTO,VERIFY,EXAMINE,OPERATION,SENDTO,SETTLEMENT,FINISH"
var externalCode : String? = null, //
var plateNumber : String? = null,
var distaddressProperty : String? = null,
var vehiclePointRemark : String? = null,
var destinationRemark : String? = null,
var lat : Double? = null,
var lng : Double? = null,
var distLat : Double? = null,
var distLng : Double? = null,
var hotline : String? = null,
var createTime : String? = null,
var acceptTime : String? = null,
var arriveTime : String? = null,
var arriveDestTime : String? = null,
var needECDevice : Boolean? = null,
var needDestAddress : Boolean? = null,
var linkToDocs : Boolean? = null,
var linkToDaDianH5 : Boolean? = null,
var carFactory : Boolean? = null,
var needWaterMarker : Boolean? = null,
var needShowPhoneBrand : Boolean? = null,
var taskNotes : String? = null, //救援要求
var feeStandard : String? = null, //收费标准
var customerNotes : String? = null, //客户提醒
var otherNotes : String? = null, //特殊提醒
var isNewCar : Boolean? = null, //是否新车
var ECDeviceString : String? = null, //搭电设备照片提示
var customerReportImgs : String? = null, //客户上报照片,多张照片用;分隔
var arriveRemind : String? = null,
var arriveRemindLink : String? = null,
var policyNo : String? = null, // 平安拖车责任险 保单号
var advanceTime : Long? = null,
var importantTip : String? = null,
var hasReplaceBatteryCapable : Int? = null, //是否有更换电瓶的能力 1 搭电可以更换电瓶 2搭电不可以更换电瓶 其他的不展示
var taskSuccessStatus : Int? = null, //作业是否完成 0 完成 1未完成 拖车默认完成
var tyreNumber : Int? = null, //辅助费用
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readValue(Int::class.java.classLoader) as? Int,
constructor(parcel : Parcel) : this(parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readString(),
parcel.readString(),
@ -149,7 +148,7 @@ data class OrderInfo(
parcel.readValue(Int::class.java.classLoader) as? Int) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
override fun writeToParcel(parcel : Parcel, flags : Int) {
parcel.writeValue(taskId)
parcel.writeValue(userOrderId)
parcel.writeString(taskCode)
@ -221,16 +220,16 @@ data class OrderInfo(
parcel.writeValue(tyreNumber)
}
override fun describeContents(): Int {
override fun describeContents() : Int {
return 0
}
companion object CREATOR : Parcelable.Creator<OrderInfo> {
override fun createFromParcel(parcel: Parcel): OrderInfo {
override fun createFromParcel(parcel : Parcel) : OrderInfo {
return OrderInfo(parcel)
}
override fun newArray(size: Int): Array<OrderInfo?> {
override fun newArray(size : Int) : Array<OrderInfo?> {
return arrayOfNulls(size)
}
}

View File

@ -1,56 +1,55 @@
package com.za.bean.request
data class OrderListRequest(
val vehicleId: Int? = null,
val deviceId: String? = null,
val vehicleId : Int? = null,
val deviceId : String? = null,
)
/**
* 照片模版请求数据
*/
data class PhotoTemplateRequest(val userOrderId: Int? = null)
data class PhotoTemplateRequest(val userOrderId : Int? = null)
//照片识别
data class OrderPhotoOcrRecognizeRequest(
val userOrderId: Int? = null,
val recognizeType: Int? = null,
val imgUrl: String? = null,
val userOrderId : Int? = null,
val recognizeType : Int? = null,
val imgUrl : String? = null,
)
data class TaskFinishRequest(
val userOrderId: Int? = null,
val lat: Double? = null,
val lng: Double? = null,
val operateTime: Long? = null)
data class TaskFinishRequest(val userOrderId : Int? = null,
val lat : Double? = null,
val lng : Double? = null,
val operateTime : Long? = null)
data class GiveUpTaskRequest(
val taskId: Int? = null, //任务id
val userId: Int? = null, //用户id
val vehicleId: Int? = null, //车辆id
val lat: Double? = null,
val lng: Double? = null,
val address: String? = null,
val templatePhotoInfoList: List<String?>? = null, //照片数据
val pushGiveUpFlag: Int? = null, //1 是 0否
val taskId : Int? = null, //任务id
val userId : Int? = null, //用户id
val vehicleId : Int? = null, //车辆id
val lat : Double? = null,
val lng : Double? = null,
val address : String? = null,
val templatePhotoInfoList : List<String?>? = null, //照片数据
val pushGiveUpFlag : Int? = null, //1 是 0否
)
data class UpdatePhotoRequest(
val taskId: Int? = null,
val taskState: String? = null,
val picturePosition: Int? = null, //图片位置
val picturePath: String? = null,//图片路径
val imgInfo: String? = null
)
data class UpdatePhotoRequest(val taskId : Int? = null,
val taskState : String? = null,
val picturePosition : Int? = null, //图片位置
val picturePath : String? = null, //图片路径
val imgInfo : String? = null)
data class SwitchTaskRequest(val currentTaskId: Int? = null,
val nextTaskId: Int? = null,
val userId: Int? = null,
val vehicleId: Int? = null)
data class SwitchTaskRequest(val currentTaskId : Int? = null,
val nextTaskId : Int? = null,
val userId : Int? = null,
val vehicleId : Int? = null)
data class HistoryTasksRequest(val userId: Int? = null, val vehicleId: Int? = null)
data class HistoryTasksRequest(val userId : Int? = null, val vehicleId : Int? = null)
data class HistoryPhotoTemplateRequest(val taskId: Int? = null)
data class HistoryPhotoTemplateRequest(val taskId : Int? = null)
data class HistoryDetailRequest(val taskId: Int? = null)
data class HistoryDetailRequest(val taskId : Int? = null)
data class TaskNotesRequest(val taskId : Int? = null)

View File

@ -4,6 +4,8 @@ import android.app.Application
import com.amap.api.location.AMapLocation
import com.blankj.utilcode.util.AppUtils
import com.tencent.mmkv.MMKV
import com.za.base.AppConfig
import com.za.base.Const
import com.za.bean.db.order.OrderInfo
import com.za.common.log.LogUtil
import com.za.room.RoomHelper
@ -112,6 +114,18 @@ object GlobalData : GlobalLocalData() {
field = value
}
var networkEnv : Int
get() {
return if (AppConfig.isRelease) {
mmkv.decodeInt("isReviewEnv", Const.NetEnv.Main)
} else {
mmkv.decodeInt("isReviewEnv", Const.NetEnv.CRM1)
}
}
set(value) {
mmkv.encode("isReviewEnv", value)
}
fun clearUserCache() {
token = null
aesKey = null
@ -119,6 +133,16 @@ object GlobalData : GlobalLocalData() {
driverInfoBean = null
loginTime = null
isLoginRecognition = null
if (AppConfig.isRelease) {
networkEnv = if (AppConfig.isRelease) {
Const.NetEnv.Main
} else {
Const.NetEnv.CRM1
}
}
}
fun clearAllOrderCache() {

View File

@ -18,13 +18,9 @@ object ZDManager {
}
private fun thirdSdkInit(isRelease : Boolean = false) {
if (isRelease) {
AppConfig.release()
} else {
AppConfig.crm1()
}
GlobalData.application = application // 在 Application 中初始化 MMKV所有进程共享同一存储路径
MMKV.initialize(application)
AppConfig.init(isRelease)
Bugly.init(application, "6972a6b56d", true)
LogUtil.init(application)
RoomHelper.init(application)

View File

@ -0,0 +1,5 @@
package com.za.common.util
class PermissionUtil {
}

View File

@ -23,6 +23,7 @@ import com.za.bean.ReportHistoryRequest
import com.za.bean.ReportInfoRequest
import com.za.bean.ReportItem
import com.za.bean.SettleInfoRequest
import com.za.bean.TaskNotesBean
import com.za.bean.TaskSettlementAndTraceBean
import com.za.bean.UpdateVersionBean
import com.za.bean.UpdateVersionRequest
@ -61,6 +62,7 @@ import com.za.bean.request.SaveEleOrderRequest
import com.za.bean.request.SwitchTaskRequest
import com.za.bean.request.TaskFinishRequest
import com.za.bean.request.TaskFinishResponse
import com.za.bean.request.TaskNotesRequest
import com.za.bean.request.TodayMaintainRequest
import com.za.bean.request.TodayMaintainUploadRequest
import com.za.bean.request.TodayMaintainbean
@ -284,4 +286,7 @@ interface ApiService {
@POST("driverApp/supplier/iaiCompareFace")
fun iaiCompareFace(@Body info : DriverFaceCompareRequest) : Observable<BaseResponse<IaiCompareFaceBean>>
@POST("driverApp/task/getTaskNotes")
fun getTaskNotes(@Body request : TaskNotesRequest) : Observable<BaseResponse<TaskNotesBean>>
}

View File

@ -6,6 +6,8 @@ import com.blankj.utilcode.util.ThreadUtils
import com.blankj.utilcode.util.ToastUtils
import com.google.gson.JsonParseException
import com.za.base.Const
import com.za.base.view.NetWarnBean
import com.za.base.view.warnBean
import com.za.bean.BaseResponse
import com.za.common.GlobalData
import com.za.common.log.LogUtil
@ -32,6 +34,11 @@ abstract class BaseObserver<T> : Observer<BaseResponse<T>> {
override fun onNext(tBaseResponse : BaseResponse<T>) {
if (tBaseResponse.isOk) {
doSuccess(tBaseResponse.result)
ThreadUtils.runOnUiThread {
if (warnBean.value != null && warnBean.value is NetWarnBean) {
warnBean.value = null
}
}
} else {
when (tBaseResponse.code) {
3, 401 -> handlerTokenExpired()
@ -67,15 +74,18 @@ abstract class BaseObserver<T> : Observer<BaseResponse<T>> {
is ConnectException -> {
doFailure(Const.NetWorkException, "与服务器断开连接")
handlerNetError()
}
is UnknownHostException -> {
doFailure(Const.NetWorkException, "与服务器断开连接")
handlerNetError()
}
is SSLHandshakeException -> {
doFailure(1, "证书验证失败")
LogUtil.print("SSLHandshakeException", e)
handlerNetError()
}
is TimeoutException -> {
@ -86,6 +96,7 @@ abstract class BaseObserver<T> : Observer<BaseResponse<T>> {
is SocketTimeoutException -> {
doFailure(Const.NetWorkException, "网络连接超时2")
LogUtil.print("SocketTimeoutException2", e)
handlerNetError()
}
else -> {
@ -102,6 +113,15 @@ abstract class BaseObserver<T> : Observer<BaseResponse<T>> {
abstract fun doFailure(code : Int, msg : String?)
private fun handlerNetError() {
ThreadUtils.runOnUiThread {
if (warnBean.value == null) {
warnBean.value = NetWarnBean("当前网络异常")
}
}
}
private fun handlerTokenExpired() {
ThreadUtils.runOnUiThread {
try {

View File

@ -1,6 +1,5 @@
package com.za.net
import android.util.Log
import com.za.base.AppConfig
import com.za.common.log.LogUtil
import okhttp3.OkHttpClient
@ -38,6 +37,11 @@ object RetrofitHelper {
}.setLevel(HttpLoggingInterceptor.Level.BODY)
fun reset() {
apiService = null
retrofit = null
}
fun getDefaultService() : ApiService {
return if (apiService == null) {
apiService = getDefaultRetrofit().create(ApiService::class.java)

View File

@ -11,7 +11,7 @@ import com.za.room.db.GlobalRoom
@SuppressLint("StaticFieldLeak")
object RoomHelper {
const val VERSION: Int = 40
const val VERSION: Int = 41
private lateinit var mContext: Context
var db: GlobalRoom? = null

View File

@ -3,6 +3,7 @@ package com.za.service.mqtt
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.service.ServiceManager
import kotlinx.coroutines.CoroutineScope
@ -59,7 +60,7 @@ object MyMqttClient {
Handler(Looper.getMainLooper()).post {
val message = String(mqttMessage.payload)
LogUtil.print("MyMqttClient ", "Message arrived: $message")
ServiceManager.handlerPushMsg(message) // Pass the message to ServiceManager for processing
ServiceManager.handlerPushMsg(message)
}
}
@ -96,10 +97,11 @@ object MyMqttClient {
//检测mqtt连接状态
fun publishMessage() {
if (mqttClient.isConnected) {
LogUtil.print("MyMqttClient ", "publishMessage success")
LogUtil.print("MyMqttClient ", "mqttClient.hasConnected")
return
}
connect()
LogUtil.print("MyMqttClient ", "mqttClient 断开重新初始化")
ServiceManager.initialize(GlobalData.application)
}
private fun subscribeTopic() {

View File

@ -7,7 +7,6 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.BaseBundle;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

View File

@ -84,7 +84,6 @@ public class HandWriteEditView extends AppCompatEditText {
/**
* 设置行高
*
*/
public void setLineHeight(float lineHeight) {
this.lineHeight = lineHeight;
@ -102,7 +101,7 @@ public class HandWriteEditView extends AppCompatEditText {
return null;
}
SpannableString mSpan = new SpannableString("1");
mSpan.setSpan(new ImageSpan(getContext(), srcBitmap), mSpan.length() - 1, mSpan.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpan.setSpan(new ImageSpan(getContext(), srcBitmap, ImageSpan.ALIGN_BASELINE), mSpan.length() - 1, mSpan.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Editable editable = getText();
//获取光标所在位置

View File

@ -0,0 +1,461 @@
package com.za.ui.camera
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat
import android.graphics.Matrix
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.OptIn
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.blankj.utilcode.util.ImageUtils
import com.blankj.utilcode.util.ToastUtils
import com.bumptech.glide.Glide
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.face.Face
import com.google.mlkit.vision.face.FaceDetection
import com.google.mlkit.vision.face.FaceDetectorOptions
import com.za.bean.request.DriverFaceCompareBean
import com.za.bean.request.DriverFaceCompareRequest
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.common.speech.SpeechManager
import com.za.net.BaseObserver
import com.za.net.CommonMethod
import com.za.net.RetrofitHelper
import com.za.servicing.R
import com.za.signature.view.CircleImageView
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import java.io.File
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.math.abs
class ServicePeopleRealActivity : AppCompatActivity() {
private lateinit var lifecycleCameraController : LifecycleCameraController
private lateinit var cameraExecutor : ExecutorService
private var avatarBitmap : Bitmap? = null
private var isActivityActive = true
private var currentStep = 0
private val steps = listOf("请保持面部居中", "请向左转头", "请向右转头", "请保持面部居中")
private var isStepCompleted = false
private lateinit var viewFinder : PreviewView
private lateinit var stepText : TextView
private lateinit var confirmationLayout : FrameLayout
private lateinit var previewImage : CircleImageView
private lateinit var retryButton : Button
private lateinit var confirmButton : Button
private val faceDetector = FaceDetection.getClient(FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL).build())
private var loadingDialog : AlertDialog? = null
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_service_people_real) // 初始化控制器
lifecycleCameraController = LifecycleCameraController(this).apply {
cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
setEnabledUseCases(LifecycleCameraController.IMAGE_CAPTURE or LifecycleCameraController.IMAGE_ANALYSIS)
}
initializeViews()
setupClickListeners()
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
cameraExecutor = Executors.newSingleThreadExecutor()
updateStepUI()
}
private fun initializeViews() {
viewFinder = findViewById(R.id.viewFinder)
viewFinder.controller = lifecycleCameraController // 将控制器与生命周期绑定
lifecycleCameraController.bindToLifecycle(this)
stepText = findViewById(R.id.stepText)
confirmationLayout = findViewById(R.id.confirmationLayout)
previewImage = findViewById(R.id.previewImage)
retryButton = findViewById(R.id.retryButton)
confirmButton = findViewById(R.id.confirmButton)
}
private fun setupClickListeners() {
retryButton.setOnClickListener {
resetDetection()
}
confirmButton.setOnClickListener {
returnPhotoResult()
}
}
private fun captureImage() {
lifecycleCameraController.takePicture(ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageCapturedCallback() {
@OptIn(ExperimentalGetImage::class)
override fun onCaptureSuccess(image : ImageProxy) {
val mediaImage = image.image
LogUtil.print("拍照成功", image.imageInfo.toString())
if (mediaImage != null) {
val inputImage =
InputImage.fromMediaImage(mediaImage, image.imageInfo.rotationDegrees)
faceDetector.process(inputImage).addOnSuccessListener { faces ->
when {
faces.isEmpty() -> {
runOnUiThread {
Toast.makeText(this@ServicePeopleRealActivity,
"未检测到人脸",
Toast.LENGTH_SHORT).show()
}
}
faces.size > 1 -> {
runOnUiThread {
Toast.makeText(this@ServicePeopleRealActivity,
"检测到多个人脸,请确保画面中只有一个人脸",
Toast.LENGTH_SHORT).show()
}
}
else -> {
LogUtil.print("人脸检测成功", faces[0].toString())
avatarBitmap = handlerBitmap(image.toBitmap(), image.imageInfo.rotationDegrees)
LogUtil.print("人脸认证通过", faces[0].toString())
runOnUiThread {
showConfirmation()
}
}
}
}.addOnFailureListener { e ->
LogUtil.print("人脸检测失败", e)
if (isActivityActive) {
Toast.makeText(this@ServicePeopleRealActivity,
"人脸检测失败",
Toast.LENGTH_SHORT).show()
}
}.addOnCompleteListener {
image.close()
}
} else {
image.close()
}
}
override fun onError(exc : ImageCaptureException) {
LogUtil.print("拍照失败", exc)
if (isActivityActive) {
Toast.makeText(this@ServicePeopleRealActivity,
"拍照失败",
Toast.LENGTH_SHORT).show()
}
}
})
}
private fun isValidFace(face : Face, imageWidth : Int, imageHeight : Int) : Boolean {
val faceBounds = face.boundingBox
val faceCenterX = faceBounds.centerX()
val faceCenterY = faceBounds.centerY()
val imageCenterX = imageWidth / 2
val imageCenterY = imageHeight / 2
// 计算人脸框占图片的比例
val faceWidthRatio = faceBounds.width().toFloat() / imageWidth
val faceHeightRatio = faceBounds.height().toFloat() / imageHeight
// 检查人脸是否居中允许30%的偏移)
val centerThreshold = imageWidth * 0.8f
val isCentered =
abs(faceCenterX - imageCenterX) < centerThreshold && abs(faceCenterY - imageCenterY) < centerThreshold
// 检查人脸大小是否合适建议占据图片30%-70%的区域)
val isProperSize = faceWidthRatio in 0.6f .. 0.7f && faceHeightRatio in 0.6f .. 0.7f
// 检查是否是正面人脸且睁眼
val isFrontalFace = abs(face.headEulerAngleY) < 15 && // 左右角度
abs(face.headEulerAngleX) < 15 && // 上下角度
abs(face.headEulerAngleZ) < 15 && // 倾斜角度
face.rightEyeOpenProbability != null && face.leftEyeOpenProbability != null && face.rightEyeOpenProbability !! > 0.8 && face.leftEyeOpenProbability !! > 0.8
return isCentered && isProperSize && isFrontalFace
}
//校正图片,解决图片可能翻转的问题
private val rotationMatrix = Matrix()
private fun handlerBitmap(bitmap : Bitmap, degrees : Int?) : Bitmap {
if (degrees == null || degrees == 0) return bitmap
rotationMatrix.reset()
rotationMatrix.postRotate(degrees.toFloat())
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, rotationMatrix, true)
}
private fun resetDetection() {
currentStep = 0
isStepCompleted = false
avatarBitmap = null
confirmationLayout.visibility = View.GONE
stepText.visibility = View.VISIBLE
updateStepUI()
}
private fun startCamera() {
lifecycleCameraController.setImageAnalysisAnalyzer(ContextCompat.getMainExecutor(this@ServicePeopleRealActivity),
FaceAnalyzer { handleFaceDetection(it) })
}
private fun handleFaceDetection(eulerY : Float) {
if (! isActivityActive) return
when (currentStep) {
0 -> {
if (abs(eulerY) < 15) {
if (! isStepCompleted) {
isStepCompleted = true
proceedToNextStep()
}
}
}
1 -> {
if (eulerY > 15) {
if (! isStepCompleted) {
isStepCompleted = true
proceedToNextStep()
}
}
}
2 -> {
if (eulerY < - 15) {
if (! isStepCompleted) {
isStepCompleted = true
proceedToNextStep()
}
}
}
3 -> {
if (abs(eulerY) < 15) {
if (! isStepCompleted) {
isStepCompleted = true
runOnUiThread {
captureImage()
}
}
}
}
}
}
private fun showConfirmation() {
avatarBitmap?.let { bitmap ->
Glide.with(previewImage).load(bitmap).into(previewImage)
confirmationLayout.visibility = View.VISIBLE
stepText.visibility = View.GONE
} ?: run {
AlertDialog.Builder(this).setTitle("提示")
.setMessage("未检测到有效的人脸照片,是否重新开始检测?")
.setPositiveButton("重新检测") { dialog, _ ->
dialog.dismiss()
resetDetectionProcess()
}.setNegativeButton("取消") { dialog, _ ->
dialog.dismiss()
finish()
}.setCancelable(false).show()
}
}
private fun resetDetectionProcess() {
currentStep = 0
isStepCompleted = false
avatarBitmap = null
confirmationLayout.visibility = View.GONE
stepText.visibility = View.VISIBLE
stepText.text = steps[currentStep]
startCamera()
}
private fun proceedToNextStep() {
currentStep ++
isStepCompleted = false
if (currentStep < steps.size) {
updateStepUI()
}
}
private fun updateStepUI() {
stepText.visibility = View.VISIBLE
stepText.text = steps[currentStep]
SpeechManager.releaseMediaPlayer()
when (currentStep) {
0 -> SpeechManager.playFaceCenter()
1 -> SpeechManager.playFaceLeft()
2 -> SpeechManager.playFaceRight()
3 -> SpeechManager.playFaceCenter()
}
}
private fun returnPhotoResult() {
avatarBitmap?.let { bitmap -> // 保存图片到临时文件
val file = ImageUtils.save2Album(bitmap, CompressFormat.JPEG, 100)
if (file?.exists() == true) { // 创建返回结果
val resultIntent = Intent()
resultIntent.putExtra("path", file.absolutePath)
setResult(RESULT_OK, resultIntent)
finish()
} else {
Toast.makeText(this, "照片保存失败,请重试", Toast.LENGTH_SHORT).show()
}
} ?: run {
Toast.makeText(this, "未获取到有效的人脸照片,请重试", Toast.LENGTH_SHORT).show()
}
}
private fun dismissLoadingDialog() {
loadingDialog?.dismiss()
}
private fun showUploadFailedDialog() {
AlertDialog.Builder(this).setTitle("上传失败").setMessage("人脸照片上传失败,请重新识别")
.setPositiveButton("重新识别") { dialog, _ ->
dialog.dismiss()
resetDetection()
}.setNegativeButton("取消") { dialog, _ ->
dialog.dismiss()
finish()
}.setCancelable(false).show()
}
@SuppressLint("CheckResult")
private fun doUploadImg(file : File) {
CommonMethod.uploadImage(file, success = {
if (it.isNullOrBlank()) {
ToastUtils.showLong("上传失败")
return@uploadImage
}
doUpload(it)
}, failed = {
dismissLoadingDialog()
showUploadFailedDialog()
})
}
private fun doUpload(url : String) {
val request = DriverFaceCompareRequest(vehicleId = GlobalData.driverInfoBean?.vehicleId,
driverId = GlobalData.driverInfoBean?.userId,
photoUrl = url)
RetrofitHelper.getDefaultService().driverFaceCompare(request).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<DriverFaceCompareBean>() {
override fun doSuccess(it : DriverFaceCompareBean?) {
ToastUtils.showLong("头像上传成功!")
finish()
}
override fun doFailure(code : Int, msg : String?) {
LogUtil.print("doUpload addRealAvatarActivity failed",
"code==$msg request==$request")
showUploadFailedDialog()
}
})
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}
override fun onRequestPermissionsResult(requestCode : Int,
permissions : Array<String>,
grantResults : IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this, "需要相机权限", Toast.LENGTH_SHORT).show()
finish()
}
}
}
override fun onResume() {
super.onResume()
isActivityActive = true
}
override fun onPause() {
super.onPause()
isActivityActive = false
}
override fun onDestroy() {
super.onDestroy()
isActivityActive = false
cameraExecutor.shutdown()
}
private class FaceAnalyzer(private val listener : (Float) -> Unit) : ImageAnalysis.Analyzer {
private val detector = FaceDetection.getClient(FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_NONE)
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_NONE).build())
@ExperimentalGetImage
override fun analyze(imageProxy : ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage != null) {
val image =
InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
detector.process(image).addOnSuccessListener { faces ->
if (faces.isNotEmpty()) {
listener(faces[0].headEulerAngleY)
}
}.addOnCompleteListener {
imageProxy.close()
}
} else {
imageProxy.close()
}
}
}
companion object {
private const val REQUEST_CODE_PERMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}
}

View File

@ -7,12 +7,10 @@ import android.view.ViewGroup
import android.webkit.JavascriptInterface
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Box
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.statusBarsPadding
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
@ -33,7 +31,6 @@ import com.tencent.smtt.sdk.WebView.setWebContentsDebuggingEnabled
import com.tencent.smtt.sdk.WebViewClient
import com.za.base.AppConfig
import com.za.base.BaseActivity
import com.za.base.theme.headPadding
import com.za.base.view.HeadView
import com.za.common.GlobalData
import com.za.common.log.LogUtil
@ -168,11 +165,6 @@ private fun CommonH5Screen(url : String,
Scaffold(topBar = {
if (title.isNotBlank()) {
HeadView(title = title, onBack = { handleBackPress(context, webView) })
} else {
Spacer(modifier = Modifier
.fillMaxWidth()
.statusBarsPadding()
.padding(vertical = headPadding))
}
}) { paddingValues ->
Box(modifier = Modifier

View File

@ -42,7 +42,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@ -76,6 +75,7 @@ import com.za.ext.callPhone
import com.za.ext.copy
import com.za.ext.finish
import com.za.servicing.R
import com.za.ui.servicing.in_servicing_setting.OrderTaskNotesDialog
class NewOrderActivity : BaseActivity() {
@ -198,6 +198,28 @@ private fun AcceptOrderScreen(jpushBean : JpushBean?, vm : NewOrderVm = viewMode
})
}
if (uiState.value.acceptOrderDialog == true) {
CommonDialog(title = "请确认是否接受该任务!",
confirmText = "确认接单",
cancelText = "再想想",
cancelEnable = false,
confirm = {
vm.updateState(uiState.value.copy(acceptOrderDialog = false))
vm.dispatch(NewOrderVm.Action.ShowTaskNotes)
},
cancel = {},
dismiss = {})
}
if (uiState.value.showTaskNotesDialog == true) {
OrderTaskNotesDialog(uiState.value.taskNotesBean,
uiState.value.jpushBean?.hasReplaceBatteryCapable == 2,
dismiss = {},
confirm = {
vm.dispatch(NewOrderVm.Action.AcceptOrder)
})
}
BottomSheetScaffold(scaffoldState = scaffoldState,
topBar = { HeadViewNotBack(title = "新订单") },
sheetContent = {
@ -377,7 +399,7 @@ private fun AcceptOrderScreen(jpushBean : JpushBean?, vm : NewOrderVm = viewMode
}
// 接单按钮
Button(onClick = { vm.dispatch(NewOrderVm.Action.AcceptOrder) },
Button(onClick = { vm.dispatch(NewOrderVm.Action.ShowTaskNotes) },
modifier = Modifier
.weight(1f)
.height(44.dp),

View File

@ -15,8 +15,10 @@ import com.blankj.utilcode.util.ToastUtils
import com.za.base.BaseVm
import com.za.base.view.LoadingManager
import com.za.bean.JpushBean
import com.za.bean.TaskNotesBean
import com.za.bean.request.AcceptOrderRequest
import com.za.bean.request.RefuseOrderRequest
import com.za.bean.request.TaskNotesRequest
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.common.util.DeviceUtil
@ -40,54 +42,76 @@ class NewOrderVm : BaseVm<NewOrderVm.Action, NewOrderVm.UiState>() {
private val _uiState = MutableStateFlow(UiState())
val uiState get() = _uiState
private var timerJob: Job? = null
private var timerJob : Job? = null
override fun dispatch(action: Action) {
override fun dispatch(action : Action) {
when (action) {
is Action.Init -> init(action.jpushBean)
is Action.AcceptOrder -> acceptOrder()
is Action.RefuseOrder -> refuseOrder()
is Action.StartTimer -> startTimer()
is Action.ShowTaskNotes -> showTaskNotes()
is Action.UpdateTimer -> updateTimer(action.remainingTime)
else -> {}
}
}
override fun updateState(uiState: UiState) {
override fun updateState(uiState : UiState) {
_uiState.value = uiState
}
private fun init(jpushBean: JpushBean?) {
private fun init(jpushBean : JpushBean?) {
updateState(uiState.value.copy(jpushBean = jpushBean))
buildMarkers(jpushBean)
searchDrivingRoute(jpushBean)
startTimer()
getTaskNote()
}
private fun buildMarkers(jpushBean: JpushBean?) {
private fun getTaskNote() {
val taskNotesRequest = TaskNotesRequest(taskId = uiState.value.jpushBean?.taskId)
RetrofitHelper.getDefaultService().getTaskNotes(taskNotesRequest)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<TaskNotesBean>() {
override fun doSuccess(it : TaskNotesBean?) {
updateState(uiState.value.copy(taskNotesBean = it))
}
override fun doFailure(code : Int, msg : String?) {
LogUtil.print("获取任务备注失败", "request=$taskNotesRequest msg=$msg")
}
})
}
private fun buildMarkers(jpushBean : JpushBean?) {
val markers = arrayListOf<MarkerOptions>()
if (jpushBean?.lat != null && jpushBean.lat != 0.0 && jpushBean.lng != null && jpushBean.lng != 0.0) {
val startMarkers = MarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(R.mipmap.sv_rescuing_map))
.position(LatLng(jpushBean.lat, jpushBean.lng))
.title(jpushBean.address)
.snippet("救援地点")
.visible(true)
val startMarkers =
MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.mipmap.sv_rescuing_map))
.position(LatLng(jpushBean.lat, jpushBean.lng)).title(jpushBean.address)
.snippet("救援地点").visible(true)
markers.add(startMarkers)
}
if (jpushBean?.distLat != null && jpushBean.distLat != 0.0 && jpushBean.distLng != null && jpushBean.distLng != 0.0) {
val startMarkers = MarkerOptions()
.icon(ImageUtil.vectorToBitmap(ActivityUtils.getTopActivity(), R.drawable.sv_map_dist))
.position(LatLng(jpushBean.distLat, jpushBean.distLng))
.title(jpushBean.distAddress)
.snippet("目的地")
.visible(true)
val startMarkers =
MarkerOptions().icon(ImageUtil.vectorToBitmap(ActivityUtils.getTopActivity(),
R.drawable.sv_map_dist)).position(LatLng(jpushBean.distLat, jpushBean.distLng))
.title(jpushBean.distAddress).snippet("目的地").visible(true)
markers.add(startMarkers)
}
updateState(uiState.value.copy(markers = markers))
}
private fun showTaskNotes() {
if (uiState.value.taskNotesBean != null && isNeedShowNotes()) {
updateState(uiState.value.copy(showTaskNotesDialog = true))
return
}
acceptOrder()
}
private fun acceptOrder() {
LoadingManager.showLoading()
ZdLocationManager.getSingleLocation(success = {
@ -101,16 +125,15 @@ class NewOrderVm : BaseVm<NewOrderVm.Action, NewOrderVm.UiState>() {
acceptOrderRequest.lat = it.latitude
acceptOrderRequest.lng = it.longitude
RetrofitHelper.getDefaultService().acceptOrder(acceptOrderRequest)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<String>() {
override fun doSuccess(it: String?) {
override fun doSuccess(it : String?) {
LoadingManager.hideLoading()
LogUtil.print("接单成功", "request=$acceptOrderRequest")
updateState(uiState.value.copy(showCallPhoneDialog = orderInfo?.isNeedCallCustomPhone()))
}
override fun doFailure(code: Int, msg: String?) {
override fun doFailure(code : Int, msg : String?) {
LoadingManager.hideLoading()
LogUtil.print("接单失败", "request=$acceptOrderRequest msg=$msg")
}
@ -123,20 +146,19 @@ class NewOrderVm : BaseVm<NewOrderVm.Action, NewOrderVm.UiState>() {
private fun refuseOrder() {
LoadingManager.showLoading()
RetrofitHelper.getDefaultService().refuseOrder(RefuseOrderRequest(taskId = uiState.value.jpushBean?.taskId,
RetrofitHelper.getDefaultService()
.refuseOrder(RefuseOrderRequest(taskId = uiState.value.jpushBean?.taskId,
vehicleId = GlobalData.driverInfoBean?.vehicleId,
taskCode = uiState.value.jpushBean?.taskCode,
userId = GlobalData.driverInfoBean?.userId))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<String>() {
override fun doSuccess(it: String?) {
userId = GlobalData.driverInfoBean?.userId)).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(object : BaseObserver<String>() {
override fun doSuccess(it : String?) {
LoadingManager.hideLoading()
updateState(uiState.value.copy(refuseSuccess = true))
LogUtil.print("refuseOrder", "订单拒绝成功")
}
override fun doFailure(code: Int, msg: String?) {
override fun doFailure(code : Int, msg : String?) {
LoadingManager.hideLoading()
ToastUtils.showShort(msg)
LogUtil.print("refuseOrder", "failed=$msg")
@ -151,27 +173,43 @@ class NewOrderVm : BaseVm<NewOrderVm.Action, NewOrderVm.UiState>() {
var timeLeft = 120
while (timeLeft > 0 && isActive) {
delay(1000)
timeLeft--
timeLeft --
updateState(uiState.value.copy(remainingTime = timeLeft))
}
if (timeLeft == 0 && isActive) {
// 倒计时结束,显示订单超时
updateState(uiState.value.copy(
isTimeout = true,
showTimeoutDialog = true
))
if (timeLeft == 0 && isActive) { // 倒计时结束,显示订单超时
updateState(uiState.value.copy(isTimeout = true, showTimeoutDialog = true))
}
} catch (e: Exception) {
} catch (e : Exception) {
LogUtil.print("startTimer", "倒计时异常: ${e.message}")
}
}
}
private fun updateTimer(remainingTime: Int) {
private fun isNeedShowNotes() : Boolean {
if (! uiState.value.taskNotesBean?.taskNotes.isNullOrBlank()) {
return true
}
if (! uiState.value.taskNotesBean?.customerNotes.isNullOrBlank()) {
return true
}
if (! uiState.value.taskNotesBean?.feeStandard.isNullOrBlank()) {
return true
}
if (! uiState.value.taskNotesBean?.otherNotes.isNullOrBlank()) {
return true
}
return false
}
private fun updateTimer(remainingTime : Int) {
updateState(uiState.value.copy(remainingTime = remainingTime))
}
private fun searchDrivingRoute(jpushBean: JpushBean?) {
private fun searchDrivingRoute(jpushBean : JpushBean?) {
if (GlobalData.currentLocation == null) {
ToastUtils.showShort("获取当前位置失败")
return
@ -179,31 +217,30 @@ class NewOrderVm : BaseVm<NewOrderVm.Action, NewOrderVm.UiState>() {
updateState(uiState.value.copy(isLoading = true))
val startPoint = LatLonPoint(
GlobalData.currentLocation?.latitude ?: 0.0,
GlobalData.currentLocation?.longitude ?: 0.0
)
val startPoint = LatLonPoint(GlobalData.currentLocation?.latitude ?: 0.0,
GlobalData.currentLocation?.longitude ?: 0.0)
val endPoint = when {
jpushBean?.distLat != null && jpushBean.distLat != 0.0 &&
jpushBean.distLng != null && jpushBean.distLng != 0.0 -> {
jpushBean?.distLat != null && jpushBean.distLat != 0.0 && jpushBean.distLng != null && jpushBean.distLng != 0.0 -> {
LatLonPoint(jpushBean.distLat, jpushBean.distLng)
}
jpushBean?.lat != null && jpushBean.lat != 0.0 &&
jpushBean.lng != null && jpushBean.lng != 0.0 -> {
jpushBean?.lat != null && jpushBean.lat != 0.0 && jpushBean.lng != null && jpushBean.lng != 0.0 -> {
LatLonPoint(jpushBean.lat, jpushBean.lng)
}
else -> null
}
if (endPoint == null) return
val fromAndTo = RouteSearch.FromAndTo(startPoint, endPoint)
val query = RouteSearch.DriveRouteQuery(fromAndTo, RouteSearch.DrivingDefault, null, null, "")
val query =
RouteSearch.DriveRouteQuery(fromAndTo, RouteSearch.DrivingDefault, null, null, "")
RouteSearch(GlobalData.application).apply {
setRouteSearchListener(object : RouteSearch.OnRouteSearchListener {
override fun onDriveRouteSearched(result: DriveRouteResult?, errorCode: Int) {
override fun onDriveRouteSearched(result : DriveRouteResult?, errorCode : Int) {
updateState(uiState.value.copy(isLoading = false))
if (errorCode == 1000 && result != null && result.paths.isNotEmpty()) {
@ -217,20 +254,18 @@ class NewOrderVm : BaseVm<NewOrderVm.Action, NewOrderVm.UiState>() {
val dateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
val estimatedTime = dateFormat.format(Date(arrivalTime))
updateState(uiState.value.copy(
routePoints = points,
updateState(uiState.value.copy(routePoints = points,
remainingDistance = path.distance.toDouble(),
estimatedArrivalTime = estimatedTime
))
estimatedArrivalTime = estimatedTime))
} else {
ToastUtils.showShort("路线规划失败,请重试")
LogUtil.print("searchDrivingRoute", "路径规划失败: errorCode=$errorCode")
}
}
override fun onBusRouteSearched(p0: BusRouteResult?, p1: Int) {}
override fun onWalkRouteSearched(p0: WalkRouteResult?, p1: Int) {}
override fun onRideRouteSearched(p0: RideRouteResult?, p1: Int) {}
override fun onBusRouteSearched(p0 : BusRouteResult?, p1 : Int) {}
override fun onWalkRouteSearched(p0 : WalkRouteResult?, p1 : Int) {}
override fun onRideRouteSearched(p0 : RideRouteResult?, p1 : Int) {}
})
calculateDriveRouteAsyn(query)
}
@ -241,33 +276,35 @@ class NewOrderVm : BaseVm<NewOrderVm.Action, NewOrderVm.UiState>() {
try {
timerJob?.cancel()
timerJob = null
} catch (e: Exception) {
} catch (e : Exception) {
LogUtil.print("onCleared", "取消倒计时异常: ${e.message}")
}
}
sealed class Action {
data class Init(val jpushBean: JpushBean?) : Action()
data class Init(val jpushBean : JpushBean?) : Action()
data object AcceptOrder : Action()
data class UpdateState(val uiState: UiState) : Action()
data class UpdateState(val uiState : UiState) : Action()
data object RefuseOrder : Action()
data object ShowTaskNotes : Action()
data object StartTimer : Action()
data class UpdateTimer(val remainingTime: Int) : Action()
data class UpdateTimer(val remainingTime : Int) : Action()
}
data class UiState(
val jpushBean: JpushBean? = null,
val showCallPhoneDialog: Boolean? = false,
val markers: ArrayList<MarkerOptions>? = null,
val refuseSuccess: Boolean? = false,
val remainingTime: Int = 50,
val routePoints: List<LatLng>? = null,
val remainingDistance: Double = 0.0,
val estimatedArrivalTime: String = "",
val isLoading: Boolean = false,
val isTimeout: Boolean = false,
val showTimeoutDialog: Boolean = false
)
data class UiState(val jpushBean : JpushBean? = null,
val showCallPhoneDialog : Boolean? = false,
val markers : ArrayList<MarkerOptions>? = null,
val refuseSuccess : Boolean? = false,
val taskNotesBean : TaskNotesBean? = null,
val showTaskNotesDialog : Boolean? = false,
val acceptOrderDialog : Boolean? = null,
val remainingTime : Int = 50,
val routePoints : List<LatLng>? = null,
val remainingDistance : Double = 0.0,
val estimatedArrivalTime : String = "",
val isLoading : Boolean = false,
val isTimeout : Boolean = false,
val showTimeoutDialog : Boolean = false)
}

View File

@ -39,14 +39,12 @@ import com.za.base.theme.bgColor
import com.za.base.theme.black5
import com.za.base.theme.headBgColor
import com.za.base.view.ChoiceMapDialog
import com.za.base.view.CommonButton
import com.za.base.view.CommonDialog
import com.za.bean.db.order.OrderInfo
import com.za.ext.callPhone
import com.za.ext.convertToFlowName
import com.za.ext.copy
import com.za.servicing.R
import com.za.ui.servicing.order_give_up.OrderGiveUpActivity
@Composable
fun OrderDetailItemScreen(orderInfo : OrderInfo?) {
@ -57,6 +55,7 @@ fun OrderDetailItemScreen(orderInfo : OrderInfo?) {
.padding(5.dp)) {
OrderDetailBaseInformationView(orderInfo = orderInfo)
OrderDetailServiceInformationView(orderInfo = orderInfo)
OrderDetailTime(orderInfo = orderInfo)
}
}
@ -187,8 +186,11 @@ private fun OrderDetailBaseInformationView(orderInfo : OrderInfo?) {
modifier = Modifier.size(15.dp))
}
if (! orderInfo?.carModel.isNullOrBlank()) {
Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Row(modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically) {
Text(text = "车型",
color = titleColor,
fontSize = titleSize,
@ -202,6 +204,25 @@ private fun OrderDetailBaseInformationView(orderInfo : OrderInfo?) {
}
}
//拖车责任险
if (! orderInfo?.policyNo.isNullOrBlank()) {
Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically) {
Text(text = "拖车责任险",
color = titleColor,
fontSize = titleSize,
fontWeight = FontWeight.Medium,
modifier = Modifier.width(75.dp))
Spacer(modifier = Modifier.width(5.dp))
Text(text = "${orderInfo?.policyNo}",
color = contentColor,
fontSize = titleSize,
fontWeight = FontWeight.Medium)
}
}
}
Spacer(modifier = Modifier.height(10.dp))
}
@ -211,7 +232,6 @@ private fun OrderDetailServiceInformationView(orderInfo : OrderInfo?) {
val titleSize = 12.sp
val titleColor = Color(0xFF7A7A7A)
val contentColor = Color.Black
val context = LocalContext.current
// 1 事发地 2 目的地
var showChoiceMapDialog by remember { mutableStateOf<Int?>(null) }
@ -355,11 +375,77 @@ private fun OrderDetailServiceInformationView(orderInfo : OrderInfo?) {
Spacer(modifier = Modifier.height(10.dp))
CommonButton(text = "客户放弃") {
OrderGiveUpActivity.goOrderGiveUpActivity(context,
orderInfo = orderInfo,
userOrderId = orderInfo?.userOrderId,
giveUpType = 0)
}
@Composable
fun OrderDetailTime(orderInfo : OrderInfo?) {
val titleSize = 12.sp
val titleColor = Color(0xFF7A7A7A)
val contentColor = Color.Black
Column(modifier = Modifier
.fillMaxWidth()
.background(color = Color.White, shape = RoundedCornerShape(4.dp))
.padding(10.dp),
verticalArrangement = Arrangement.Top) {
Box(contentAlignment = Alignment.CenterStart) {
Text(text = "订单时间",
color = Color.Black,
fontWeight = FontWeight.Medium,
fontSize = 16.sp)
}
HorizontalDivider(color = black5, modifier = Modifier.padding(vertical = 10.dp))
if (! orderInfo?.acceptTime.isNullOrBlank()) {
Row(modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically) {
Text(text = "接单时间",
color = titleColor,
fontSize = titleSize,
fontWeight = FontWeight.Medium,
modifier = Modifier.width(75.dp))
Spacer(modifier = Modifier.width(5.dp))
Text(text = "${orderInfo?.acceptTime}",
color = contentColor,
fontSize = titleSize,
fontWeight = FontWeight.Medium)
}
}
if (! orderInfo?.arriveTime.isNullOrBlank()) {
Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically) {
Text(text = "到达事发地时间",
color = titleColor,
fontSize = titleSize,
fontWeight = FontWeight.Medium,
modifier = Modifier.width(75.dp))
Spacer(modifier = Modifier.width(5.dp))
Text(text = "${orderInfo?.arriveTime}",
color = contentColor,
fontSize = titleSize,
fontWeight = FontWeight.Medium)
}
}
if (! orderInfo?.arriveDestTime.isNullOrBlank()) {
Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically) {
Text(text = "到达目的地时间",
color = titleColor,
fontSize = titleSize,
fontWeight = FontWeight.Medium,
modifier = Modifier.width(75.dp))
Spacer(modifier = Modifier.width(5.dp))
Text(text = "${orderInfo?.arriveDestTime}",
color = contentColor,
fontSize = titleSize,
fontWeight = FontWeight.Medium)
}
}
Spacer(modifier = Modifier.height(10.dp))
}
}

View File

@ -28,9 +28,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.za.base.theme.headBgColor
import com.za.base.theme.white80
import com.za.base.view.CommonButton
import com.za.base.view.HeadView
import com.za.bean.db.order.OrderInfo
import com.za.ext.finish
import com.za.ui.servicing.order_give_up.OrderGiveUpActivity
import kotlinx.coroutines.launch
@Composable
@ -41,6 +43,13 @@ fun OrderDetailScreen(orderInfo : OrderInfo?) {
val scope = rememberCoroutineScope()
Scaffold(topBar = {
HeadView(title = "订单信息", onBack = { context.finish() })
}, bottomBar = {
CommonButton(text = "客户放弃") {
OrderGiveUpActivity.goOrderGiveUpActivity(context,
orderInfo = orderInfo,
userOrderId = orderInfo?.userOrderId,
giveUpType = 0)
}
}) {
Column(modifier = Modifier
.fillMaxSize()

View File

@ -1,5 +1,8 @@
package com.za.ui.servicing.in_servicing_setting
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
@ -9,9 +12,14 @@ 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.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -21,9 +29,13 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import coil.compose.AsyncImage
import com.za.base.theme.black5
import com.za.base.theme.black65
import com.za.base.view.CommonButton
import com.za.bean.TaskNotesBean
import com.za.bean.db.order.OrderInfo
import com.za.servicing.R
@ -49,10 +61,10 @@ fun OrderRequirementsScreen(orderInfo : OrderInfo?) {
}
@Composable
private fun OrderRequirementsItemView(title : String?, content : String?) {
fun OrderRequirementsItemView(title : String?, content : String?) {
Column(modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)) {
.padding(vertical = 5.dp)) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
AsyncImage(model = R.drawable.sv_warn_red,
contentDescription = null,
@ -76,10 +88,10 @@ private fun OrderRequirementsItemView(title : String?, content : String?) {
}
@Composable
private fun CarModeView(orderInfo : OrderInfo?) {
fun CarModeView(orderInfo : OrderInfo?) {
Column(modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)) {
.padding(vertical = 5.dp)) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
AsyncImage(model = R.drawable.sv_warn_red,
contentDescription = null,
@ -101,3 +113,89 @@ private fun CarModeView(orderInfo : OrderInfo?) {
HorizontalDivider(color = black5, modifier = Modifier.fillMaxWidth())
}
}
@Composable
fun OrderTaskNotesDialog(taskNotesBean : TaskNotesBean?,
isShowChangeBattery : Boolean? = false,
dismiss : () -> Unit,
confirm : () -> Unit) {
Dialog(onDismissRequest = { dismiss() },
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)) {
Box(contentAlignment = Alignment.BottomCenter,
modifier = Modifier.background(color = Color.White,
shape = RoundedCornerShape(13.dp))) {
Column(modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.verticalScroll(state = rememberScrollState())
.padding(horizontal = 10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top) {
Spacer(modifier = Modifier.height(20.dp))
Row(modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp)) {
Text("合同名称",
color = Color.Black,
fontSize = 15.sp,
fontWeight = FontWeight.Medium)
Spacer(Modifier.weight(1f))
Text("平安保险",
color = Color.Black,
fontSize = 15.sp,
fontWeight = FontWeight.Bold)
}
HorizontalDivider(color = black5, modifier = Modifier.padding(vertical = 10.dp))
if (! taskNotesBean?.otherNotes.isNullOrBlank()) {
OrderRequirementsItemView(title = "特殊提醒",
content = taskNotesBean?.otherNotes)
}
if (! taskNotesBean?.feeStandard.isNullOrBlank()) {
OrderRequirementsItemView(title = "收费标准",
content = taskNotesBean?.feeStandard)
}
if (! taskNotesBean?.modelVinNo.isNullOrBlank()) {
OrderRequirementsItemView(title = "车型", content = taskNotesBean?.modelVinNo)
}
if (! taskNotesBean?.taskNotes.isNullOrBlank()) {
OrderRequirementsItemView(title = "救援要求",
content = taskNotesBean?.taskNotes)
}
if (! taskNotesBean?.customerNotes.isNullOrBlank()) {
OrderRequirementsItemView(title = "客户要求",
content = taskNotesBean?.customerNotes)
}
if (isShowChangeBattery == true) {
Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.padding(vertical = 10.dp, horizontal = 10.dp),
verticalAlignment = Alignment.CenterVertically) {
Icon(imageVector = Icons.Default.Warning,
tint = Color.Red,
contentDescription = "",
modifier = Modifier.size(24.dp))
Text("此订单不允许销售电瓶",
color = Color.Red,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(start = 8.dp))
}
}
Spacer(modifier = Modifier.height(65.dp))
}
CommonButton(text = "我已阅读") { confirm() }
}
}
}

View File

@ -40,7 +40,7 @@ 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
import com.za.ui.camera.ServicePeopleRealActivity
@Composable
fun ServicePeopleConfirmScreen(vm : InServicePeopleConfirmVm = viewModel(),
@ -86,8 +86,7 @@ fun ServicePeopleConfirmScreen(vm : InServicePeopleConfirmVm = viewModel(),
confirmText = "重新认证",
confirm = {
vm.updateState(uiState.value.copy(showCompareFailedDialog = null))
val intent = Intent(context, ZdCameraXActivity::class.java)
intent.putExtra("isBack", false)
val intent = Intent(context, ServicePeopleRealActivity::class.java)
getResult.launch(intent)
},
cancelText = "关闭",
@ -166,8 +165,7 @@ fun ServicePeopleConfirmScreen(vm : InServicePeopleConfirmVm = viewModel(),
Spacer(modifier = Modifier.height(40.dp))
CommonButton(text = "开始核验", onClick = {
val intent = Intent(context, ZdCameraXActivity::class.java)
intent.putExtra("isBack", false)
val intent = Intent(context, ServicePeopleRealActivity::class.java)
getResult.launch(intent)
})
}

View File

@ -37,7 +37,8 @@ class ConfirmH5SuccessVm : IServicingVm<ConfirmH5SuccessVm.Action, ConfirmH5Succ
is Action.UpdateState -> updateState(action.uiState)
is Action.TaskFinish -> taskFinish()
is Action.ClearOfflineTask -> clearCurrentOrderOfflineTask()
is Action.UpdateCurrentEleWorkOrder->updateCurrentEleWorkOrder(action.type, action.path)
is Action.UpdateCurrentEleWorkOrder -> updateCurrentEleWorkOrder(action.type,
action.path)
}
}
@ -214,13 +215,14 @@ class ConfirmH5SuccessVm : IServicingVm<ConfirmH5SuccessVm.Action, ConfirmH5Succ
updateState(uiState.value.copy(goNextPage = true))
return
}
updateState(uiState.value.copy(loadingState = LoadingState.LoadingFailed))
updateState(uiState.value.copy(loadingState = LoadingState.LoadingFailed(msg)))
LogUtil.print("任务失败", "code==$code msg==$msg")
}
})
}, failed = {
LoadingManager.hideLoading()
updateState(uiState.value.copy(loadingState = LoadingState.LoadingFailed))
ToastUtils.showLong(it)
updateState(uiState.value.copy(loadingState = LoadingState.LoadingFailed(it)))
})
}

View File

@ -12,6 +12,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.za.base.BaseActivity
import com.za.base.view.CommonDialog
import com.za.base.view.LoadingManager
import com.za.room.RoomHelper
import com.za.ui.servicing.order_confirm.input_money.InputMoneyActivity
import com.za.ui.servicing.order_confirm.real_order_confirm.RealOrderConfirmActivity
import com.za.ui.servicing.order_confirm.receive_money.ReceiveMoneyActivity
@ -25,7 +26,7 @@ class OrderConfirmActivity : BaseActivity() {
}
@Composable
fun OrderConfirmInitScreen(vm: OrderConfirmInitVm = viewModel()) {
fun OrderConfirmInitScreen(vm : OrderConfirmInitVm = viewModel()) {
val uiState = vm.uiState.collectAsStateWithLifecycle()
val context = LocalContext.current
LaunchedEffect(key1 = Unit) {
@ -33,17 +34,27 @@ fun OrderConfirmInitScreen(vm: OrderConfirmInitVm = viewModel()) {
}
if (uiState.value.showNoNeedPayDialog == true) {
CommonDialog(title = "是否需要收款?", confirmText = "去收款", cancelText = "无需收款",
CommonDialog(title = "是否需要收款?",
confirmText = "去收款",
cancelText = "无需收款",
confirm = {
vm.updateState(uiState.value.copy(showNoNeedPayDialog = false))
InputMoneyActivity.goInputMoney(context, userOrderId = uiState.value.orderInfo?.userOrderId
?: 0, taskId = uiState.value.orderInfo?.taskId ?: 0)
InputMoneyActivity.goInputMoney(context,
userOrderId = uiState.value.orderInfo?.userOrderId ?: 0,
taskId = uiState.value.orderInfo?.taskId ?: 0)
},
cancel = {
vm.updateState(uiState.value.copy(showNoNeedPayDialog = false, orderConfirmState = OrderConfirmInitVm.OrderConfirmState.OrderConfirm))
uiState.value.eleWorkOrderBean?.copy(driverChoiceNoNeedReceiveMoney = true)?.let {
RoomHelper.db?.eleWorkOrderDao()?.update(it)
}
vm.updateState(uiState.value.copy(showNoNeedPayDialog = false,
orderConfirmState = OrderConfirmInitVm.OrderConfirmState.Init))
vm.dispatch(OrderConfirmInitVm.Action.Init)
},
dismiss = {
vm.updateState(uiState.value.copy(showNoNeedPayDialog = false, orderConfirmState = OrderConfirmInitVm.OrderConfirmState.OrderConfirm))
vm.updateState(uiState.value.copy(showNoNeedPayDialog = false,
orderConfirmState = OrderConfirmInitVm.OrderConfirmState.Init))
vm.dispatch(OrderConfirmInitVm.Action.Init)
})
}
@ -66,8 +77,9 @@ fun OrderConfirmInitScreen(vm: OrderConfirmInitVm = viewModel()) {
}
is OrderConfirmInitVm.OrderConfirmState.PaymentInfo -> {
ReceiveMoneyActivity.goReceiveMoney(context, userOrderId = uiState.value.orderInfo?.userOrderId
?: 0, taskId = uiState.value.orderInfo?.taskId ?: 0)
ReceiveMoneyActivity.goReceiveMoney(context,
userOrderId = uiState.value.orderInfo?.userOrderId ?: 0,
taskId = uiState.value.orderInfo?.taskId ?: 0)
}
else -> RealOrderConfirmActivity.goRealOrderConfirm(context)

View File

@ -19,11 +19,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
class OrderConfirmInitVm : IServicingVm<OrderConfirmInitVm.Action, OrderConfirmInitVm.UiState>() {
private val _uiState = MutableStateFlow(UiState())
val uiState get() = _uiState
override fun updateState(uiState: UiState) {
override fun updateState(uiState : UiState) {
_uiState.value = uiState
}
override fun dispatch(action: Action) {
override fun dispatch(action : Action) {
when (action) {
is Action.Init -> init()
is Action.UpdateState -> updateState(action.uiState)
@ -35,18 +35,23 @@ class OrderConfirmInitVm : IServicingVm<OrderConfirmInitVm.Action, OrderConfirmI
queryPaymentInfo(uiState.value.orderInfo)
}
private fun queryPaymentInfo(orderInfo: OrderInfo?) {
private fun queryPaymentInfo(orderInfo : OrderInfo?) {
LoadingManager.showLoading()
val paymentInfoRequest = PaymentInfoRequest(orderInfo?.userOrderId, orderInfo?.taskId)
val eleWorkOrderBean = RoomHelper.db?.eleWorkOrderDao()?.getEleWorkOrder(taskId = orderInfo?.taskId
?: 0)
val eleWorkOrderBean =
RoomHelper.db?.eleWorkOrderDao()?.getEleWorkOrder(taskId = orderInfo?.taskId ?: 0)
RetrofitHelper.getDefaultService().paymentInfoQuery(paymentInfoRequest)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<PaymentInfoBean>() {
override fun doSuccess(it: PaymentInfoBean?) {
override fun doSuccess(it : PaymentInfoBean?) {
LoadingManager.hideLoading()
updateState(uiState.value.copy(paymentInfoBean = it, orderInfo = orderInfo, eleWorkOrderBean = eleWorkOrderBean))
updateState(uiState.value.copy(paymentInfoBean = it,
orderInfo = orderInfo,
eleWorkOrderBean = eleWorkOrderBean))
if (eleWorkOrderBean != null && eleWorkOrderBean.driverChoiceNoNeedReceiveMoney == true) {
handlerOtherState(orderInfo, eleWorkOrderBean)
return
}
if (it?.isPayment == true && it.tradeState != 2) {
if (it.askPayment == true) {
updateState(uiState.value.copy(showNoNeedPayDialog = true))
@ -56,28 +61,20 @@ class OrderConfirmInitVm : IServicingVm<OrderConfirmInitVm.Action, OrderConfirmI
return
}
if (orderInfo?.electronOrderState == 0 || orderInfo?.electronOrderState == 1
|| orderInfo?.electronOrderState == 2
) {
updateState(uiState.value.copy(orderConfirmState = OrderConfirmState.ConfirmEle))
return
handlerOtherState(orderInfo, eleWorkOrderBean)
}
if (eleWorkOrderBean?.changeBattery == true) {
updateState(uiState.value.copy(orderConfirmState = OrderConfirmState.ChangeBattery))
return
}
if (eleWorkOrderBean?.hasCreatedEleWorkOrderPhoto == null || eleWorkOrderBean.hasCreatedEleWorkOrderPhoto != true) {
updateState(uiState.value.copy(orderConfirmState = OrderConfirmState.ConfirmH5Success))
return
}
updateState(uiState.value.copy(orderConfirmState = OrderConfirmState.OrderConfirm))
}
override fun doFailure(code: Int, msg: String?) {
override fun doFailure(code : Int, msg : String?) {
LoadingManager.hideLoading()
LogUtil.print("eleworkOrder", eleWorkOrderBean.toJson() ?: "")
updateState(uiState.value.copy(orderInfo = orderInfo, eleWorkOrderBean = eleWorkOrderBean))
handlerOtherState(orderInfo, eleWorkOrderBean)
LogUtil.print("queryPaymentInfo", "failed=$msg request=${paymentInfoRequest.toJson()}")
}
})
}
private fun handlerOtherState(orderInfo : OrderInfo?, eleWorkOrderBean : EleWorkOrderBean?) {
if (orderInfo?.electronOrderState == 0 || orderInfo?.electronOrderState == 1 || orderInfo?.electronOrderState == 2) {
updateState(uiState.value.copy(orderConfirmState = OrderConfirmState.ConfirmEle))
return
@ -87,31 +84,26 @@ class OrderConfirmInitVm : IServicingVm<OrderConfirmInitVm.Action, OrderConfirmI
updateState(uiState.value.copy(orderConfirmState = OrderConfirmState.ChangeBattery))
return
}
if (eleWorkOrderBean?.hasCreatedEleWorkOrderPhoto == null || eleWorkOrderBean.hasCreatedEleWorkOrderPhoto != true) {
updateState(uiState.value.copy(orderConfirmState = OrderConfirmState.ConfirmH5Success))
return
}
updateState(uiState.value.copy(orderConfirmState = OrderConfirmState.OrderConfirm))
LogUtil.print("queryPaymentInfo", "failed=$msg request=${paymentInfoRequest.toJson()}")
}
})
}
sealed class Action {
data object Init : Action()
data class UpdateState(val uiState: UiState) : Action()
data class UpdateState(val uiState : UiState) : Action()
}
data class UiState(
val orderConfirmState: OrderConfirmState = OrderConfirmState.Init,
val orderInfo: OrderInfo? = null,
val eleWorkOrderBean: EleWorkOrderBean? = null,
val goNextPage: UpdateTaskBean? = null,
val isGoNextPageDialog: Boolean? = null,
val paymentInfoBean: PaymentInfoBean? = null,
val showNoNeedPayDialog: Boolean? = null,
val orderConfirmState : OrderConfirmState = OrderConfirmState.Init,
val orderInfo : OrderInfo? = null,
val eleWorkOrderBean : EleWorkOrderBean? = null,
val goNextPage : UpdateTaskBean? = null,
val isGoNextPageDialog : Boolean? = null,
val paymentInfoBean : PaymentInfoBean? = null,
val showNoNeedPayDialog : Boolean? = null,
)
sealed class OrderConfirmState {

View File

@ -0,0 +1,49 @@
package com.za.ui.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Path
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.camera.view.PreviewView
import com.blankj.utilcode.util.ConvertUtils
class CirclePreviewContainer @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val path = Path()
private var previewView: PreviewView? = null
init {
setWillNotDraw(false)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
val centerX = w / 2f
val centerY = h / 2f
val radius = ConvertUtils.dp2px(150f).toFloat().coerceAtMost(w / 2f).coerceAtMost(h / 2f)
path.reset()
path.addCircle(centerX, centerY, radius, Path.Direction.CW)
}
override fun dispatchDraw(canvas: Canvas) {
canvas.save()
canvas.clipPath(path)
super.dispatchDraw(canvas)
canvas.restore()
}
override fun onFinishInflate() {
super.onFinishInflate()
if (childCount > 0) {
val child = getChildAt(0)
if (child is PreviewView) {
previewView = child
}
}
}
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Dark semi-transparent background -->
<item>
<shape android:shape="rectangle">
<solid android:color="#00000000" />
</shape>
</item>
<!-- Circle with light border -->
<item android:gravity="center">
<shape android:shape="oval">
<size
android:width="300dp"
android:height="300dp" />
<stroke
android:width="2dp"
android:color="#FFFFFFFF" />
<solid android:color="#00000000" />
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.za.ui.view.CirclePreviewContainer
android:layout_width="300dp"
android:layout_height="300dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.za.ui.view.CirclePreviewContainer>
<View
android:id="@+id/previewOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/preview_overlay_mask" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/stepText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="50dp"
android:background="#80000000"
android:padding="16dp"
android:textColor="@android:color/white"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="请保持面部居中" />
<FrameLayout
android:id="@+id/confirmationLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:padding="16dp">
<com.za.signature.view.CircleImageView
android:id="@+id/previewImage"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:scaleType="centerCrop" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="16dp"
android:text="确认使用这张照片?"
android:textColor="@android:color/white"
android:textSize="18sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/retryButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="重试" />
<Button
android:id="@+id/confirmButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确认" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>