[FeedBack]反馈代码提交

[feedback]优化ui
This commit is contained in:
renwj
2022-03-17 14:58:59 +08:00
parent 11c6f2cf2b
commit 639950ccc5
50 changed files with 1334 additions and 149 deletions

View File

@@ -82,6 +82,7 @@ dependencies {
implementation rootProject.ext.dependencies.androidxappcompat
implementation rootProject.ext.dependencies.androidxconstraintlayout
implementation rootProject.ext.dependencies.androidxrecyclerview
implementation rootProject.ext.dependencies.flexbox
if (Boolean.valueOf(USE_MAVEN_PACKAGE)) {
implementation rootProject.ext.dependencies.mogoserviceapi

View File

@@ -8,6 +8,7 @@ import com.mogo.eagle.core.data.chain.ChainLogParam
import com.mogo.eagle.core.data.constants.MogoServicePaths
import com.mogo.eagle.core.function.api.devatools.IDevaToolsProvider
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
import com.zhjt.mogo_core_function_devatools.feedback.FeedbackManager
import com.zhjt.mogo_core_function_devatools.logcatch.MogoLogCatchManager
import com.zhjt.mogo_core_function_devatools.trace.TraceManager.Companion.traceManager
import record_cache.RecordPanelOuterClass
@@ -56,8 +57,11 @@ class DevaToolsProvider : IDevaToolsProvider {
BadCaseManager.onReceiveBadCaseRecord(record)
}
override fun showFeedbackWindow(ctx: Context) {
FeedbackManager.showFeedbackWindow(ctx)
}
override fun onDestroy() {
MogoLogCatchManager.onDestroy()
}
}

View File

@@ -4,6 +4,7 @@ import android.transition.AutoTransition
import android.transition.TransitionManager
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.lifecycle.Lifecycle.Event
import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
import androidx.lifecycle.LifecycleCoroutineScope
@@ -16,9 +17,10 @@ import com.mogo.eagle.core.function.call.hmi.CallerHmiManager
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.kotlin.lifecycleOwner
import com.mogo.eagle.core.utilcode.kotlin.onClick
import com.mogo.eagle.core.utilcode.kotlin.toast
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.mogo.eagle.core.utilcode.util.Utils
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.biz.BadCasePresenter
import com.zhjt.mogo_core_function_devatools.badcase.biz.BadCaseView
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
@@ -36,8 +38,7 @@ internal object BadCaseManager : LifecycleEventObserver {
* 超过此时间case入口自动消失
*/
private val CASE_EXPIRE_DURATION: Long =
TimeUnit.HOURS.toMillis(4)/* TimeUnit.SECONDS.toMillis(10) */
private val CASE_EXPIRE_DURATION: Long = TimeUnit.HOURS.toMillis(4)/* TimeUnit.SECONDS.toMillis(10) */
private var onShow: (() -> Unit)? = null
private var onHide: (() -> Unit)? = null
@@ -190,20 +191,20 @@ internal object BadCaseManager : LifecycleEventObserver {
hideFloat = null
},
onSelect = { reason ->
val uploadResult =
presenter.upload(mutableMapOf<String, String>().also { itx ->
itx["carLicense"] = MoGoAiCloudClientConfig.getInstance().sn
itx["filename"] = record.fileName ?: ""
itx["filesize"] = record.total.toString()
itx["key"] = record.key ?: ""
itx["reason"] = reason.reason ?: ""
itx["duration"] = record.duration.toInt().toString()
itx["timestamp"] = record.timestamp
})
val uploadResult = presenter.upload(mutableMapOf<String, String>().also { itx ->
itx["carLicense"] = MoGoAiCloudClientConfig.getInstance().sn
itx["filename"] = record.fileName ?: ""
itx["filesize"] = record.total.toString()
itx["key"] = record.key ?: ""
itx["reason"] = reason.reason ?: ""
itx["duration"] = record.duration.toInt().toString()
itx["timestamp"] = record.timestamp
itx["channel"] = "0"
})
if (uploadResult == null || uploadResult.code != 200) {
ToastUtils.showShort("接管反馈失败")
it.context.toast("上报失败")
} else {
ToastUtils.showShort("接管反馈成功")
it.context.toast("上报成功")
record.consumed = true
withContext(Dispatchers.IO) {
presenter.deleteRecord(record)
@@ -246,14 +247,11 @@ internal object BadCaseManager : LifecycleEventObserver {
}
}
private fun showBadCaseFloat(
onDismiss: () -> Unit,
onSelect: suspend (reason: Reason) -> Unit
) {
private fun showBadCaseFloat(onDismiss: () -> Unit, onSelect:suspend (reason: Reason) -> Unit) {
val context = viewHolder?.get()?.context ?: Utils.getApp()
BadCaseView(context).also {
it.register(record, onDismiss, onSelect)
hideFloat = CallerHmiManager.showBadCaseFloat(floatView = it)
BadCaseView(context).also { itx ->
itx.register(record, onDismiss, onSelect)
hideFloat = CallerHmiManager.showFloatWindow("BadCaseFloat", floatView = itx, WindowManager.LayoutParams().also { it.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE})
}
}

View File

@@ -1,28 +0,0 @@
package com.zhjt.mogo_core_function_devatools.badcase.api.entity
import androidx.annotation.Keep
import com.google.gson.annotations.Expose
@Keep
internal class BadCaseResponse {
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
}
}

View File

@@ -2,7 +2,7 @@ package com.zhjt.mogo_core_function_devatools.badcase.biz
import android.util.Log
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.repository.Repository
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
@@ -12,7 +12,7 @@ internal class BadCasePresenter: IBadCasePresenter {
Repository()
}
override suspend fun loadBadCases() = repository.loadBadCases()
override suspend fun loadBadCases(isDriven: Boolean) = repository.loadBadCases(isDriven)
override suspend fun insertRecord(record: AutoPilotRecord) {
try {
@@ -49,4 +49,8 @@ internal class BadCasePresenter: IBadCasePresenter {
Log.d(BadCaseManager.TAG, " --- 2 ----")
return repository.getLastModified()
}
override suspend fun getTaskId(): Int {
return repository.getTaskId()
}
}

View File

@@ -21,7 +21,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.mogo.eagle.core.utilcode.kotlin.*
import com.mogo.eagle.core.utilcode.rv.divider.CommonDividerItemDecoration
import com.zhjt.mogo_core_function_devatools.R
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
import kotlinx.android.synthetic.main.layout_badcase_collect.view.*
import kotlinx.coroutines.launch
@@ -58,7 +58,7 @@ internal class BadCaseView: ConstraintLayout {
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())
layoutParams = ViewGroup.LayoutParams(960.PX, 1528.PX)
close?.onClick {
onDismiss?.invoke()
}
@@ -69,8 +69,8 @@ internal class BadCaseView: ConstraintLayout {
}
}
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))
val enabled = gradient(radius = 16.PX, 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.PX, 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)
@@ -86,7 +86,7 @@ internal class BadCaseView: ConstraintLayout {
scope.launchWhenCreated {
time_of_take_over?.text = "接管时间: ${SimpleDateFormat("yyyy.MM.dd HH:mm", Locale.getDefault()).format(record?.toLongTime() ?: System.currentTimeMillis())}"
showLoading()
presenter.loadBadCases().also {
presenter.loadBadCases(true).also {
cases = it
refresh(it)
}
@@ -101,7 +101,7 @@ internal class BadCaseView: ConstraintLayout {
it.addItemDecoration(
CommonDividerItemDecoration
.Builder()
.verticalInnerSpace(50.toPixels().toInt())
.verticalInnerSpace(50.PX)
.build())
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))

View File

@@ -1,13 +1,13 @@
package com.zhjt.mogo_core_function_devatools.badcase.biz
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
internal interface IBadCasePresenter {
suspend fun loadBadCases(): List<BadCaseResponse.Reason>
suspend fun loadBadCases(isDriven: Boolean): List<Reason>
suspend fun updateLastModified(timestamp: Long)
@@ -20,4 +20,6 @@ internal interface IBadCasePresenter {
suspend fun getUnConsumedRecords(): List<AutoPilotRecord>
suspend fun deleteRecord(record: AutoPilotRecord)
suspend fun getTaskId(): Int
}

View File

@@ -3,8 +3,8 @@ package com.zhjt.mogo_core_function_devatools.badcase.repository
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_DEVA
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.BadCaseDbModel
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.BadCaseNetModel
@@ -25,8 +25,8 @@ internal class Repository {
BadCaseStore
}
suspend fun loadBadCases(): List<Reason> {
return net.get()?.data?.takeIf { it.isNotEmpty() }?.also { store.updateRecords(it) } ?: store.records().takeIf { it.isNotEmpty() } ?: getBuildIn()
suspend fun loadBadCases(isDriven: Boolean): List<Reason> {
return net.get()?.data?.takeIf { it.isNotEmpty() }?.also { store.updateRecords(it, isDriven) } ?: store.records(isDriven).takeIf { it.isNotEmpty() } ?: getBuildIn(isDriven)
}
suspend fun uploadLastModified(timestamp: Long) {
@@ -38,40 +38,44 @@ internal class Repository {
return store.getLastModified()
}
private fun getBuildIn(): List<Reason> {
private fun getBuildIn(isDriven: Boolean): List<Reason> {
CallerLogger.d("$M_DEVA${BadCaseManager.TAG}", "-- load cases from buildin -- 1 --")
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 = "其它"
if (isDriven) {
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 = "其它"
}
} else {
TODO()
}
return data
}
@@ -91,4 +95,8 @@ internal class Repository {
suspend fun getAllUnConsumedRecord(): List<AutoPilotRecord>? {
return db.dao().getAllUnConsumedRecords()
}
suspend fun getTaskId(): Int {
return store.getTaskIdAndIncrement()
}
}

View File

@@ -4,9 +4,9 @@ import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.network.MoGoRetrofitFactory
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
import com.zhjt.mogo_core_function_devatools.badcase.api.BadCaseApi
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.BadCaseApi
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.consts.BadCaseHost
internal class BadCaseNetModel {

View File

@@ -1,7 +1,7 @@
package com.zhjt.mogo_core_function_devatools.badcase.api
package com.zhjt.mogo_core_function_devatools.badcase.repository.net.api
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.UploadResult
import retrofit2.Response
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded

View File

@@ -0,0 +1,45 @@
package com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity
import androidx.annotation.Keep
import com.google.gson.annotations.Expose
@Keep
internal class BadCaseResponse {
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
override fun equals(other: Any?): Boolean {
if (javaClass != other?.javaClass) return false
other as Reason
if (id != other.id) return false
if (reason != other.reason) return false
if (isChecked != other.isChecked) return false
return true
}
override fun hashCode(): Int {
var result = id?.hashCode() ?: 0
result = 31 * result + (reason?.hashCode() ?: 0)
result = 31 * result + isChecked.hashCode()
return result
}
}
}

View File

@@ -1,4 +1,4 @@
package com.zhjt.mogo_core_function_devatools.badcase.api.entity
package com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity
import androidx.annotation.Keep

View File

@@ -7,7 +7,7 @@ import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.util.Utils
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
import com.zhjt.mogo_core_function_devatools.badcase.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.generated.BadCauses
import com.zhjt.mogo_core_function_devatools.badcase.generated.Cause
import kotlinx.coroutines.FlowPreview
@@ -55,15 +55,10 @@ internal object BadCaseStore {
private val store: DataStore<BadCauses> by lazy {
DataStoreFactory.create(serializer = serializer) {
File(
Utils.getApp().filesDir,
"bad_cases.pb"
)
}
DataStoreFactory.create(serializer = serializer) { File(Utils.getApp().filesDir, "bad_cases.pb") }
}
suspend fun updateRecords(reasons: List<Reason>): BadCauses {
suspend fun updateRecords(reasons: List<Reason>, isDriven: Boolean): BadCauses {
CallerLogger.d("$M_DEVA${BadCaseManager.TAG}", "--- updateRecords ---")
val data = mutableListOf<Cause>()
reasons.forEach { itx ->
@@ -74,7 +69,11 @@ internal object BadCaseStore {
}
}
return store.updateData { itx ->
itx.toBuilder().clearData().addAllData(data).build()
if (isDriven) {
itx.toBuilder().clearDrivenData().addAllDrivenData(data).build()
} else {
itx.toBuilder().clearDrivingData().addAllDrivingData(data).build()
}
}
}
@@ -101,10 +100,10 @@ internal object BadCaseStore {
}
@OptIn(FlowPreview::class)
suspend fun records(): List<Reason> {
CallerLogger.d("$M_DEVA${BadCaseManager.TAG}", "-- load cases from pb -- 1 -- ")
suspend fun records(isDriven: Boolean): List<Reason> {
val causes = store.data.firstOrNull()
return causes?.dataList?.map {
val list = if (isDriven) causes?.drivenDataList else causes?.drivingDataList
return list?.map {
Reason().also { itx ->
itx.id = it.id
itx.reason = it.reason
@@ -114,4 +113,18 @@ internal object BadCaseStore {
acc
} ?: emptyList()
}
suspend fun getTaskIdAndIncrement(): Int {
val causes = store.data.firstOrNull()
val ret = causes?.taskId ?: 0
return ret.also {
store.updateData { itx ->
var old = ret
if (old == Int.MAX_VALUE) {
old = 0
}
itx.toBuilder().setTaskId(old + 1).build()
}
}
}
}

View File

@@ -0,0 +1,210 @@
package com.zhjt.mogo_core_function_devatools.feedback
import android.content.Context
import android.text.TextUtils
import android.util.Log
import android.view.WindowManager
import android.widget.TextView
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotIdentifyListener
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.hmi.CallerHmiManager
import com.mogo.eagle.core.utilcode.kotlin.onDetach
import com.mogo.eagle.core.utilcode.kotlin.safeCancel
import com.mogo.eagle.core.utilcode.kotlin.scope
import com.mogo.eagle.core.utilcode.kotlin.toast
import com.mogo.eagle.core.utilcode.mogo.toast.TipToast
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import com.zhjt.mogo_core_function_devatools.R
import com.zhjt.mogo_core_function_devatools.badcase.repository.db.entity.AutoPilotRecord
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.badcase.toRecord
import com.zhjt.mogo_core_function_devatools.feedback.biz.FeedBackView
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Feedback.BadCase
import com.zhjt.mogo_core_function_devatools.feedback.biz.impl.FeedbackPresenter
import com.zhjt.mogo_core_function_devatools.feedback.callback.IFeedbackCallback
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import record_cache.RecordPanelOuterClass
import java.lang.IllegalStateException
import kotlin.Result.Companion
internal object FeedbackManager {
const val TAG = "feedback"
private var hideFloat: (() -> Unit)? = null
private val presenter by lazy { FeedbackPresenter() }
@OptIn(ExperimentalCoroutinesApi::class)
private var autoPilotCallback = Channel<AutoPilotRecord>(Channel.RENDEZVOUS)
@Synchronized
get() = if (field.isClosedForReceive || field.isClosedForSend) {
field = Channel(Channel.RENDEZVOUS)
field
} else field
fun showFeedbackWindow(ctx: Context) {
CallerHmiManager.showFloatWindow("FeedbackWindow", FeedBackView(ctx).also { itx ->
itx.registerCallback(object : IFeedbackCallback {
override fun onClose() {
hideFloat?.invoke()
}
override fun onBadCaseItemClicked(reason: Reason) {
val oldData = itx.adapter.data ?: return
if (reason.isChecked) {
return
}
reason.isChecked = true
val badCase = oldData.firstOrNull() as? BadCase
badCase?.reasons?.filterNot { it.id == reason.id }?.forEach {
it.isChecked = false
}
itx.adapter.notifyItemChanged(0)
}
override fun onStartBadCaseRecord(record: TextView) {
if (!CallerAutoPilotManager.isConnected()) {
TipToast.shortTip("请检查车机与域控制器连接是否正常")
return
}
val data = itx.adapter.data ?: return
val badCase = data.firstOrNull() as? BadCase ?: return
val checked = badCase.reasons.find { it.isChecked }
if (checked == null) {
TipToast.shortTip("请选择一个Case")
return
}
record.scope.launch {
val taskId = presenter.getBadCaseTaskId()
val listener = object : IMoGoAutopilotIdentifyListener {
override fun onAutopilotRecordResult(recordPanel: RecordPanelOuterClass.RecordPanel) {
super.onAutopilotRecordResult(recordPanel)
val newRecord = recordPanel.toRecord() ?: return
Log.d(TAG, "-- 收到工控机录制任务回调 -- $recordPanel")
if (newRecord.type == 1 && newRecord.id == taskId) {
if (newRecord.stat == 100 || newRecord.stat == 101) {
Log.d(TAG, "录制Bag完成, 触发结束录制全量日志 ...")
stopRecordLog(newRecord)
launch {
send(newRecord)
}
}
if (newRecord.stat == 300) {
Log.d(TAG, "录制Bag开始, 触发录制全量日志 ...")
startRecordLog(newRecord)
}
}
}
}
CallerAutopilotIdentifyListenerManager.addListener("Feedback", listener)
record.onDetach {
CallerAutopilotIdentifyListenerManager.removeListener("Feedback")
hideFloat = null
}
record.text = "结束录制"
record.setTag(R.id.feed_back_badcase_tag, 1)
record.setTag(R.id.feed_back_badcase_taskid_tag, taskId)
recordBag(1, taskId, 20)
Log.d(TAG, "延时20秒开始....")
delay(20000) //延时20秒
Log.d(TAG, "延时20秒结束....")
stopRecordBag(1, taskId)
upload(record.context, badCase, checked)
}.also {
record.setTag(R.id.feed_back_badcase_job, it)
}
}
override fun onStopBadCaseRecord(record: TextView) {
val data = itx.adapter.data ?: return
val badCase = data.firstOrNull() as? BadCase ?: return
val checked = badCase.reasons.find { it.isChecked } ?: throw IllegalStateException("这种状态不存在")
val taskId = (record.getTag(R.id.feed_back_badcase_taskid_tag) as? Int) ?: throw IllegalStateException("TaskId 不存在")
val oldJob = record.getTag(R.id.feed_back_badcase_job) as? Job
record.scope.launch {
stopRecordBag(1, taskId)
oldJob?.safeCancel()
upload(record.context, badCase, checked)
}
}
})
}, WindowManager.LayoutParams().also { it.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN }).also { hideFloat = it }
}
private fun startRecordLog(record: AutoPilotRecord) {
//val fileName = record.fileName
//TODO zhongchao 添加全量日志开始录制
}
private fun stopRecordLog(newRecord: AutoPilotRecord) {
//val fileName = record.fileName
//TODO zhongchao 添加全量日志结束录制
}
private suspend fun upload(ctx: Context, badCase: BadCase, checked: Reason) {
val result = receive()
val remark = badCase.remark.text
presenter.upload(mutableMapOf<String, String>().also { itx ->
itx["carLicense"] = MoGoAiCloudClientConfig.getInstance().sn
itx["filename"] = result.fileName ?: ""
itx["filesize"] = result.total.toString()
itx["key"] = result.key ?: ""
itx["reason"] = checked.reason ?: ""
itx["duration"] = result.duration.toInt().toString()
itx["timestamp"] = result.timestamp
itx["channel"] = "1"
if (!TextUtils.isEmpty(remark)) {
itx["remark"] = remark.toString()
}
}).also {
if (it == null || it.code != 200) {
ctx.toast("上报失败")
} else {
ctx.toast("上报成功")
hideFloat?.invoke()
}
}
}
private suspend fun send(record: AutoPilotRecord) = autoPilotCallback.send(record)
private suspend fun receive(): AutoPilotRecord = autoPilotCallback.receive()
private suspend fun recordBag(type: Int, id: Int, duration: Int) = suspendCancellableCoroutine<Unit> {
val future = ThreadUtils.getIoPool().submit {
try {
CallerAutoPilotManager.recordPackage(type, id, duration)
it.resumeWith(Result.success(Unit))
} catch (t: Throwable) {
it.resumeWith(Companion.failure(t))
}
}
it.invokeOnCancellation {
try {
future.cancel(true)
} catch (t: Throwable) {
t.printStackTrace()
}
}
}
private suspend fun stopRecordBag(type: Int, id: Int) = suspendCancellableCoroutine<Unit> {
val future = ThreadUtils.getIoPool().submit {
try {
CallerAutoPilotManager.stopRecord(type, id)
it.resumeWith(Result.success(Unit))
} catch (t: Throwable) {
it.resumeWith(Companion.failure(t))
}
}
it.invokeOnCancellation {
try {
future.cancel(true)
} catch (t: Throwable) {
t.printStackTrace()
}
}
}
}

View File

@@ -0,0 +1,93 @@
package com.zhjt.mogo_core_function_devatools.feedback.biz
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.mogo.eagle.core.utilcode.kotlin.*
import com.zhjt.mogo_core_function_devatools.R
import com.zhjt.mogo_core_function_devatools.feedback.biz.adapter.FeedbackAdapter
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Feedback
import com.zhjt.mogo_core_function_devatools.feedback.biz.impl.FeedbackPresenter
import com.zhjt.mogo_core_function_devatools.feedback.callback.IFeedbackCallback
import kotlinx.android.synthetic.main.layout_fb.view.*
internal class FeedBackView : ConstraintLayout {
private var cb: IFeedbackCallback? = null
private val presenter by lazy {
FeedbackPresenter()
}
private val scope by lazy {
lifecycleOwner.lifecycleScope
}
internal val adapter by lazy {
FeedbackAdapter<Feedback>()
}
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_fb, this, true).also {
observe(arrayOf(ON_DESTROY)) { itx ->
if (itx == ON_DESTROY) {
cb = null
}
}
}
layoutParams = ViewGroup.LayoutParams(960.PX, 1528.PX)
close.onClick {
cb?.onClose()
}
top_mask?.background = gradient(orientation = GradientDrawable.Orientation.TOP_BOTTOM, startColor = Color.parseColor("#151D41"), endColor = Color.parseColor("#05151D41"))
background = ColorDrawable(Color.parseColor("#F0151D41"))
rv?.also {
it.fixGestureConflictForViews(listOf(R.id.et))
it.itemAnimator?.run {
changeDuration = 0
addDuration = 0
moveDuration = 0
removeDuration = 0
}
it.adapter = adapter
it.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
loadFeedbackAndRefresh()
}
}
private fun loadFeedbackAndRefresh() {
scope.launchWhenCreated {
showLoading()
presenter.loadFeedBacks().also {
adapter.data = it
}
hideLoading()
}
}
private fun showLoading() {
pb?.visibility = View.VISIBLE
}
private fun hideLoading() {
pb?.visibility = View.GONE
}
fun registerCallback(cb: IFeedbackCallback) {
this.cb = cb
this.adapter.setCallback(cb)
}
}

View File

@@ -0,0 +1,14 @@
package com.zhjt.mogo_core_function_devatools.feedback.biz
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Feedback
internal interface IFeedbackPresenter {
suspend fun loadFeedBacks(): List<Feedback>
suspend fun getBadCaseTaskId(): Int
suspend fun upload(params: Map<String, String>): UploadResult?
}

View File

@@ -0,0 +1,63 @@
package com.zhjt.mogo_core_function_devatools.feedback.biz.adapter
import android.util.Log
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.zhjt.mogo_core_function_devatools.feedback.FeedbackManager
import com.zhjt.mogo_core_function_devatools.feedback.biz.adapter.vh.BadCaseFBViewHolder
import com.zhjt.mogo_core_function_devatools.feedback.biz.adapter.vh.base.FeedbackViewHolder
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Feedback
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Feedback.BadCase
import com.zhjt.mogo_core_function_devatools.feedback.biz.diff.FeedbackDiffCallback
import com.zhjt.mogo_core_function_devatools.feedback.callback.IFeedbackCallback
internal class FeedbackAdapter<T: Feedback>: RecyclerView.Adapter<FeedbackViewHolder<T>>() {
companion object {
const val ITEM_TYPE_BAD_CASE = 0x0101
}
private var cb: IFeedbackCallback? = null
var data: List<T>? = null
@Synchronized
set(value) {
val result = DiffUtil.calculateDiff(FeedbackDiffCallback(field, value))
result.dispatchUpdatesTo(this)
field = value
}
override fun getItemViewType(position: Int): Int {
val data = data ?: return super.getItemViewType(position)
when(val item = data[position]) {
is BadCase -> {
Log.d(FeedbackManager.TAG, "item->$item")
return ITEM_TYPE_BAD_CASE
}
}
return super.getItemViewType(position)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedbackViewHolder<T> {
when (viewType) {
ITEM_TYPE_BAD_CASE -> {
return BadCaseFBViewHolder(cb, parent) as FeedbackViewHolder<T>
}
else -> {
throw IllegalStateException("不支持ViewType: $viewType")
}
}
}
override fun onBindViewHolder(holder: FeedbackViewHolder<T>, position: Int) {
val item = data?.get(position) ?: return
holder.onBind(item, position)
}
override fun getItemCount(): Int = data?.size ?: 0
fun setCallback(cb: IFeedbackCallback) {
this.cb = cb
}
}

View File

@@ -0,0 +1,155 @@
package com.zhjt.mogo_core_function_devatools.feedback.biz.adapter.vh
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.GradientDrawable.Orientation.LEFT_RIGHT
import android.graphics.drawable.StateListDrawable
import android.text.Selection
import android.text.TextUtils
import android.text.TextUtils.TruncateAt.END
import android.util.StateSet
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.google.android.flexbox.FlexboxLayout
import com.mogo.eagle.core.utilcode.kotlin.*
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.zhjt.mogo_core_function_devatools.R
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse.Reason
import com.zhjt.mogo_core_function_devatools.feedback.biz.adapter.vh.base.FeedbackViewHolder
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Feedback.BadCase
import com.zhjt.mogo_core_function_devatools.feedback.callback.IFeedbackCallback
@SuppressLint("SetTextI18n")
internal class BadCaseFBViewHolder(cb: IFeedbackCallback?, parent: ViewGroup): FeedbackViewHolder<BadCase>(cb,
LayoutInflater
.from(parent.context)
.inflate(R.layout.layout_fb_badcase, parent, false)) {
private val flex by lazy {
itemView.findViewById<FlexboxLayout>(R.id.flex)
}
private val et by lazy {
itemView.findViewById<EditText>(R.id.et)
}
init {
itemView.findViewById<TextView>(R.id.record).also {
it.background = gradient(radius = 16.PX, orientation = LEFT_RIGHT, centerX = 0.06f, startColor = Color.rgb(35, 146, 252), endColor = Color.rgb(28, 75, 252))
it.onClick { _ ->
val flag = (it.getTag(R.id.feed_back_badcase_tag) as? Int) ?: 0
if (flag == 0) {
cb?.onStartBadCaseRecord(it)
} else {
cb?.onStopBadCaseRecord(it)
}
}
}
itemView.findViewById<View>(R.id.et_root).also {
it.background = shape(solid = Color.parseColor("#263869"), radius = 20.PX)
}
val words = itemView.findViewById<TextView>(R.id.words_count)
et.onClick {
et.requestFocus()
et.isFocusable = true
et.isFocusableInTouchMode = true
}
et.watch(
200,
onCountChanged = {
words.spannableText(listOf(it.toString(), "/200"), listOf(Color.parseColor("#5EBFFF"), Color.WHITE))
},
onTextChanged = {
data().remark.text = it?.toString() ?: ""
},
onReachMaxCountAction = {
ToastUtils.showShort("已超过最大字符数")
},
onGetFocus = {
it.background = shape(solid = Color.parseColor("#263869"), radius = 20.PX, stroke = Color.parseColor("#5EBFFF"))
}
)
}
override fun onBind(data: BadCase, position: Int) {
super.onBind(data, position)
flex.refresh(data)
val text = data.remark.text
if (!TextUtils.isEmpty(text)) {
et.setText(text)
Selection.setSelection(et.text, et.text.length)
}
}
private fun FlexboxLayout.refresh(data: BadCase) {
val vh = (tag as? Map<*, *> ?: emptyMap<Reason, ViewHolder>()).toMutableMap()
if (vh.isEmpty()) {
data.reasons.forEach { itx ->
val view = getBadCaseView(context)
val check = view.findViewById<ImageView>(R.id.check)
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))
}
check.isSelected = itx.isChecked
val reason = view.findViewById<TextView>(R.id.reason)
reason.text = itx.reason
vh[itx] = ViewHolder(check, reason)
view.onClick {
cb?.onBadCaseItemClicked(itx)
}
view.layoutParams = FlexboxLayout.LayoutParams(FlexboxLayout.LayoutParams.MATCH_PARENT, FlexboxLayout.LayoutParams.WRAP_CONTENT).also {
it.flexBasisPercent = 0.5f
}
addView(view)
}
tag = vh
} else {
data.reasons.forEach {
(vh[it] as? ViewHolder)?.run {
check.isSelected = it.isChecked
reason.text = it.reason
}
}
}
}
private fun getBadCaseView(context: Context): View {
return LinearLayout(context).also { itx ->
itx.orientation = LinearLayout.HORIZONTAL
itx.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
itx.gravity = Gravity.CENTER_VERTICAL
//ImageView -- check
val check = ImageView(context)
check.layoutParams = LinearLayout.LayoutParams(70.PX, 70.PX)
check.id = R.id.check
itx.addView(check)
//TextView -- Reason
val reason = TextView(context)
reason.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).also {
it.marginStart = 30.PX
it.marginEnd = 20.PX
}
reason.setTextColor(Color.WHITE)
reason.setTextSize(TypedValue.COMPLEX_UNIT_PX, 42.0f)
reason.maxLines = 1
reason.ellipsize = END
reason.id = R.id.reason
itx.addView(reason)
}
}
private data class ViewHolder(val check: ImageView, val reason: TextView)
}

View File

@@ -0,0 +1,22 @@
package com.zhjt.mogo_core_function_devatools.feedback.biz.adapter.vh.base
import android.view.View
import androidx.annotation.CallSuper
import androidx.recyclerview.widget.RecyclerView
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Feedback
import com.zhjt.mogo_core_function_devatools.feedback.callback.IFeedbackCallback
import java.util.concurrent.atomic.AtomicReference
internal open class FeedbackViewHolder<T: Feedback>(internal val cb: IFeedbackCallback?, view: View): RecyclerView.ViewHolder(view) {
private val data by lazy { AtomicReference<T>() }
fun data(): T {
return data.get()
}
@CallSuper
open fun onBind(data: T, position: Int) {
this.data.set(data)
}
}

View File

@@ -0,0 +1,28 @@
package com.zhjt.mogo_core_function_devatools.feedback.biz.bean
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse.Reason
internal sealed class Feedback {
class BadCase(var remark: Remark, var reasons: List<Reason>): Feedback() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as BadCase
if (reasons != other.reasons) return false
return true
}
override fun hashCode(): Int {
return reasons.hashCode()
}
}
}
/**
* 记录文本编辑框的状态
* @param text: 文件编辑框中输入的文字
* @param cursorPos: 光标位置
*/
data class Remark(var text: CharSequence = "", var cursorPos: Int = 0)

View File

@@ -0,0 +1,25 @@
package com.zhjt.mogo_core_function_devatools.feedback.biz.diff
import androidx.recyclerview.widget.DiffUtil
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Feedback
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Feedback.BadCase
internal class FeedbackDiffCallback<T: Feedback>(private val oldData: List<T>?, private val newData: List<T>?): DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldData?.size ?: 0
}
override fun getNewListSize(): Int = newData?.size ?: 0
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldData?.takeIf { it.size > oldItemPosition }?.equals(newData?.takeIf { it.size > newItemPosition }) ?: false
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldData?.get(oldItemPosition)
val newItem = newData?.get(newItemPosition)
if (oldItem == null || newItem == null) {
return false
}
return oldItem == newItem
}
}

View File

@@ -0,0 +1,28 @@
package com.zhjt.mogo_core_function_devatools.feedback.biz.impl
import com.zhjt.mogo_core_function_devatools.badcase.biz.BadCasePresenter
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.UploadResult
import com.zhjt.mogo_core_function_devatools.feedback.biz.IFeedbackPresenter
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Feedback
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Feedback.BadCase
import com.zhjt.mogo_core_function_devatools.feedback.biz.bean.Remark
internal class FeedbackPresenter: IFeedbackPresenter {
private val badCase by lazy {
BadCasePresenter()
}
override suspend fun loadFeedBacks(): List<Feedback> = mutableListOf<Feedback>().also {
//添加BadCase数据
it += BadCase(Remark(), badCase.loadBadCases(false))
}
override suspend fun getBadCaseTaskId(): Int {
return badCase.getTaskId()
}
override suspend fun upload(params: Map<String, String>): UploadResult? {
return badCase.upload(params)
}
}

View File

@@ -0,0 +1,28 @@
package com.zhjt.mogo_core_function_devatools.feedback.callback
import android.view.View
import android.widget.TextView
import com.zhjt.mogo_core_function_devatools.badcase.repository.net.api.entity.BadCaseResponse.Reason
internal interface IFeedbackCallback {
/**
* 点击关闭弹窗按钮时回调
*/
fun onClose()
/**
* BadCase某一条目被点击了
*/
fun onBadCaseItemClicked(reason: Reason)
/**
* 点击开始录制
*/
fun onStartBadCaseRecord(record: TextView)
/**
* 点击停止录制
*/
fun onStopBadCaseRecord(record: TextView)
}

View File

@@ -6,7 +6,9 @@ option java_outer_classname = "BadCausesProto";
message BadCauses {
int64 lastModified = 1;
repeated Cause data = 2 ;
repeated Cause drivenData = 2 ; //被动触发BadCase数据
int32 taskId = 3;
repeated Cause drivingData = 4; //主动触发BadCase数据
}
message Cause {

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:width="0px" android:height="50px" />
</shape>

View File

@@ -103,7 +103,7 @@
android:id="@+id/pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@android:style/Widget.Holo.ProgressBar.Large"
style="@android:style/Widget.Holo.Light.ProgressBar.Inverse"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@@ -2,15 +2,16 @@
<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_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical">
android:gravity="center_vertical"
tools:ignore="HardcodedText,PxUsage,ContentDescription">
<ImageView
android:id="@+id/check"
android:layout_width="70px"
android:layout_height="70px"
android:layout_marginStart="113px"/>
android:layout_marginStart="113px" />
<TextView
android:id="@+id/reason"

View File

@@ -0,0 +1,48 @@
<?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:layout_width="match_parent"
tools:layout_height="match_parent"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="120px"/>
<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"
tools:ignore="PxUsage" />
<ProgressBar
android:id="@+id/pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@android:style/Widget.Holo.Light.ProgressBar.Inverse"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone"/>
<View
android:id="@+id/top_mask"
android:layout_width="0dip"
android:layout_height="80px"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</merge>

View File

@@ -0,0 +1,116 @@
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="HardcodedText,PxUsage">
<LinearLayout
android:id="@+id/ll_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:gravity="center_vertical"
android:layout_marginTop="92px"
android:layout_marginStart="80px"
android:orientation="horizontal">
<View
android:layout_width="14px"
android:layout_height="50px"
android:background="#2966EC"
android:layout_marginEnd="19px"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="42px"
android:textColor="#ffffff"
android:text="Case上报"/>
</LinearLayout>
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/flex"
android:layout_width="0dip"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ll_title"
app:flexWrap="wrap"
app:flexDirection="row"
app:dividerDrawable="@drawable/flex_divider"
app:showDivider="middle"
app:justifyContent="flex_start"
android:layout_marginStart="80px"
android:layout_marginTop="54px" />
<TextView
android:id="@+id/supply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/flex"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="80px"
android:layout_marginTop="86px"
android:text="补充"
android:textColor="#ffffff"
android:textSize="36px" />
<LinearLayout
android:id="@+id/et_root"
android:layout_width="0dip"
android:layout_height="396px"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/supply"
app:layout_constraintEnd_toEndOf="parent"
android:paddingTop="30px"
android:paddingStart="25px"
android:paddingEnd="45px"
android:layout_marginStart="75px"
android:layout_marginEnd="95px"
android:layout_marginTop="32px"
android:orientation="vertical">
<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:background="@null"
android:focusable="false"
android:gravity="start"
android:focusableInTouchMode="true"
android:inputType="textMultiLine"
android:textSize="36px"
android:textColorHint="#4CFFFFFF"
android:hint="Case细节描述"
android:textColor="#FFFFFF" />
<TextView
android:id="@+id/words_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="28px"
android:layout_gravity="end"
tools:text="126/200"/>
</LinearLayout>
<TextView
android:id="@+id/record"
android:layout_width="0px"
android:layout_height="126px"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="75px"
android:layout_marginEnd="95px"
android:layout_marginTop="40px"
android:textColor="#ffffff"
android:textSize="42px"
android:gravity="center"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@+id/et_root"
android:text="录制Case"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="feed_back_badcase_tag" type="id" />
<item name="feed_back_badcase_taskid_tag" type="id" />
<item name="feed_back_badcase_job" type="id" />
</resources>

View File

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