This commit is contained in:
zhongchao
2022-03-11 19:41:19 +08:00
parent 9275ed5ff2
commit 180bdfd50a
35 changed files with 1260 additions and 834 deletions

View File

@@ -38,6 +38,10 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {

View File

@@ -2,21 +2,14 @@ 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.ViewGroup
import android.view.WindowManager
import android.view.animation.OvershootInterpolator
import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.lifecycleScope
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
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.AdUpgradeStateHelper
@@ -30,9 +23,9 @@ 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.devatools.CallerDevaToolsManager
import com.mogo.eagle.core.function.call.map.CallerMapDataCollectorManager
import com.mogo.eagle.core.function.call.monitor.CallerMonitorManager
import com.mogo.eagle.core.function.hmi.R
@@ -45,23 +38,15 @@ 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.Repository
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.lifecycleOwner
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.eagle.core.utilcode.util.Utils
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
@@ -94,132 +79,8 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
// 检测、自动驾驶速度设置
private var toolsViewFloat: WarningFloat.Builder? = null
@Volatile
private var autoPilotBadCaseEntrance: View? = null
private var autoPilotBadCaseView: AutoPilotBadCaseView? = null
private var onBadCaseShow: (() -> View)? = null
private var onBadCaseHide: (() -> Unit)? = null
private var upgradeTipsView: (() -> View)? = null
companion object {
private const val MSG_WHAT_DISMISS_BAD_CASE_ENTRY = 0x1010
private val CASE_EXPIRE_DURATION = 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) {
Log.d("QQQ", "-- step -- 1 --")
var oldT = try {
old?.timestamp?.takeIf { it.isNotBlank() }?.let {
SimpleDateFormat("yyyyMMddHHmmss", 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("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.time
?: 0L
} ?: 0L
} catch (t: Throwable) {
t.printStackTrace()
0L
}
if (oldT == 0L || (newT > 0L && (newT - oldT > 0L) && (newT - oldT) < CASE_EXPIRE_DURATION)) {
Log.d("QQQ", "-- step -- 2 --")
record?.takeIf { it.key != old?.key && it.timestamp != old?.timestamp }?.also {
Log.d("QQQ", "record: [$record] is displaying and consuming ~~~")
showBadCaseEntrance(it)
}
continue
}
while (oldT != 0L && newT != 0L && (newT - oldT) >= CASE_EXPIRE_DURATION) {
Log.d("QQQ", "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("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.time
?: 0L
} ?: 0L
} catch (t: Throwable) {
t.printStackTrace()
0L
}
}
record?.takeIf { it.key != old?.key && it.timestamp != old?.timestamp }?.also {
Log.d("QQQ", "record: [$record] is displaying for rest ...")
showBadCaseEntrance(it)
}
} else {
withContext(Dispatchers.Main) {
entrance.takeIf { it.visibility != View.VISIBLE }?.also {
(entrance.parent as? ViewGroup)?.let { g ->
TransitionManager.beginDelayedTransition(g, AutoTransition())
}
it.visibility = View.VISIBLE
}
}
Log.d("QQQ", "record: [$old] hasn't been consumed~~~~")
}
} finally {
delay(1000)
}
}
}
}
}
}
private val handler by lazy {
Handler(Handler.Callback { it ->
if (it.what == MSG_WHAT_DISMISS_BAD_CASE_ENTRY) {
val entrance = autoPilotBadCaseEntrance
if (entrance != null && entrance.visibility == View.VISIBLE) {
val record = entrance.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult
record?.consumed = true
record?.let { itx ->
lifecycleScope.launch(Dispatchers.IO) {
try {
val i = Repository.dao().deleteRecord(itx)
Log.d("QQQ", "delete result: $i")
} catch (t: Throwable) {
Log.d("QQQ", "---- delete error: ${t.message}")
}
}
}
dismissBadCaseFloatView()
if (entrance.visibility != View.GONE) {
(entrance.parent as? ViewGroup)?.let { g ->
TransitionManager.beginDelayedTransition(g, AutoTransition())
}
entrance.visibility = View.GONE
onBadCaseHide?.invoke()
}
}
return@Callback true
}
return@Callback false
})
}
override fun vipIdentification(visible: Boolean) {
ThreadUtils.runOnUiThread {
Logger.d(TAG, "vipIdentification")
@@ -260,18 +121,6 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
CallerAutopilotIdentifyListenerManager.addListener(TAG, this)
lifecycleScope.launchWhenResumed {
withContext(Dispatchers.IO) {
val dao = Repository.dao()
try {
dao.getAllUnConsumedRecords()?.forEach {
channel.send(it)
}
} catch (t: Throwable) {
t.printStackTrace()
}
}
}
/*// TODO 这里后面需要改成独立进程通讯后台获取YUV
view.postDelayed({
@@ -282,21 +131,8 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
@OptIn(ExperimentalCoroutinesApi::class)
override fun onAutopilotRecordResult(record: AutoPilotRecordResult?) {
record ?: return
Log.d("QQQ", "onAutopilotRecordResult:$record")
if (record.type == 1 && record.stat == 100) {
lifecycleScope.launchWhenResumed {
withContext(Dispatchers.IO) {
val dao = Repository.dao()
try {
dao.insertRecord(record)
} catch (t: Throwable) {
t.printStackTrace()
}
record.also {
channel.send(it)
}
}
}
if (HmiBuildConfig.isShowBadCaseView && record.type == 1 && record.stat == 100) {
CallerDevaToolsManager.onReceiveBadCaseRecord(record)
}
if (record.type == 2 && (record.stat == 101 || record.stat == 100)) {
CallerMapDataCollectorManager.finish(record.id, record.stat, "", record.fileName ?: "", record.note ?: "")
@@ -308,61 +144,6 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
CallerAutopilotIdentifyListenerManager.removeListener(TAG)
}
private fun showBadCaseEntrance(record: AutoPilotRecordResult) {
Log.d("QQQ", "showBadCaseEntrance:$record")
lifecycleScope.launch {
if (HmiBuildConfig.isShowBadCaseView) {
if (vsBadCaseToolsView?.parent != null) {
val inflateView = vsBadCaseToolsView.inflate()
autoPilotBadCaseEntrance = inflateView
}
}
val entrance = autoPilotBadCaseEntrance
Log.d("QQQ", "show --- 1 ----")
if (entrance != null) {
if (entrance.visibility != View.VISIBLE) {
(entrance.parent as? ViewGroup)?.let { g ->
TransitionManager.beginDelayedTransition(g, AutoTransition())
}
entrance.visibility = View.VISIBLE
}
entrance.setTag(R.id.autopilot_badcase_record, record)
entrance.onClick {
showBadCasesFloat {
if (it.visibility != View.GONE) {
(entrance.parent as? ViewGroup)?.let { g ->
TransitionManager.beginDelayedTransition(g, AutoTransition())
}
it.visibility = View.GONE
onBadCaseHide?.invoke()
}
}
}
dismissBadCaseEntryAfterSomeTime()
} else {
val view = onBadCaseShow?.invoke()
if (view != null) {
view.lifecycleOwner.lifecycle.addObserver(badCaseEntranceObserver)
view.visibility = View.GONE
autoPilotBadCaseEntrance = view
showBadCaseEntrance(record)
}
}
}
}
private val badCaseEntranceObserver = LifecycleEventObserver { _, event ->
if (event == ON_DESTROY) {
onBadCaseShow = null
onBadCaseHide = null
autoPilotBadCaseEntrance = null
}
}
override fun registerBadCaseCallback(onShow: () -> View, onHide: (() -> Unit)?) {
onBadCaseShow = onShow
onBadCaseHide = onHide
}
/**
*注册工控机升级提示圆点View的回调
@@ -391,116 +172,6 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
}
}
private fun showBadCasesFloat(dismiss: (() -> Unit)?) {
Log.d("QQQ", "showBadCaseToolsFloat")
context?.let { it ->
if (autoPilotToolsFloat == null) {
if (autoPilotBadCaseView == null) {
autoPilotBadCaseView = AutoPilotBadCaseView(it).also { itx ->
val record =
autoPilotBadCaseEntrance?.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult
itx.tag = record
itx.onDismiss {
dismissBadCaseFloatView()
}
itx.onSelect {
lifecycleScope.launch {
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["duration"] = record?.duration?.toInt()?.toString()
?: ""
params["timestamp"] = record?.timestamp ?: ""
}
val response = post(params)
if (response.isSuccessful) {
val body = response.body()
if (body == null) {
Log.e("QQQ", "返回的body是空的~~~")
return@launch
}
if (body.code == 200) {
Logger.i(TAG, "ok:${body}")
dismissBadCaseFloatView()
dismiss?.invoke()
CallerAutoPilotManager.recordCause(
record?.key,
record?.fileName,
it.id, it.reason)
ToastUtils.showShort("接管反馈成功~")
record?.also {
it.consumed = true
withContext(Dispatchers.IO) {
try {
Repository.dao().deleteRecord(record)
} catch (t: Throwable) {
Log.d("QQQ", "---- delete error 2: ${t.message}")
}
}
}
return@launch
}
Log.e("QQQ", "fail:${body}")
}
} catch (t: Throwable) {
t.printStackTrace()
ToastUtils.showShort("网络请求失败,请尝试联网~")
Log.e("QQQ", "exception:${t.message}")
}
}
}
}
}
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, CASE_EXPIRE_DURATION)
}
private fun showToolsFloat() {
Logger.d(TAG, "showToolsFloat")
@@ -597,6 +268,10 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
} else {
setToolsViewVisibility(View.GONE)
}
if (HmiBuildConfig.isShowBadCaseView) {
CallerDevaToolsManager.initBadCase(vsBadCaseToolsView)
}
}
override fun getLayoutId(): Int {
@@ -1125,18 +800,54 @@ class MoGoHmiFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>
}
private fun dismissBadCaseFloatView() {
autoPilotToolsFloat?.let {
WarningFloat.dismiss(it.config.floatTag, false)
autoPilotToolsFloat = null
autoPilotBadCaseView = null
override fun showBadCaseFloat(tag: String, floatView: View): () -> Unit {
WarningFloat.with(context ?: Utils.getApp())
.setTag(tag)
.setLayout(floatView)
.setSidePattern(SidePattern.LEFT)
.setGravity(Gravity.START, 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
}
})
.also {
autoPilotToolsFloat = it
}
.show()
return {
autoPilotToolsFloat?.let {
WarningFloat.dismiss(it.config.floatTag, false)
autoPilotToolsFloat = null
}
}
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy")
handler.removeCallbacksAndMessages(null)
}

View File

@@ -1,404 +0,0 @@
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 androidx.room.*
import com.google.gson.annotations.Expose
import com.mogo.commons.debug.DebugConfig
import com.mogo.commons.debug.DebugConfig.getNetMode
import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult
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.MoGoRetrofitFactory
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 java.text.SimpleDateFormat
import java.util.*
import kotlin.Result.Companion.failure
import kotlin.Result.Companion.success
private typealias OnDismissCallback = () -> Unit
private typealias OnSelectCallback = (Reason) -> Unit
object Repository {
fun dao(): Dao {
return Room.databaseBuilder(Utils.getApp(), RecordDb::class.java, "bad-cases").build().dao()
}
@Database(entities = [
AutoPilotRecordResult::class
],
version = 1,
exportSchema = false)
abstract class RecordDb : RoomDatabase() {
abstract fun dao(): Dao
}
@androidx.room.Dao
interface Dao {
@Transaction
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertRecord(record: AutoPilotRecordResult): Long
@Transaction
@Delete
suspend fun deleteRecord(record: AutoPilotRecordResult): Int
@Query("SELECT * FROM record ORDER BY timestamp ASC")
suspend fun getAllUnConsumedRecords(): List<AutoPilotRecordResult>?
}
}
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 MoGoRetrofitFactory.getInstance(getHost()).create(BadCaseApi::class.java).post(map)
}
private suspend fun get(): Response<BadCaseEntity>? {
return try { MoGoRetrofitFactory.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.id = "1"
it.reason = "变道有干扰"
}
data += Reason().also {
it.id = "2"
it.reason = "遇红绿灯未停车"
}
data += Reason().also {
it.id = "3"
it.reason = "遇障碍物未停车"
}
data += Reason().also {
it.id = "4"
it.reason = "无法绕行"
}
data += Reason().also {
it.id = "5"
it.reason = "画龙"
}
data += Reason().also {
it.id = "6"
it.reason = "转弯过于靠近路侧"
}
data += Reason().also {
it.id = "7"
it.reason = "无故退出自动驾驶"
}
data += Reason().also {
it.id = "8"
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
}
time_of_take_over?.text = "接管时间: ${(tag as? AutoPilotRecordResult)?.timestamp?.let {
try {
SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.let { itx ->
SimpleDateFormat("yyyy.MM.dd HH:mm", Locale.getDefault()).format(itx)
}
} catch (e: Throwable) {
null
} ?: SimpleDateFormat("yyyy.MM.dd HH:mm", Locale.getDefault()).format(Date())}}"
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.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -98,14 +98,14 @@
app:layout_constraintStart_toEndOf="@+id/viewPerspectiveSwitch"
app:layout_goneMarginStart="50px" />
<ViewStub
<ImageView
android:id="@+id/vsBadCaseToolsView"
android:inflatedId="@+id/badCaseToolsView"
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"
android:visibility="gone"
android:background="@drawable/icon_car_ap_badcase_entrance"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivToolsIcon"
app:layout_goneMarginStart="50px"/>

View File

@@ -1,111 +0,0 @@
<?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="150px"/>
<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="150px"/>
<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

@@ -1,28 +0,0 @@
<?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>