[BadCase]代码提交

This commit is contained in:
renwenjie
2022-01-19 15:54:30 +08:00
parent 63b99ee896
commit 48d9baa3bb
29 changed files with 1304 additions and 107 deletions

View File

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

View File

@@ -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浮窗和系统浮窗如若系统浮窗无权限先进行权限申请
*/

View File

@@ -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)) {

View File

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

View File

@@ -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.SECONDS.toMillis(10)
}
@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, "${TimeUnit.MILLISECONDS.toMinutes(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)
}

View File

@@ -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" */"http://front.zdjs-private-test.myghost.zhidaoauto.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

View File

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

View File

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

View File

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

View File

@@ -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" />

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="autopilot_badcase_record" type="id" />
</resources>