From 639950ccc50359e3f239c1b08df6b7e369ca15b6 Mon Sep 17 00:00:00 2001 From: renwj Date: Thu, 17 Mar 2022 14:58:59 +0800 Subject: [PATCH] =?UTF-8?q?[FeedBack]=E5=8F=8D=E9=A6=88=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [feedback]优化ui --- .../functions/test/AutoPilotBadCaseTest.kt | 32 ++- config.gradle | 2 +- .../autopilot/MoGoAutopilotProvider.kt | 8 + .../mogo-core-function-devatools/build.gradle | 1 + .../DevaToolsProvider.kt | 6 +- .../badcase/BadCaseManager.kt | 42 ++-- .../badcase/api/entity/BadCaseResponse.kt | 28 --- .../badcase/biz/BadCasePresenter.kt | 8 +- .../badcase/biz/BadCaseView.kt | 12 +- .../badcase/biz/IBadCasePresenter.kt | 8 +- .../badcase/repository/Repository.kt | 80 ++++--- .../badcase/repository/net/BadCaseNetModel.kt | 6 +- .../{ => repository/net}/api/BadCaseApi.kt | 6 +- .../net/api/entity/BadCaseResponse.kt | 45 ++++ .../net}/api/entity/UploadResult.kt | 2 +- .../badcase/repository/store/BadCaseStore.kt | 37 ++- .../feedback/FeedbackManager.kt | 210 ++++++++++++++++++ .../feedback/biz/FeedBackView.kt | 93 ++++++++ .../feedback/biz/IFeedbackPresenter.kt | 14 ++ .../feedback/biz/adapter/FeedbackAdapter.kt | 63 ++++++ .../biz/adapter/vh/BadCaseFBViewHolder.kt | 155 +++++++++++++ .../biz/adapter/vh/base/FeedbackViewHolder.kt | 22 ++ .../feedback/biz/bean/Feedback.kt | 28 +++ .../feedback/biz/diff/FeedbackDiffCallback.kt | 25 +++ .../feedback/biz/impl/FeedbackPresenter.kt | 28 +++ .../feedback/callback/IFeedbackCallback.kt | 28 +++ .../src/main/proto/badcase.proto | 4 +- .../src/main/res/drawable/flex_divider.xml | 4 + .../res/layout/layout_badcase_collect.xml | 2 +- .../main/res/layout/layout_badcase_item.xml | 7 +- .../src/main/res/layout/layout_fb.xml | 48 ++++ .../src/main/res/layout/layout_fb_badcase.xml | 116 ++++++++++ .../src/main/res/values/ids.xml | 6 + .../src/main/res/values/strings.xml | 4 + .../function/hmi/notification/WarningFloat.kt | 4 + .../notification/WarningFloatWindowHelper.kt | 6 + .../notification/WarningNotificationConfig.kt | 3 + .../core/function/hmi/ui/MoGoHmiFragment.kt | 26 ++- .../hmi/ui/tools/AutoPilotAndCheckView.kt | 6 +- .../drawable-xxhdpi/debug_icon_feedback.png | Bin 0 -> 23255 bytes .../main/res/layout/view_auto_pilot_check.xml | 24 +- .../src/main/res/values/strings.xml | 1 + .../api/autopilot/IMoGoAutopilotProvider.kt | 5 +- .../api/devatools/IDevaToolsProvider.kt | 6 + .../api/hmi/warning/IMoGoWaringProvider.kt | 3 +- .../call/autopilot/CallerAutoPilotManager.kt | 14 ++ .../call/devatools/CallerDevaToolsManager.kt | 9 + .../function/call/hmi/CallerHmiManager.kt | 7 +- .../res/drawable-xxhdpi/icon_close_nor.png | Bin .../eagle/core/utilcode/kotlin/Extensions.kt | 189 +++++++++++++++- 50 files changed, 1334 insertions(+), 149 deletions(-) delete mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/api/entity/BadCaseResponse.kt rename core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/{ => repository/net}/api/BadCaseApi.kt (64%) create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/api/entity/BadCaseResponse.kt rename core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/{ => repository/net}/api/entity/UploadResult.kt (80%) create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/FeedbackManager.kt create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/FeedBackView.kt create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/IFeedbackPresenter.kt create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/FeedbackAdapter.kt create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/vh/BadCaseFBViewHolder.kt create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/vh/base/FeedbackViewHolder.kt create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/bean/Feedback.kt create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/diff/FeedbackDiffCallback.kt create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/impl/FeedbackPresenter.kt create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/callback/IFeedbackCallback.kt create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/res/drawable/flex_divider.xml create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_fb.xml create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_fb_badcase.xml create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/res/values/ids.xml create mode 100644 core/function-impl/mogo-core-function-devatools/src/main/res/values/strings.xml create mode 100644 core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/debug_icon_feedback.png rename core/{function-impl/mogo-core-function-hmi => mogo-core-res}/src/main/res/drawable-xxhdpi/icon_close_nor.png (100%) diff --git a/app/src/androidTest/java/com/mogo/functions/test/AutoPilotBadCaseTest.kt b/app/src/androidTest/java/com/mogo/functions/test/AutoPilotBadCaseTest.kt index 33b6edda37..41984bba30 100644 --- a/app/src/androidTest/java/com/mogo/functions/test/AutoPilotBadCaseTest.kt +++ b/app/src/androidTest/java/com/mogo/functions/test/AutoPilotBadCaseTest.kt @@ -3,8 +3,8 @@ package com.mogo.functions.test import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest -import com.mogo.eagle.core.data.autopilot.AutoPilotRecordResult import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager +import com.mogo.eagle.core.function.hmi.ui.MoGoHmiFragment import com.mogo.eagle.core.function.main.MainLauncherActivity import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -14,6 +14,7 @@ import org.junit.runner.RunWith import record_cache.RecordPanelOuterClass import java.text.SimpleDateFormat import java.util.* +import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import kotlin.random.Random @@ -31,7 +32,7 @@ class AutoPilotBadCaseTest { @ExperimentalCoroutinesApi @Test fun showBadCaseEntrance1(): Unit = runBlocking(Dispatchers.Main) { - delay(5000) + ensureMoGoHmiFragmentShow() var index = 0 (1 until 50) .map { it } @@ -61,7 +62,7 @@ class AutoPilotBadCaseTest { @ExperimentalCoroutinesApi @Test fun showBadCaseEntrance2(): Unit = runBlocking(Dispatchers.Main) { - delay(5000) + ensureMoGoHmiFragmentShow() var index = 0 (1 until 50) .map { it } @@ -118,4 +119,29 @@ class AutoPilotBadCaseTest { .collect() delay(TimeUnit.HOURS.toMillis(2)) } + + private suspend fun ensureMoGoHmiFragmentShow(): MoGoHmiFragment = suspendCancellableCoroutine { + launch.onActivity { itx -> + val executor = Executors.newSingleThreadScheduledExecutor() + executor.scheduleAtFixedRate({ + var find = + itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment + while (find == null) { + find = + itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment + + } + while (!find.isResumed) { + Thread.sleep(500) + } + it.resumeWith(Result.success(find)) + try { + Thread.sleep(500) + executor.shutdownNow() + } catch (e: Throwable) { + e.printStackTrace() + } + }, 50, 500, TimeUnit.MILLISECONDS) + } + } } \ No newline at end of file diff --git a/config.gradle b/config.gradle index 24442788dd..bc53544d66 100644 --- a/config.gradle +++ b/config.gradle @@ -28,7 +28,7 @@ ext { androidxcardview : "androidx.cardview:cardview:1.0.0", localbroadcastmanager : "androidx.localbroadcastmanager:localbroadcastmanager:1.0.0", // flexbox - flexbox : 'com.google.android:flexbox:2.0.1', + flexbox : 'com.google.android.flexbox:flexbox:3.0.0', // 测试 junit : "junit:junit:4.12", androidxjunit : "androidx.test.ext:junit:1.1.2", diff --git a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt index ae04c9f6cd..ec438bc1e4 100644 --- a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt +++ b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt @@ -35,6 +35,7 @@ import com.zhidao.support.adas.high.AdasManager import com.zhidao.support.adas.high.AdasOptions import com.zhidao.support.adas.high.bean.IPCUpgradeInfo import com.zhidao.support.adas.high.common.Constants +import com.zhidao.support.adas.high.common.Constants.IPC_CONNECTION_STATUS import com.zhidao.support.adas.high.common.CupidLogUtils import io.netty.channel.Channel import java.util.concurrent.TimeUnit @@ -332,4 +333,11 @@ class MoGoAutopilotProvider : override fun getGlobalPath() { AdasManager.getInstance().sendGlobalPathReq() } + + /** + * 车机与工控机是否连上了 + */ + override fun isConnected(): Boolean { + return AdasManager.getInstance().ipcConnectionStatus == IPC_CONNECTION_STATUS.CONNECTED + } } \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/build.gradle b/core/function-impl/mogo-core-function-devatools/build.gradle index 89f499a564..da21494c74 100644 --- a/core/function-impl/mogo-core-function-devatools/build.gradle +++ b/core/function-impl/mogo-core-function-devatools/build.gradle @@ -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 diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt index 251d6abdc6..7b7ea646ed 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt @@ -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() } - } \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/BadCaseManager.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/BadCaseManager.kt index 2bf4e937eb..1d6a1531e9 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/BadCaseManager.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/BadCaseManager.kt @@ -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().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().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}) } } diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/api/entity/BadCaseResponse.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/api/entity/BadCaseResponse.kt deleted file mode 100644 index e4d4382e7e..0000000000 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/api/entity/BadCaseResponse.kt +++ /dev/null @@ -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? = 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 - } -} diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/BadCasePresenter.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/BadCasePresenter.kt index 6bd32d01fd..2cbff00cc6 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/BadCasePresenter.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/BadCasePresenter.kt @@ -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() + } } \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/BadCaseView.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/BadCaseView.kt index ace4779a78..732f057f5c 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/BadCaseView.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/BadCaseView.kt @@ -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() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BadCaseViewHolder = BadCaseViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.layout_badcase_item, parent, false)) diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/IBadCasePresenter.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/IBadCasePresenter.kt index f30877fd2a..f7a5b262d2 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/IBadCasePresenter.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/biz/IBadCasePresenter.kt @@ -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 + suspend fun loadBadCases(isDriven: Boolean): List suspend fun updateLastModified(timestamp: Long) @@ -20,4 +20,6 @@ internal interface IBadCasePresenter { suspend fun getUnConsumedRecords(): List suspend fun deleteRecord(record: AutoPilotRecord) + + suspend fun getTaskId(): Int } \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/Repository.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/Repository.kt index b4916f254d..99604fe8bf 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/Repository.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/Repository.kt @@ -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 { - return net.get()?.data?.takeIf { it.isNotEmpty() }?.also { store.updateRecords(it) } ?: store.records().takeIf { it.isNotEmpty() } ?: getBuildIn() + suspend fun loadBadCases(isDriven: Boolean): List { + 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 { + private fun getBuildIn(isDriven: Boolean): List { CallerLogger.d("$M_DEVA${BadCaseManager.TAG}", "-- load cases from buildin -- 1 --") val data = mutableListOf() - 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? { return db.dao().getAllUnConsumedRecords() } + + suspend fun getTaskId(): Int { + return store.getTaskIdAndIncrement() + } } \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/BadCaseNetModel.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/BadCaseNetModel.kt index 6fdb7e6f1e..40702a3b3c 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/BadCaseNetModel.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/BadCaseNetModel.kt @@ -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 { diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/api/BadCaseApi.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/api/BadCaseApi.kt similarity index 64% rename from core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/api/BadCaseApi.kt rename to core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/api/BadCaseApi.kt index 07350f1355..989a46e1ad 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/api/BadCaseApi.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/api/BadCaseApi.kt @@ -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 diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/api/entity/BadCaseResponse.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/api/entity/BadCaseResponse.kt new file mode 100644 index 0000000000..27dfff91ca --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/api/entity/BadCaseResponse.kt @@ -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? = 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 + } + } +} diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/api/entity/UploadResult.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/api/entity/UploadResult.kt similarity index 80% rename from core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/api/entity/UploadResult.kt rename to core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/api/entity/UploadResult.kt index 8661560732..ac7babc0c3 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/api/entity/UploadResult.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/net/api/entity/UploadResult.kt @@ -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 diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/store/BadCaseStore.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/store/BadCaseStore.kt index 0ad0314a3a..6eaf12a945 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/store/BadCaseStore.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/badcase/repository/store/BadCaseStore.kt @@ -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 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): BadCauses { + suspend fun updateRecords(reasons: List, isDriven: Boolean): BadCauses { CallerLogger.d("$M_DEVA${BadCaseManager.TAG}", "--- updateRecords ---") val data = mutableListOf() 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 { - CallerLogger.d("$M_DEVA${BadCaseManager.TAG}", "-- load cases from pb -- 1 -- ") + suspend fun records(isDriven: Boolean): List { 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() + } + } + } } \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/FeedbackManager.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/FeedbackManager.kt new file mode 100644 index 0000000000..6b3ed93c25 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/FeedbackManager.kt @@ -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(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().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 { + 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 { + 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() + } + } + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/FeedBackView.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/FeedBackView.kt new file mode 100644 index 0000000000..290c2ab5c9 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/FeedBackView.kt @@ -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() + } + + 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) + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/IFeedbackPresenter.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/IFeedbackPresenter.kt new file mode 100644 index 0000000000..31dfbea827 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/IFeedbackPresenter.kt @@ -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 + + suspend fun getBadCaseTaskId(): Int + + suspend fun upload(params: Map): UploadResult? +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/FeedbackAdapter.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/FeedbackAdapter.kt new file mode 100644 index 0000000000..5867a49358 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/FeedbackAdapter.kt @@ -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: RecyclerView.Adapter>() { + + companion object { + const val ITEM_TYPE_BAD_CASE = 0x0101 + } + + private var cb: IFeedbackCallback? = null + + var data: List? = 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 { + when (viewType) { + ITEM_TYPE_BAD_CASE -> { + return BadCaseFBViewHolder(cb, parent) as FeedbackViewHolder + } + else -> { + throw IllegalStateException("不支持ViewType: $viewType") + } + } + } + + override fun onBindViewHolder(holder: FeedbackViewHolder, 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 + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/vh/BadCaseFBViewHolder.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/vh/BadCaseFBViewHolder.kt new file mode 100644 index 0000000000..335d25039d --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/vh/BadCaseFBViewHolder.kt @@ -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(cb, + LayoutInflater + .from(parent.context) + .inflate(R.layout.layout_fb_badcase, parent, false)) { + + private val flex by lazy { + itemView.findViewById(R.id.flex) + } + + private val et by lazy { + itemView.findViewById(R.id.et) + } + + init { + itemView.findViewById(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(R.id.et_root).also { + it.background = shape(solid = Color.parseColor("#263869"), radius = 20.PX) + } + val words = itemView.findViewById(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()).toMutableMap() + if (vh.isEmpty()) { + data.reasons.forEach { itx -> + val view = getBadCaseView(context) + val check = view.findViewById(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(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) +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/vh/base/FeedbackViewHolder.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/vh/base/FeedbackViewHolder.kt new file mode 100644 index 0000000000..d058d78327 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/adapter/vh/base/FeedbackViewHolder.kt @@ -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(internal val cb: IFeedbackCallback?, view: View): RecyclerView.ViewHolder(view) { + + private val data by lazy { AtomicReference() } + + fun data(): T { + return data.get() + } + + @CallSuper + open fun onBind(data: T, position: Int) { + this.data.set(data) + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/bean/Feedback.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/bean/Feedback.kt new file mode 100644 index 0000000000..7ee31743d4 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/bean/Feedback.kt @@ -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): 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) diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/diff/FeedbackDiffCallback.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/diff/FeedbackDiffCallback.kt new file mode 100644 index 0000000000..71da2796f2 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/diff/FeedbackDiffCallback.kt @@ -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(private val oldData: List?, private val newData: List?): 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 + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/impl/FeedbackPresenter.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/impl/FeedbackPresenter.kt new file mode 100644 index 0000000000..e6d89e9168 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/biz/impl/FeedbackPresenter.kt @@ -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 = mutableListOf().also { + //添加BadCase数据 + it += BadCase(Remark(), badCase.loadBadCases(false)) + } + + override suspend fun getBadCaseTaskId(): Int { + return badCase.getTaskId() + } + + override suspend fun upload(params: Map): UploadResult? { + return badCase.upload(params) + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/callback/IFeedbackCallback.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/callback/IFeedbackCallback.kt new file mode 100644 index 0000000000..6bc329121d --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/feedback/callback/IFeedbackCallback.kt @@ -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) +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/proto/badcase.proto b/core/function-impl/mogo-core-function-devatools/src/main/proto/badcase.proto index d76f5ad9a0..da39b611a9 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/proto/badcase.proto +++ b/core/function-impl/mogo-core-function-devatools/src/main/proto/badcase.proto @@ -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 { diff --git a/core/function-impl/mogo-core-function-devatools/src/main/res/drawable/flex_divider.xml b/core/function-impl/mogo-core-function-devatools/src/main/res/drawable/flex_divider.xml new file mode 100644 index 0000000000..1b193db85f --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/res/drawable/flex_divider.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_badcase_collect.xml b/core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_badcase_collect.xml index fbc2562dba..3e0e5ae2a7 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_badcase_collect.xml +++ b/core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_badcase_collect.xml @@ -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" diff --git a/core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_badcase_item.xml b/core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_badcase_item.xml index 339adbdf24..1c336dfac4 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_badcase_item.xml +++ b/core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_badcase_item.xml @@ -2,15 +2,16 @@ + android:gravity="center_vertical" + tools:ignore="HardcodedText,PxUsage,ContentDescription"> + android:layout_marginStart="113px" /> + + + + + + + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_fb_badcase.xml b/core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_fb_badcase.xml new file mode 100644 index 0000000000..4ecd6f8bfa --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/res/layout/layout_fb_badcase.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/res/values/ids.xml b/core/function-impl/mogo-core-function-devatools/src/main/res/values/ids.xml new file mode 100644 index 0000000000..eefb61e677 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/res/values/ids.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/res/values/strings.xml b/core/function-impl/mogo-core-function-devatools/src/main/res/values/strings.xml new file mode 100644 index 0000000000..c29106e01a --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloat.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloat.kt index 483800259c..a258e6358a 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloat.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloat.kt @@ -169,6 +169,10 @@ class WarningFloat { this.config.isEnqueue = enqueue } + fun softInputMode(mode: Int) = apply { + this.config.softInputMode = mode + } + /** * 创建浮窗,包括Activity浮窗和系统浮窗,如若系统浮窗无权限,先进行权限申请 */ diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloatWindowHelper.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloatWindowHelper.kt index 2060d4c113..040628e086 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloatWindowHelper.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningFloatWindowHelper.kt @@ -56,6 +56,7 @@ internal class WarningFloatWindowHelper( // 获取 WindowManager windowManager = context.getSystemService(Service.WINDOW_SERVICE) as WindowManager params = WindowManager.LayoutParams().apply { + // 设置窗口类型为应用子窗口,和PopupWindow同类型 type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL format = PixelFormat.RGBA_8888 @@ -73,6 +74,10 @@ internal class WarningFloatWindowHelper( x = config.locationPair.first y = config.locationPair.second } + //添加软键盘展示模式 + if (config.softInputMode != 0) { + softInputMode = config.softInputMode + } } } @@ -89,6 +94,7 @@ internal class WarningFloatWindowHelper( // 为了避免创建的时候闪一下,我们先隐藏视图,不能直接设置GONE,否则定位会出现问题 floatingView.visibility = View.INVISIBLE // 将frameLayout添加到系统windowManager中 + windowManager.addView(frameLayout, params) // 在浮窗绘制完成的时候,设置初始坐标、执行入场动画 diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningNotificationConfig.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningNotificationConfig.kt index 51997f5177..79d025bdd4 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningNotificationConfig.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/notification/WarningNotificationConfig.kt @@ -19,6 +19,9 @@ data class WarningNotificationConfig( var layoutId: Int? = null, var layoutView: View? = null, + // 软键盘模式 + var softInputMode: Int = 0, + // 当前浮窗的tag var floatTag: String? = null, // 是否正在执行动画 diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt index fcb1237545..7e343fef53 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt @@ -6,6 +6,7 @@ import android.text.TextUtils import android.view.Gravity import android.view.View import android.view.WindowManager +import android.view.WindowManager.LayoutParams import android.view.animation.OvershootInterpolator import androidx.lifecycle.lifecycleScope import com.alibaba.android.arouter.facade.annotation.Route @@ -80,8 +81,6 @@ class MoGoHmiFragment : MvpFragment private var toolsView: AutoPilotAndCheckView? = null - private var autoPilotToolsFloat: WarningFloat.Builder? = null - // 检测、自动驾驶速度设置 private var toolsViewFloat: WarningFloat.Builder? = null @@ -223,6 +222,11 @@ class MoGoHmiFragment : MvpFragment override fun showDebugPanelView() { toggleDebugView() } + + override fun showFeedbackView() { + dismissToolsFloatView() + CallerDevaToolsManager.showFeedbackView(it) + } }) } toolsViewFloat = WarningFloat.with(it) @@ -874,7 +878,8 @@ class MoGoHmiFragment : MvpFragment } - override fun showBadCaseFloat(tag: String, floatView: View): () -> Unit { + override fun showFloatWindow(tag: String, floatView: View, attrs: LayoutParams?): () -> Unit { + var floatWindow: WarningFloat.Builder? WarningFloat.with(context ?: Utils.getApp()) .setTag(tag) .setLayout(floatView) @@ -884,7 +889,7 @@ class MoGoHmiFragment : MvpFragment .setAnimator(object : DefaultAnimator() { override fun enterAnim( view: View, - params: WindowManager.LayoutParams, + params: LayoutParams, windowManager: WindowManager, sidePattern: SidePattern ): Animator? = @@ -895,7 +900,7 @@ class MoGoHmiFragment : MvpFragment override fun exitAnim( view: View, - params: WindowManager.LayoutParams, + params: LayoutParams, windowManager: WindowManager, sidePattern: SidePattern ): Animator? = @@ -904,17 +909,20 @@ class MoGoHmiFragment : MvpFragment }) .addWarningStatusListener(object : IMoGoWarningStatusListener { override fun onDismiss() { - autoPilotToolsFloat = null + floatWindow = null } }) .also { - autoPilotToolsFloat = it + floatWindow = it + if (((attrs?.softInputMode?: 0) and LayoutParams.SOFT_INPUT_MASK_ADJUST) != 0) { + it.softInputMode(attrs!!.softInputMode) + } } .show() return { - autoPilotToolsFloat?.let { + floatWindow?.let { WarningFloat.dismiss(it.config.floatTag, false) - autoPilotToolsFloat = null + floatWindow = null } } } diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/AutoPilotAndCheckView.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/AutoPilotAndCheckView.kt index 5c2eafcfff..c1a6d1fe28 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/AutoPilotAndCheckView.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/AutoPilotAndCheckView.kt @@ -13,10 +13,10 @@ import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotManager import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager import com.mogo.eagle.core.function.hmi.R import com.mogo.eagle.core.function.hmi.ui.utils.KeyBoardUtil +import com.mogo.eagle.core.utilcode.kotlin.onClick import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils import com.mogo.eagle.core.utilcode.util.ToastUtils import kotlinx.android.synthetic.main.view_auto_pilot_check.view.* -import kotlinx.android.synthetic.main.view_check_system.view.* /** * @author ChenFufeng @@ -85,6 +85,9 @@ class AutoPilotAndCheckView @JvmOverloads constructor( ivDebugPanel.setOnClickListener { clickListener?.showDebugPanelView() } + ivDebugFeedback.onClick { + clickListener?.showFeedbackView() + } etInputSpeed.setOnTouchListener { v, _ -> if (!connectStatus) { ToastUtils.showShort("设置车速失败,请启动域控制器") @@ -176,5 +179,6 @@ class AutoPilotAndCheckView @JvmOverloads constructor( fun go2CheckPage() fun onClose(v: View) fun showDebugPanelView() + fun showFeedbackView() } } \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/debug_icon_feedback.png b/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/debug_icon_feedback.png new file mode 100644 index 0000000000000000000000000000000000000000..c1e9c6a265b795f8b2785b1d2c5715ace7b534a2 GIT binary patch literal 23255 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 a`~LxvMnBI1eulIF0000 + android:layout_marginStart="142px" /> + + + + 车辆检测 调试面板 + 反馈 车速设置 系统运行 关机 diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotProvider.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotProvider.kt index 7a018a380b..32c838fee7 100644 --- a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotProvider.kt +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/autopilot/IMoGoAutopilotProvider.kt @@ -135,5 +135,8 @@ interface IMoGoAutopilotProvider : IMoGoFunctionServerProvider { */ fun getGlobalPath() - + /** + * 域控制器是否连上了 + */ + fun isConnected(): Boolean } \ No newline at end of file diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt index 2ebc65e4cf..c6f436e918 100644 --- a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt @@ -1,5 +1,6 @@ package com.mogo.eagle.core.function.api.devatools +import android.content.Context import android.view.View import com.mogo.eagle.core.data.chain.ChainLogParam import com.mogo.eagle.core.function.api.base.IMoGoFunctionServerProvider @@ -33,4 +34,9 @@ interface IDevaToolsProvider : IMoGoFunctionServerProvider { * 当工控机回调时调用 */ fun onReceiveBadCaseRecord(record: RecordPanelOuterClass.RecordPanel) + + /** + * 展示反馈页面 + */ + fun showFeedbackWindow(ctx: Context) } \ No newline at end of file diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/hmi/warning/IMoGoWaringProvider.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/hmi/warning/IMoGoWaringProvider.kt index b2166aae87..47e7b91465 100644 --- a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/hmi/warning/IMoGoWaringProvider.kt +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/hmi/warning/IMoGoWaringProvider.kt @@ -1,6 +1,7 @@ package com.mogo.eagle.core.function.api.hmi.warning import android.view.View +import android.view.WindowManager.LayoutParams import com.mogo.eagle.core.data.enums.WarningDirectionEnum import com.mogo.eagle.core.data.notice.NoticeNormalData import com.mogo.eagle.core.data.notice.NoticeTrafficStylePushData @@ -212,7 +213,7 @@ interface IMoGoWaringProvider : IMoGoHmiViewProxy { * @param tag: 唯一标识 * @return 触发消失时回调 */ - fun showBadCaseFloat(tag: String = "BadCaseFloat", floatView: View): () -> Unit + fun showFloatWindow(tag: String = "BadCaseFloat", floatView: View, attrs: LayoutParams?): () -> Unit } \ No newline at end of file diff --git a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotManager.kt b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotManager.kt index 69de58bf2a..5a77da1476 100644 --- a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotManager.kt +++ b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/autopilot/CallerAutoPilotManager.kt @@ -73,6 +73,13 @@ object CallerAutoPilotManager { providerApi?.recordPackage(type, id, duration) } + /** + * 停止录制bag包 + */ + fun stopRecord(type: Int, id: Int) { + providerApi?.stopRecord(type, id) + } + fun setEnableLog(isEnableLog: Boolean) { providerApi?.setEnableLog(isEnableLog) } @@ -124,4 +131,11 @@ object CallerAutoPilotManager { fun setControlAutopilotCarAuto(isEnable: Boolean) { providerApi?.setControlAutopilotCarAuto(isEnable) } + + /** + * 车机与工控机是否连上了 + */ + fun isConnected(): Boolean { + return providerApi?.isConnected() ?: false + } } \ No newline at end of file diff --git a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt index f1bb6c6c5b..6771d327fe 100644 --- a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt +++ b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt @@ -1,5 +1,7 @@ package com.mogo.eagle.core.function.call.devatools +import android.content.Context +import android.view.ContextMenu import android.view.View import com.mogo.eagle.core.data.constants.MogoServicePaths import com.mogo.eagle.core.data.chain.ChainLogParam @@ -64,4 +66,11 @@ object CallerDevaToolsManager { fun onReceiveBadCaseRecord(record: RecordPanelOuterClass.RecordPanel) { devaToolsProviderApi?.onReceiveBadCaseRecord(record) } + + /** + * 展示反馈界面 + */ + fun showFeedbackView(ctx: Context) { + devaToolsProviderApi?.showFeedbackWindow(ctx) + } } \ No newline at end of file diff --git a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/hmi/CallerHmiManager.kt b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/hmi/CallerHmiManager.kt index f17a73c9cd..424815e2b8 100644 --- a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/hmi/CallerHmiManager.kt +++ b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/hmi/CallerHmiManager.kt @@ -1,6 +1,7 @@ package com.mogo.eagle.core.function.call.hmi import android.view.View +import android.view.WindowManager.LayoutParams import com.alibaba.android.arouter.launcher.ARouter import com.mogo.eagle.core.data.constants.MoGoFragmentPaths import com.mogo.eagle.core.data.enums.WarningDirectionEnum @@ -289,10 +290,10 @@ object CallerHmiManager : CallerBase() { } /** - * 展示BadCase浮层 + * 展示浮层 */ - fun showBadCaseFloat(tag: String = "BadCaseFloat", floatView: View): (() -> Unit)? { - return waringProviderApi?.showBadCaseFloat(tag, floatView) + fun showFloatWindow(tag: String, floatView: View, attrs: LayoutParams? = null): (() -> Unit)? { + return waringProviderApi?.showFloatWindow(tag, floatView, attrs) } /** diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/icon_close_nor.png b/core/mogo-core-res/src/main/res/drawable-xxhdpi/icon_close_nor.png similarity index 100% rename from core/function-impl/mogo-core-function-hmi/src/main/res/drawable-xxhdpi/icon_close_nor.png rename to core/mogo-core-res/src/main/res/drawable-xxhdpi/icon_close_nor.png diff --git a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/kotlin/Extensions.kt b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/kotlin/Extensions.kt index c7ad99afb6..69d5b1209c 100644 --- a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/kotlin/Extensions.kt +++ b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/kotlin/Extensions.kt @@ -2,22 +2,41 @@ package com.mogo.eagle.core.utilcode.kotlin import android.content.Context import android.graphics.Color +import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable -import android.text.TextUtils +import android.text.* +import android.text.style.ForegroundColorSpan import android.util.TypedValue +import android.view.Gravity +import android.view.MotionEvent import android.view.View +import android.view.WindowManager +import android.widget.EditText +import android.widget.PopupWindow +import android.widget.TextView import androidx.annotation.ColorInt import androidx.annotation.IntRange import androidx.core.view.ViewCompat +import androidx.core.widget.doAfterTextChanged +import androidx.core.widget.doBeforeTextChanged +import androidx.fragment.app.FragmentActivity import androidx.lifecycle.* import androidx.lifecycle.Lifecycle.Event +import androidx.lifecycle.Lifecycle.Event.ON_DESTROY +import androidx.recyclerview.widget.RecyclerView +import com.mogo.eagle.core.utilcode.reminder.Reminder +import com.mogo.eagle.core.utilcode.reminder.api.impl.PopupWindowReminder import com.mogo.eagle.core.utilcode.util.ClickUtils import com.mogo.eagle.core.utilcode.util.R import com.mogo.eagle.core.utilcode.util.Utils import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel +import java.lang.IllegalStateException import java.util.* +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeUnit.SECONDS +import kotlin.math.abs val T.lifecycleOwner: LifecycleOwner get() = getTag(R.id.view_lifecycle_owner) as? LifecycleOwner ?: object : LifecycleOwner, LifecycleEventObserver { @@ -64,6 +83,9 @@ val T.lifecycleOwner: LifecycleOwner setTag(R.id.view_lifecycle_owner, it) } +val T.scope + get() = lifecycleOwner.lifecycleScope + fun View.onClick(block: (View) -> Unit) { this.setOnClickListener { if (ClickUtils.isClickTooFrequent(this)) { @@ -76,6 +98,9 @@ fun View.onClick(block: (View) -> Unit) { val T.lifeCycleScope: LifecycleCoroutineScope get() = (this as? LifecycleOwner)?.lifecycleScope ?: ProcessLifecycleOwner.get().lifecycleScope +val T.lifeCycleOwner: LifecycleOwner + get() = (this as? LifecycleOwner) ?: ProcessLifecycleOwner.get() + fun T.observe(target: Array, block: ((event: Event) -> Unit) ?= null) { block?.let { this.lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver { @@ -88,6 +113,18 @@ fun T.observe(target: Array, block: ((event: Event) -> Unit) ?= } } +fun T.onDetach(block: (() -> Unit)? = null) { + block?.also { + this.lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver { + override fun onStateChanged(source: LifecycleOwner, event: Event) { + if (event == ON_DESTROY) { + it.invoke() + } + } + }) + } +} + fun Job.safeCancel(cause: String? = null) = try { this.cancel(if (TextUtils.isEmpty(cause)) null else CancellationException(cause)) } catch (t: Throwable) { t.printStackTrace() } @@ -104,8 +141,9 @@ fun Deferred.safeCancel(cause: String? = null) = try { this.cancel(if (TextUtils.isEmpty(cause)) null else CancellationException(cause)) } catch (t: Throwable) { t.printStackTrace() } -fun shape(@ColorInt solid: Int = Color.TRANSPARENT, @ColorInt stroke: Int = Color.TRANSPARENT, @IntRange(from = 0) strokeWidth: Int = 0, @IntRange(from = 0) radius: Int = 0, radii: FloatArray = FloatArray(8).apply { Arrays.fill(this, radius.toFloat()) }, shape: Int = GradientDrawable.RECTANGLE): Drawable { +fun shape(@ColorInt solid: Int = Color.TRANSPARENT, @ColorInt stroke: Int = Color.TRANSPARENT, @IntRange(from = 0) strokeWidth: Int = 0, @IntRange(from = 0) radius: Int = 0, radii: FloatArray = FloatArray(8).apply { Arrays.fill(this, radius.toFloat()) }, shape: Int = GradientDrawable.RECTANGLE, width: Int = 0, height: Int = 0): Drawable { val drawable = GradientDrawable() + drawable.setSize(width, height) drawable.shape = shape drawable.gradientType = GradientDrawable.LINEAR_GRADIENT drawable.setColor(solid) @@ -115,7 +153,7 @@ fun shape(@ColorInt solid: Int = Color.TRANSPARENT, @ColorInt stroke: Int = Colo } fun gradient(shape: Int = GradientDrawable.RECTANGLE, @IntRange(from = 0) radius: Int = 0, radii: FloatArray = FloatArray(8).apply { Arrays.fill(this, radius.toFloat()) }, gradientType: Int = GradientDrawable.LINEAR_GRADIENT, orientation: GradientDrawable.Orientation = GradientDrawable.Orientation.TOP_BOTTOM, centerX: Float = 0.5f, centerY: Float = 0.5f, @ColorInt startColor: Int, @ColorInt centerColor: Int = startColor, @ColorInt endColor: Int): Drawable{ - val drawable = GradientDrawable(orientation, intArrayOf(endColor, centerColor, startColor)) + val drawable = GradientDrawable(orientation, intArrayOf(startColor, centerColor, endColor)) drawable.shape = shape drawable.gradientType = gradientType drawable.orientation = orientation @@ -124,7 +162,146 @@ fun gradient(shape: Int = GradientDrawable.RECTANGLE, @IntRange(from = 0) radius return drawable } -fun Int.toPixels(context: Context? = null): Float { - val ctx = context ?: Utils.getApp() - return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, this.toFloat(), ctx.resources.displayMetrics) +val Int.PX: Int + get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, this.toFloat(), Utils.getApp().resources.displayMetrics).toInt() + +val Int.SP: Int + get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this.toFloat(), Utils.getApp().resources.displayMetrics).toInt() + + +fun T.watch(@IntRange(from = 1) maxCharCount: Int, onCountChanged:((count: Int) -> Unit)? = null, onTextChanged:((editable: Editable?) -> Unit)? = null, onReachMaxCountAction:((text: T) -> Unit)? = null, onGetFocus:((text: T) -> Unit)? = null) { + doAfterTextChanged { itx -> + onTextChanged?.invoke(itx) + if (itx == null || itx.isEmpty()) { + onCountChanged?.invoke(0) + return@doAfterTextChanged + } + val length = itx.length + if (length > maxCharCount) { + text = itx.delete(maxCharCount, length) + Selection.setSelection(text, maxCharCount) + onCountChanged?.invoke(maxCharCount) + onReachMaxCountAction?.invoke(this) + } else { + onCountChanged?.invoke(length) + } + } + + doBeforeTextChanged { _, _, _, _ -> + onGetFocus?.invoke(this) + } +} + +fun T.spannableText(parts: List, colors: List) { + if (parts.isEmpty() || colors.isEmpty() || parts.size != colors.size) { + return + } + val ssb = SpannableStringBuilder() + parts.forEachIndexed { index, sequence -> + val text = SpannableString(sequence) + text.setSpan(ForegroundColorSpan(colors[index]), 0, sequence.length, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE) + ssb.append(text) + } + text = ssb +} + +fun RecyclerView.fixGestureConflictForViews(ids: List = emptyList()) { + val fixer = GestureConflictFixer(ids) + addOnItemTouchListener(fixer) + addOnScrollListener(fixer) +} + +private class GestureConflictFixer(private val ids: List) : RecyclerView.OnScrollListener(), RecyclerView.OnItemTouchListener { + + private var scrollState = RecyclerView.SCROLL_STATE_IDLE + private var scrollPointerId = -1 + private var initialTouchX = 0 + private var initialTouchY = 0 + private var dx = 0 + private var dy = 0 + + private var intercpted = false + + override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { + when (e.actionMasked) { + MotionEvent.ACTION_DOWN -> { + scrollPointerId = e.getPointerId(0) + initialTouchX = (e.x + 0.5f).toInt() + initialTouchY = (e.y + 0.5f).toInt() + intercpted = computeIntercepted(initialTouchX, initialTouchY, rv, ids) + } + MotionEvent.ACTION_POINTER_DOWN -> { + val actionIndex = e.actionIndex + scrollPointerId = e.getPointerId(actionIndex) + initialTouchX = (e.getX(actionIndex) + 0.5f).toInt() + initialTouchY = (e.getY(actionIndex) + 0.5f).toInt() + } + MotionEvent.ACTION_MOVE -> { + val index = e.findPointerIndex(scrollPointerId) + if (index >= 0 && scrollState != RecyclerView.SCROLL_STATE_DRAGGING) { + val x = (e.getX(index) + 0.5f).toInt() + val y = (e.getY(index) + 0.5f).toInt() + dx = x - initialTouchX + dy = y - initialTouchY + } + } + MotionEvent.ACTION_UP -> { + intercpted = false + } + } + return false + } + + private fun computeIntercepted(x: Int, y: Int, rv: RecyclerView, ids: List): Boolean { + return ids.takeIf { it.isNotEmpty() }?.find { + val out = Rect() + rv.findViewById(it).getGlobalVisibleRect(out) + out.contains(x, y) + }?.let { it != View.NO_ID } ?: false + } + + override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {} + + override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {} + + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + val oldState = scrollState + scrollState = newState + if (oldState == RecyclerView.SCROLL_STATE_IDLE && newState == RecyclerView.SCROLL_STATE_DRAGGING) { + recyclerView.layoutManager?.let { + if (intercpted) { + recyclerView.stopScroll() + } + } + } + } +} + +fun Context.toast(text: CharSequence, duration: Long = 2, unit: TimeUnit = SECONDS) { + val activity = (this as? FragmentActivity) ?: throw IllegalStateException("please use Activity to trigger toast show.") + activity.lifeCycleScope.launchWhenResumed { + val pop = PopupWindow(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT).also { + it.isOutsideTouchable = false + it.isTouchable = false + it.isFocusable = false + it.setBackgroundDrawable(shape(solid = Color.parseColor("#99000000"), radius = 32.PX)) + } + val tv = TextView(this@toast) + tv.setTextColor(Color.WHITE) + tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, 56.0f) + tv.setPaddingRelative(114.PX, 61.PX, 114.PX, 61.PX) + tv.text = text + pop.contentView = tv + val reminder = object : PopupWindowReminder(pop) { + override fun show() { + pop.showAtLocation(activity.window.decorView, Gravity.CENTER, 0, 0) + lifecycleOwner().lifecycleScope.launch { + delay(unit.toMillis(duration)) + hide() + } + } + override fun isOverride(): Boolean = true + } + Reminder.enqueue(activity.lifeCycleOwner, reminder) + } }