[BadCase]代码提交
[BadCase]相关参数改成正式的 [BadCase]相关参数改成正式的
This commit is contained in:
@@ -109,4 +109,8 @@ class MoGoAutopilotProvider :
|
||||
override fun setIPCReboot() {
|
||||
AdasManager.getInstance().rebootIPC()
|
||||
}
|
||||
|
||||
override fun recordCause(key: String?, name: String?, reason: String?) {
|
||||
AdasManager.getInstance().recordCause(key, name, reason)
|
||||
}
|
||||
}
|
||||
@@ -147,6 +147,14 @@ class WarningFloat {
|
||||
}
|
||||
}
|
||||
|
||||
fun setWindowWidth(width: Int) = apply {
|
||||
this.config.width = width
|
||||
}
|
||||
|
||||
fun setWindowHeight(height: Int) = apply {
|
||||
this.config.height = height
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建浮窗,包括Activity浮窗和系统浮窗,如若系统浮窗无权限,先进行权限申请
|
||||
*/
|
||||
|
||||
@@ -65,8 +65,8 @@ internal class WarningFloatWindowHelper(
|
||||
// 没有边界限制,允许窗口扩展到屏幕外
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|
||||
else WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
||||
width = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
width = config.width
|
||||
height = config.height
|
||||
|
||||
// 如若设置了固定坐标,直接定位
|
||||
if (config.locationPair != Pair(0, 0)) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.mogo.eagle.core.function.hmi.notification
|
||||
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import com.mogo.eagle.core.function.hmi.notification.anim.DefaultAnimator
|
||||
import com.mogo.eagle.core.function.hmi.notification.enums.ShowPattern
|
||||
import com.mogo.eagle.core.function.hmi.notification.enums.SidePattern
|
||||
@@ -59,4 +60,9 @@ data class WarningNotificationConfig(
|
||||
// Callbacks
|
||||
var callbacks: OnFloatCallbacks? = null,
|
||||
|
||||
// 窗口宽度
|
||||
var width: Int = WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
|
||||
// 窗口高度
|
||||
var height: Int = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
@@ -1,23 +1,30 @@
|
||||
package com.mogo.eagle.core.function.hmi.ui
|
||||
|
||||
import android.animation.Animator
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.view.*
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.alibaba.android.arouter.facade.annotation.Route
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
|
||||
import com.mogo.commons.mvp.MvpFragment
|
||||
import com.mogo.commons.voice.AIAssist
|
||||
import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult
|
||||
import com.mogo.eagle.core.data.camera.CameraEntity
|
||||
import com.mogo.eagle.core.data.config.HmiBuildConfig
|
||||
import com.mogo.eagle.core.data.constants.MoGoFragmentPaths
|
||||
import com.mogo.eagle.core.data.enums.WarningDirectionEnum
|
||||
import com.mogo.eagle.core.data.notice.NoticeNormalData
|
||||
import com.mogo.eagle.core.data.notice.NoticeTrafficStylePushData
|
||||
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotIdentifyListener
|
||||
import com.mogo.eagle.core.function.api.hmi.warning.IMoGoWaringProvider
|
||||
import com.mogo.eagle.core.function.api.hmi.warning.IMoGoWarningStatusListener
|
||||
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotManager
|
||||
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotIdentifyListenerManager
|
||||
import com.mogo.eagle.core.function.call.check.CallerCheckManager
|
||||
import com.mogo.eagle.core.function.call.monitor.CallerMonitorManager
|
||||
import com.mogo.eagle.core.function.hmi.R
|
||||
@@ -30,12 +37,21 @@ import com.mogo.eagle.core.function.hmi.ui.notice.NoticeBannerView
|
||||
import com.mogo.eagle.core.function.hmi.ui.notice.NoticeNormalBannerView
|
||||
import com.mogo.eagle.core.function.hmi.ui.setting.DebugSettingView
|
||||
import com.mogo.eagle.core.function.hmi.ui.tools.AutoPilotAndCheckView
|
||||
import com.mogo.eagle.core.function.hmi.ui.tools.AutoPilotBadCaseView
|
||||
import com.mogo.eagle.core.function.hmi.ui.tools.post
|
||||
import com.mogo.eagle.core.function.hmi.ui.widget.V2XNotificationView
|
||||
import com.mogo.eagle.core.utilcode.kotlin.onClick
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.Logger
|
||||
import com.mogo.eagle.core.utilcode.util.ThreadUtils
|
||||
import com.mogo.eagle.core.utilcode.util.ToastUtils
|
||||
import com.mogo.module.common.enums.EventTypeEnum
|
||||
import kotlinx.android.synthetic.main.fragment_hmi.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* @author xiaoyuzhou
|
||||
@@ -45,7 +61,7 @@ import kotlinx.android.synthetic.main.fragment_hmi.*
|
||||
@Route(path = MoGoFragmentPaths.PATH_FRAGMENT_HMI)
|
||||
class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>(),
|
||||
IMoGoWaringProvider,
|
||||
MoGoWarningContract.View {
|
||||
MoGoWarningContract.View, IMoGoAutopilotIdentifyListener {
|
||||
private val TAG = "MoGoHmiFragment"
|
||||
|
||||
// DebugSettingView
|
||||
@@ -64,9 +80,106 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
|
||||
|
||||
private var toolsView: AutoPilotAndCheckView? = null
|
||||
|
||||
private var autoPilotToolsFloat: WarningFloat.Builder? = null
|
||||
|
||||
// 检测、自动驾驶速度设置
|
||||
private var toolsViewFloat: WarningFloat.Builder? = null
|
||||
|
||||
@Volatile
|
||||
private var autoPilotBadCaseEntrance: View? = null
|
||||
|
||||
private var autoPilotBadCaseView: AutoPilotBadCaseView? = null
|
||||
|
||||
companion object {
|
||||
private const val MSG_WHAT_DISMISS_BAD_CASE_ENTRY = 0x1010
|
||||
private val DURATION_FOR_DISMISS = TimeUnit.HOURS.toMillis(4)
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
private val channel by lazy {
|
||||
Channel<AutoPilotRecordResult?>(UNLIMITED).also {
|
||||
lifecycleScope.launchWhenResumed {
|
||||
withContext(Dispatchers.Default) {
|
||||
while (!it.isClosedForReceive) {
|
||||
try {
|
||||
val entrance = autoPilotBadCaseEntrance
|
||||
val old = entrance?.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult
|
||||
if (entrance == null || old == null || old.consumed) {
|
||||
Logger.d(TAG, "-- step -- 1 --")
|
||||
var oldT = try {
|
||||
old?.timestamp?.takeIf { it.isNotBlank() }?.let {
|
||||
SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()).parse(it)?.time ?: 0L
|
||||
} ?: 0L
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
0L
|
||||
}
|
||||
var record: AutoPilotRecordResult? = null
|
||||
var newT = try {
|
||||
it.receive()?.also { record = it }?.timestamp?.takeIf { it.isNotBlank() }?.let {
|
||||
SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()).parse(it)?.time
|
||||
?: 0L
|
||||
} ?: 0L
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
0L
|
||||
}
|
||||
|
||||
if (oldT == 0L || (newT > 0L && (newT - oldT > 0L) && (newT - oldT) < DURATION_FOR_DISMISS)) {
|
||||
Logger.d(TAG, "-- step -- 2 --")
|
||||
record?.takeIf { it.key != old?.key && it.timestamp != old?.timestamp }?.also {
|
||||
Logger.d(TAG, "record: [$record] is displaying and consuming ~~~" )
|
||||
showBadCaseEntrance(it)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
while (oldT != 0L && newT != 0L && (newT - oldT) >= DURATION_FOR_DISMISS) {
|
||||
Logger.d(TAG, "record: [$record] has been discarded, because it has been timeout." )
|
||||
oldT = newT
|
||||
newT = try {
|
||||
it.receive()?.also {
|
||||
record = it
|
||||
}?.timestamp?.takeIf { it.isNotBlank() }?.let {
|
||||
SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()).parse(it)?.time ?: 0L
|
||||
} ?: 0L
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
0L
|
||||
}
|
||||
}
|
||||
record?.takeIf { it.key != old?.key && it.timestamp != old?.timestamp }?.also {
|
||||
Logger.d(TAG, "record: [$record] is displaying for rest ..." )
|
||||
showBadCaseEntrance(it)
|
||||
}
|
||||
} else {
|
||||
Logger.d(TAG, "record: [$old] hasn't been consumed~~~~" )
|
||||
}
|
||||
} finally {
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val handler by lazy {
|
||||
Handler(Handler.Callback {
|
||||
if (it.what == MSG_WHAT_DISMISS_BAD_CASE_ENTRY) {
|
||||
val entrance = autoPilotBadCaseEntrance
|
||||
if (entrance != null && entrance.visibility == View.VISIBLE) {
|
||||
Logger.d(TAG, "${DURATION_FOR_DISMISS}毫秒后BadCase入口消失")
|
||||
(entrance.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult)?.let { itx ->
|
||||
itx.consumed = true
|
||||
}
|
||||
entrance.visibility = View.GONE
|
||||
}
|
||||
return@Callback true
|
||||
}
|
||||
return@Callback false
|
||||
})
|
||||
}
|
||||
|
||||
override fun vipIdentification(visible: Boolean) {
|
||||
ThreadUtils.runOnUiThread {
|
||||
@@ -138,6 +251,155 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
CallerAutopilotIdentifyListenerManager.addListener(TAG, this)
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun onAutopilotRecordResult(record: AutoPilotRecordResult?) {
|
||||
record ?: return
|
||||
Logger.d(TAG, "onAutopilotRecordResult:$record")
|
||||
if (record.type == 1 && record.stat == 100) {
|
||||
lifecycleScope.launchWhenResumed {
|
||||
channel.send(record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
CallerAutopilotIdentifyListenerManager.removeListener(TAG)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun showBadCaseEntrance(record: AutoPilotRecordResult) {
|
||||
Logger.d(TAG, "showBadCaseEntrance:$record")
|
||||
lifecycleScope.launch {
|
||||
if (vs_bad_case_entrance?.parent != null) {
|
||||
val inflateView = vs_bad_case_entrance.inflate()
|
||||
autoPilotBadCaseEntrance = inflateView
|
||||
}
|
||||
val entrance = autoPilotBadCaseEntrance
|
||||
if (entrance != null) {
|
||||
if (entrance.visibility != View.VISIBLE) {
|
||||
entrance.visibility = View.VISIBLE
|
||||
}
|
||||
entrance.setTag(R.id.autopilot_badcase_record, record)
|
||||
entrance.onClick {
|
||||
showBadCasesFloat {
|
||||
it.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
dismissBadCaseEntryAfterSomeTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showBadCasesFloat(dismiss: (() -> Unit)?) {
|
||||
Logger.d(TAG, "showBadCaseToolsFloat")
|
||||
context?.let { it ->
|
||||
if (autoPilotToolsFloat == null) {
|
||||
if (autoPilotBadCaseView == null) {
|
||||
autoPilotBadCaseView = AutoPilotBadCaseView(it).also { itx ->
|
||||
itx.onDismiss {
|
||||
val record =
|
||||
autoPilotBadCaseEntrance?.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult
|
||||
CallerAutoPilotManager.recordCause(record?.key, record?.fileName, null)
|
||||
dismissBadCaseFloatView()
|
||||
}
|
||||
itx.onSelect {
|
||||
lifecycleScope.launch {
|
||||
val record =
|
||||
autoPilotBadCaseEntrance?.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult
|
||||
try {
|
||||
val params = mutableMapOf<String, String>()
|
||||
autoPilotBadCaseEntrance?.apply {
|
||||
params["carLicense"] =
|
||||
MoGoAiCloudClientConfig.getInstance().sn
|
||||
params["filename"] = record?.fileName ?: ""
|
||||
params["filesize"] = record?.total.toString()
|
||||
params["key"] = record?.key ?: ""
|
||||
params["reason"] = it.reason ?: ""
|
||||
params["timestamp"] = System.currentTimeMillis().toString()
|
||||
}
|
||||
val response = post(params)
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body()
|
||||
if (body == null) {
|
||||
Logger.e(TAG, "返回的body是空的~~~")
|
||||
return@launch
|
||||
}
|
||||
if (body.code == 200) {
|
||||
Logger.i(TAG, "ok:${body}")
|
||||
dismissBadCaseFloatView()
|
||||
dismiss?.invoke()
|
||||
ToastUtils.showShort("接管反馈成功~")
|
||||
return@launch
|
||||
}
|
||||
Logger.e(TAG, "fail:${body}")
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
ToastUtils.showShort("网络请求失败,请尝试联网~")
|
||||
Logger.e(TAG, "exception:${t.message}")
|
||||
} finally {
|
||||
record?.consumed = true
|
||||
CallerAutoPilotManager.recordCause(
|
||||
record?.key,
|
||||
record?.fileName,
|
||||
it.id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
autoPilotToolsFloat = WarningFloat.with(it)
|
||||
.setTag("BadCaseCollectFloat")
|
||||
.setLayout(autoPilotBadCaseView!!)
|
||||
.setSidePattern(SidePattern.LEFT)
|
||||
.setGravity(Gravity.LEFT, offsetY = 72)
|
||||
.setImmersionStatusBar(true)
|
||||
.setAnimator(object : DefaultAnimator() {
|
||||
override fun enterAnim(
|
||||
view: View,
|
||||
params: WindowManager.LayoutParams,
|
||||
windowManager: WindowManager,
|
||||
sidePattern: SidePattern
|
||||
): Animator? =
|
||||
super.enterAnim(view, params, windowManager, sidePattern)
|
||||
?.apply {
|
||||
interpolator = OvershootInterpolator()
|
||||
}
|
||||
|
||||
override fun exitAnim(
|
||||
view: View,
|
||||
params: WindowManager.LayoutParams,
|
||||
windowManager: WindowManager,
|
||||
sidePattern: SidePattern
|
||||
): Animator? =
|
||||
super.exitAnim(view, params, windowManager, sidePattern)
|
||||
?.setDuration(200)
|
||||
})
|
||||
.addWarningStatusListener(object : IMoGoWarningStatusListener {
|
||||
override fun onDismiss() {
|
||||
autoPilotToolsFloat = null
|
||||
autoPilotBadCaseView = null
|
||||
}
|
||||
})
|
||||
.show()
|
||||
} else {
|
||||
autoPilotToolsFloat?.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissBadCaseEntryAfterSomeTime() {
|
||||
handler.removeMessages(MSG_WHAT_DISMISS_BAD_CASE_ENTRY)
|
||||
handler.sendEmptyMessageDelayed(MSG_WHAT_DISMISS_BAD_CASE_ENTRY, DURATION_FOR_DISMISS)
|
||||
}
|
||||
|
||||
private fun showToolsFloat() {
|
||||
Logger.d(TAG, "showToolsFloat")
|
||||
context?.let {
|
||||
@@ -696,9 +958,18 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
|
||||
// toolsView?.setStatus(true)
|
||||
}
|
||||
|
||||
private fun dismissBadCaseFloatView() {
|
||||
autoPilotToolsFloat?.let {
|
||||
WarningFloat.dismiss(it.config.floatTag, false)
|
||||
autoPilotToolsFloat = null
|
||||
autoPilotBadCaseView = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Log.d(TAG, "onDestroy")
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
package com.mogo.eagle.core.function.hmi.ui.tools
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.StateListDrawable
|
||||
import android.os.Handler
|
||||
import android.util.AttributeSet
|
||||
import android.util.StateSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.Keep
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.annotations.Expose
|
||||
import com.mogo.commons.debug.DebugConfig
|
||||
import com.mogo.commons.debug.DebugConfig.getNetMode
|
||||
import com.mogo.eagle.core.function.hmi.R
|
||||
import com.mogo.eagle.core.function.hmi.ui.tools.BadCaseEntity.Reason
|
||||
import com.mogo.eagle.core.network.RetrofitFactory
|
||||
import com.mogo.eagle.core.network.utils.GsonUtil
|
||||
import com.mogo.eagle.core.utilcode.kotlin.*
|
||||
import com.mogo.eagle.core.utilcode.util.ThreadUtils
|
||||
import com.mogo.eagle.core.utilcode.util.Utils
|
||||
import kotlinx.android.synthetic.main.layout_badcase_collect.view.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.FieldMap
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import kotlin.Result.Companion.failure
|
||||
import kotlin.Result.Companion.success
|
||||
|
||||
|
||||
private typealias OnDismissCallback = () -> Unit
|
||||
private typealias OnSelectCallback = (Reason) -> Unit
|
||||
|
||||
interface BadCaseApi {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("/yycp-vehicle-management-service/tool/badcase/add")
|
||||
suspend fun post(@FieldMap map: Map<String, String>): Response<PostResult>
|
||||
|
||||
@GET("/yycp-vehicle-management-service/tool/badcase/reasons")
|
||||
suspend fun get(): Response<BadCaseEntity>
|
||||
}
|
||||
|
||||
@Keep
|
||||
class BadCaseEntity {
|
||||
var code: Int = -1
|
||||
var data: List<Reason>? = null
|
||||
var msg: String? = null
|
||||
var success: Boolean = false
|
||||
var total: Int = -1
|
||||
|
||||
@Expose(serialize = false, deserialize = false)
|
||||
var isBuildIn: Boolean = false
|
||||
|
||||
@Keep
|
||||
class Reason {
|
||||
var id: String? = null
|
||||
var reason: String? = null
|
||||
|
||||
/**
|
||||
* 业务字段,不参与序列化和反序列化
|
||||
*/
|
||||
@Expose(deserialize = false, serialize = false)
|
||||
var isChecked: Boolean = false
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
class PostResult {
|
||||
var code: Int = -1
|
||||
var msg: String? = null
|
||||
var data: Array<String>? = null
|
||||
var success: Boolean = false
|
||||
|
||||
override fun toString(): String {
|
||||
return "Result(code=$code, msg=$msg, data=${data?.contentToString()}, success=$success)"
|
||||
}
|
||||
}
|
||||
|
||||
private fun getHost(): String = if (getNetMode() == DebugConfig.NET_MODE_RELEASE) "http://dzt.zhidaozhixing.com" else "http://front.zdjs-private-test.myghost.zhidaoauto.com"
|
||||
|
||||
internal suspend fun post(map: Map<String, String>): Response<PostResult> {
|
||||
return RetrofitFactory.getInstance(getHost()).create(BadCaseApi::class.java).post(map)
|
||||
}
|
||||
|
||||
private suspend fun get(): Response<BadCaseEntity>? {
|
||||
return try { RetrofitFactory.getInstance(getHost()).create(BadCaseApi::class.java).get() } catch (t: Throwable) { t.printStackTrace(); null}
|
||||
}
|
||||
|
||||
private suspend fun updateCache(entity: BadCaseEntity) = suspendCancellableCoroutine<Unit> {
|
||||
try {
|
||||
val future = ThreadUtils.getIoPool().submit {
|
||||
try {
|
||||
val gson = GsonUtil.jsonFromObject(entity)
|
||||
Sp.saveBody(gson)
|
||||
it.resumeWith(success(Unit))
|
||||
} catch (t: Throwable) {
|
||||
it.resumeWith(failure(t))
|
||||
}
|
||||
}
|
||||
it.invokeOnCancellation {
|
||||
future.cancel(true)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
it.resumeWith(failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getCache(): BadCaseEntity? = suspendCancellableCoroutine {
|
||||
try {
|
||||
val body = Sp.getBody()
|
||||
if (body != null && body.isNotEmpty()) {
|
||||
val future = ThreadUtils.getIoPool().submit {
|
||||
try {
|
||||
val result = GsonUtil.objectFromJson(body, BadCaseEntity::class.java)
|
||||
it.resumeWith(success(result))
|
||||
} catch (t: Throwable) {
|
||||
it.resumeWith(success(null))
|
||||
}
|
||||
}
|
||||
it.invokeOnCancellation {
|
||||
future.cancel(true)
|
||||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
it.resumeWith(success(null))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuildIn(): BadCaseEntity = BadCaseEntity().also { itx ->
|
||||
val data = mutableListOf<Reason>()
|
||||
data += Reason().also {
|
||||
it.reason = "变道有干扰"
|
||||
}
|
||||
data += Reason().also {
|
||||
it.reason = "遇红绿灯未停车"
|
||||
}
|
||||
data += Reason().also {
|
||||
it.reason = "遇障碍物未停车"
|
||||
}
|
||||
itx.data = data
|
||||
itx.isBuildIn = true
|
||||
}
|
||||
|
||||
internal object Sp {
|
||||
|
||||
private val sp by lazy {
|
||||
Utils.getApp().getSharedPreferences("bad_case_prefs", MODE_PRIVATE)
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
fun saveBody(body: String) {
|
||||
sp.edit().putString("prefs", body).commit()
|
||||
}
|
||||
|
||||
fun getBody(): String? {
|
||||
return sp.getString("prefs", null)
|
||||
}
|
||||
}
|
||||
|
||||
class AutoPilotBadCaseView: ConstraintLayout {
|
||||
|
||||
private var dismiss: OnDismissCallback? = null
|
||||
private var select: OnSelectCallback? = null
|
||||
private var cases: List<Reason>? = null
|
||||
|
||||
private var selectCase: Reason? = null
|
||||
|
||||
private val scope = CoroutineScope(Handler().asCoroutineDispatcher() + SupervisorJob())
|
||||
|
||||
constructor(context: Context) : this(context, null)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
LayoutInflater.from(context).inflate(R.layout.layout_badcase_collect, this, true)
|
||||
background = ColorDrawable(Color.parseColor("#F0151D41"))
|
||||
isClickable = true
|
||||
layoutParams = ViewGroup.LayoutParams(960.toPixels().toInt(), 1528.toPixels().toInt())
|
||||
|
||||
close?.onClick {
|
||||
dismiss?.invoke()
|
||||
}
|
||||
cancel?.also {
|
||||
it.background = shape(solid = Color.parseColor("#3B4577"), radius = 16)
|
||||
it.onClick {
|
||||
dismiss?.invoke()
|
||||
}
|
||||
}
|
||||
ok?.also {
|
||||
val enabled = gradient(radius = 16.toPixels().toInt(), orientation = GradientDrawable.Orientation.LEFT_RIGHT, centerX = 0.06f, startColor = Color.rgb(35, 146, 252), endColor = Color.rgb(28, 75, 252))
|
||||
val disabled = gradient(radius = 16.toPixels().toInt(), orientation = GradientDrawable.Orientation.LEFT_RIGHT, centerX = 0.06f, startColor = Color.rgb(24, 71, 129), endColor = Color.rgb(21, 46, 129))
|
||||
it.background = object : StateListDrawable() {}.also { itx ->
|
||||
itx.addState(intArrayOf(android.R.attr.state_enabled), enabled)
|
||||
itx.addState(StateSet.WILD_CARD, disabled)
|
||||
}
|
||||
it.onClick {
|
||||
val case = selectCase
|
||||
if (case != null) {
|
||||
select?.invoke(case)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
val adapter = rv_take_over?.adapter
|
||||
if (adapter != null && adapter.itemCount > 0) {
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
showLoading()
|
||||
try {
|
||||
get()?.takeIf { it.isSuccessful && it.body() != null && it.body()?.code == 200 }?.let {
|
||||
val entity = it.body()!!
|
||||
try {
|
||||
updateCache(entity)
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
} finally {
|
||||
updateBadCaseList(entity)
|
||||
}
|
||||
}
|
||||
?:
|
||||
getCache()?.also {
|
||||
updateBadCaseList(it)
|
||||
}
|
||||
?:
|
||||
updateBadCaseList(getBuildIn())
|
||||
} finally {
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBadCaseList(body: BadCaseEntity) {
|
||||
cases = body.data
|
||||
rv_take_over?.let {
|
||||
it.layoutManager = LinearLayoutManager(it.context, LinearLayoutManager.VERTICAL, false)
|
||||
it.adapter = object : RecyclerView.Adapter<BadCaseViewHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BadCaseViewHolder = BadCaseViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.layout_badcase_item, parent, false))
|
||||
override fun onBindViewHolder(holder: BadCaseViewHolder, position: Int) {
|
||||
val cases = cases
|
||||
if (cases == null || cases.isEmpty()) {
|
||||
return
|
||||
}
|
||||
if (position >= cases.size) {
|
||||
return
|
||||
}
|
||||
val case = cases[position]
|
||||
holder.bindData(case)
|
||||
}
|
||||
override fun getItemCount(): Int = cases?.size ?: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
scope.cancel("Cancel all for AutoPilotBadCaseView#onDetachedFromWindow")
|
||||
}
|
||||
|
||||
private fun showLoading() {
|
||||
pb?.let {
|
||||
it.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideLoading() {
|
||||
pb?.let {
|
||||
it.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private inner class BadCaseViewHolder(item: View) : RecyclerView.ViewHolder(item) {
|
||||
|
||||
private val check: ImageView = item.findViewById(R.id.check)
|
||||
private val reason: TextView = item.findViewById(R.id.reason)
|
||||
|
||||
init {
|
||||
check.background = StateListDrawable().also {
|
||||
it.addState(intArrayOf(android.R.attr.state_selected), ContextCompat.getDrawable(itemView.context, R.drawable.icon_ap_badcase_check))
|
||||
it.addState(StateSet.WILD_CARD, ContextCompat.getDrawable(itemView.context, R.drawable.icon_ap_badcase_default))
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun bindData(case: Reason) {
|
||||
check.isSelected = case.isChecked
|
||||
reason.text = case.reason ?: ""
|
||||
if (case.isChecked) {
|
||||
ok?.isSelected = true
|
||||
}
|
||||
itemView.onClick {
|
||||
case.isChecked = !case.isChecked
|
||||
selectCase = case
|
||||
cancelOtherChecked(case)
|
||||
ok?.isEnabled = hasCheckedItem()
|
||||
rv_take_over?.adapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasCheckedItem(): Boolean = cases?.find { it.isChecked } != null
|
||||
|
||||
private fun cancelOtherChecked(case: Reason) {
|
||||
val cases = cases
|
||||
if (cases == null || cases.isEmpty()) {
|
||||
return
|
||||
}
|
||||
cases.filterNot { it == case }.forEach {
|
||||
it.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onDismiss(dismiss:() -> Unit) {
|
||||
this.dismiss = dismiss
|
||||
}
|
||||
|
||||
fun onSelect(cb:(Reason) -> Unit) {
|
||||
this.select = cb
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -97,6 +97,18 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/viewPerspectiveSwitch"
|
||||
app:layout_goneMarginStart="50px" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/vs_bad_case_entrance"
|
||||
android:inflatedId="@+id/bad_case_entrance"
|
||||
android:layout_width="@dimen/module_hmi_check_size"
|
||||
android:layout_height="@dimen/module_hmi_check_size"
|
||||
android:layout_marginStart="25px"
|
||||
android:layout_marginBottom="40px"
|
||||
android:layout="@layout/view_ap_badcase_entrance"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/ivToolsIcon"
|
||||
app:layout_goneMarginStart="50px"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/viewUpgradeTips"
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge 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"
|
||||
tools:background="#151D41F0"
|
||||
tools:layout_height="match_parent"
|
||||
tools:layout_width="match_parent"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/close"
|
||||
android:layout_width="107px"
|
||||
android:layout_height="107px"
|
||||
android:layout_marginTop="66px"
|
||||
android:layout_marginEnd="40px"
|
||||
android:src="@drawable/icon_close_nor"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/blue_block"
|
||||
android:layout_width="14px"
|
||||
android:layout_height="50px"
|
||||
android:layout_marginStart="80px"
|
||||
android:layout_marginTop="92px"
|
||||
android:background="#2966EC"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="19px"
|
||||
android:text="请选择本次接管原因"
|
||||
android:textColor="#ffffff"
|
||||
android:textSize="42px"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/blue_block"
|
||||
app:layout_constraintStart_toEndOf="@+id/blue_block"
|
||||
app:layout_constraintTop_toTopOf="@+id/blue_block" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time_of_take_over"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="113px"
|
||||
android:layout_marginTop="10px"
|
||||
android:textColor="#A7B6F0"
|
||||
android:textSize="32px"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/blue_block"
|
||||
tools:text="接管时间:2021.12.21 14:32" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_take_over"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="72px"
|
||||
android:overScrollMode="never"
|
||||
app:layout_constraintBottom_toTopOf="@+id/ok"
|
||||
android:layout_marginBottom="20px"
|
||||
app:layout_constraintTop_toBottomOf="@+id/time_of_take_over" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ok"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="123px"
|
||||
android:paddingTop="31px"
|
||||
android:paddingEnd="123px"
|
||||
android:paddingBottom="31px"
|
||||
android:text="确认"
|
||||
android:textColor="#ffffff"
|
||||
android:textSize="42px"
|
||||
android:layout_marginStart="100px"
|
||||
android:layout_marginEnd="100px"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/cancel"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:enabled="false"
|
||||
android:layout_marginBottom="100px"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="123px"
|
||||
android:paddingTop="31px"
|
||||
android:paddingEnd="123px"
|
||||
android:paddingBottom="31px"
|
||||
android:text="取消"
|
||||
android:textColor="#ffffff"
|
||||
android:textSize="42px"
|
||||
android:layout_marginEnd="100px"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/ok"
|
||||
android:layout_marginBottom="100px"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pb"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@android:style/Widget.Holo.ProgressBar.Large"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
</merge>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/dp_10"
|
||||
android:paddingTop="@dimen/dp_10"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/check"
|
||||
android:layout_width="70px"
|
||||
android:layout_height="70px"
|
||||
android:layout_marginStart="113px"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/reason"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#ffffff"
|
||||
android:layout_marginStart="30px"
|
||||
android:layout_marginEnd="20px"
|
||||
android:textSize="42px"
|
||||
android:lines="1"
|
||||
android:ellipsize="end"
|
||||
tools:text="转弯过于靠近路侧转弯过于靠近路侧转弯过于靠近路侧转弯过于靠近路侧转弯过于靠近路侧转弯过于靠近路侧转弯过于靠近路侧转弯过于靠近路侧"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ImageView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="@dimen/dp_10"
|
||||
android:scaleType="center"
|
||||
tools:layout_width="@dimen/module_hmi_check_size"
|
||||
tools:layout_height="@dimen/module_hmi_check_size"
|
||||
android:background="@drawable/icon_car_ap_badcase_entrance" />
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="autopilot_badcase_record" type="id" />
|
||||
</resources>
|
||||
Reference in New Issue
Block a user