feat(task): 添加复合材料铺贴任务功能
- 实现复合材料铺贴任务的完整业务流程 - 集成 OCR 识别功能用于逐层铺贴验证 - 添加任务详情查询和状态管理 - 实现拍照识别和结果处理逻辑 - 优化任务路由逻辑支持多种任务类型匹配 - 添加语音控制命令支持 - 设计专用 UI 界面和视觉反馈 - 配置网络请求接口和服务端通信 - 添加数据模型定义和状态枚举 - 实现离线命令监听和回调处理 - 完善错误处理和用户提示信息
这个提交包含在:
父节点
75e9fca5a4
当前提交
3f97ff01f4
@ -0,0 +1,56 @@
|
|||||||
|
package com.nova.brain.glass.model.data
|
||||||
|
|
||||||
|
data class CompositeLayupDetailItem(
|
||||||
|
val id: Long = 0,
|
||||||
|
val taskId: Long = 0,
|
||||||
|
val taskNo: String = "",
|
||||||
|
val stepSeq: Int = 0,
|
||||||
|
val detailInfo: String = "",
|
||||||
|
val detailResult: String = "",
|
||||||
|
val detailStatus: Int = 0,
|
||||||
|
val createdAt: String = "",
|
||||||
|
val updatedAt: String = "",
|
||||||
|
val createdBy: String = "",
|
||||||
|
val updatedBy: String = "",
|
||||||
|
val isDeleted: Long = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CompositeLayupTaskDetail(
|
||||||
|
val id: Long = 0,
|
||||||
|
val taskNo: String = "",
|
||||||
|
val taskName: String = "",
|
||||||
|
val taskType: Int = 0,
|
||||||
|
val taskStatus: Int = 0,
|
||||||
|
val taskSource: String = "",
|
||||||
|
val taskSteps: Int = 0,
|
||||||
|
val taskCurrentStep: Int = 0,
|
||||||
|
val taskFilePaths: String = "",
|
||||||
|
val centralTaskId: String = "",
|
||||||
|
val centralTaskResult: String = "",
|
||||||
|
val createdAt: String = "",
|
||||||
|
val updatedAt: String = "",
|
||||||
|
val createdBy: String = "",
|
||||||
|
val updatedBy: String = "",
|
||||||
|
val isDeleted: Long = 0,
|
||||||
|
val detailList: List<CompositeLayupDetailItem>? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CompositeLayupTaskDetailResponse(
|
||||||
|
val code: Int = 0,
|
||||||
|
val message: String = "",
|
||||||
|
val data: CompositeLayupTaskDetail? = null,
|
||||||
|
val success: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CompositeLayupRecognizeResult(
|
||||||
|
val finished: Boolean = false,
|
||||||
|
val success: Boolean = false,
|
||||||
|
val errorMessage: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CompositeLayupRecognizeResponse(
|
||||||
|
val code: Int = 0,
|
||||||
|
val message: String = "",
|
||||||
|
val data: CompositeLayupRecognizeResult? = null,
|
||||||
|
val success: Boolean = false
|
||||||
|
)
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.nova.brain.glass.repository
|
||||||
|
|
||||||
|
import com.nova.brain.glass.model.data.CompositeLayupRecognizeResponse
|
||||||
|
import com.nova.brain.glass.model.data.CompositeLayupTaskDetailResponse
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Multipart
|
||||||
|
import retrofit2.http.Part
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface Service4 {
|
||||||
|
|
||||||
|
@GET("/api/glass/workTask/queryTask")
|
||||||
|
fun queryTask(@Query("taskNo") taskNo: String): Observable<CompositeLayupTaskDetailResponse>
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST("/api/glass/workTask/ocrRecognize")
|
||||||
|
fun ocrRecognize(
|
||||||
|
@Part("taskNo") taskNo: RequestBody,
|
||||||
|
@Part("stepSeq") stepSeq: RequestBody,
|
||||||
|
@Part file: MultipartBody.Part
|
||||||
|
): Observable<CompositeLayupRecognizeResponse>
|
||||||
|
}
|
||||||
@ -0,0 +1,256 @@
|
|||||||
|
package com.nova.brain.glass.ui
|
||||||
|
|
||||||
|
import android.os.Environment
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.nova.brain.glass.R
|
||||||
|
import com.nova.brain.glass.databinding.ActivityCompositeLayupTaskBinding
|
||||||
|
import com.nova.brain.glass.helper.GlassMediaServiceHelper
|
||||||
|
import com.nova.brain.glass.helper.OfflineCmdListener
|
||||||
|
import com.nova.brain.glass.helper.OfflineCmdServiceHelper
|
||||||
|
import com.nova.brain.glass.viewmodel.CompositeLayupRecognizeState
|
||||||
|
import com.nova.brain.glass.viewmodel.CompositeLayupTaskVM
|
||||||
|
import com.rokid.security.glass3.sdk.base.data.media.PhotoResolution
|
||||||
|
import com.rokid.security.system.server.media.callback.PhotoFileCallback
|
||||||
|
import com.xuqm.base.common.LogHelper
|
||||||
|
import com.xuqm.base.extensions.showMessage
|
||||||
|
import com.xuqm.base.ui.BaseActivity
|
||||||
|
import java.io.File
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class CompositeLayupTaskActivity : BaseActivity<ActivityCompositeLayupTaskBinding>() {
|
||||||
|
override fun getLayoutId(): Int = R.layout.activity_composite_layup_task
|
||||||
|
override fun fullscreen(): Boolean = true
|
||||||
|
|
||||||
|
private enum class ScreenMode {
|
||||||
|
START,
|
||||||
|
STEP_SUCCESS,
|
||||||
|
FAILED,
|
||||||
|
FINISHED
|
||||||
|
}
|
||||||
|
|
||||||
|
private val viewModel: CompositeLayupTaskVM by lazy {
|
||||||
|
ViewModelProvider(this)[CompositeLayupTaskVM::class.java]
|
||||||
|
}
|
||||||
|
private val taskNoFromIntent: String by lazy {
|
||||||
|
intent.getStringExtra(EXTRA_TASK_NO).orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isPhotoFallback = false
|
||||||
|
private var isCaptureInFlight = false
|
||||||
|
private var screenMode = ScreenMode.START
|
||||||
|
|
||||||
|
private val listener = object : OfflineCmdListener {
|
||||||
|
override fun onOfflineCmd(cmd: String) {
|
||||||
|
runOnUiThread {
|
||||||
|
when (cmd) {
|
||||||
|
"退出", "返回", "退回" -> finish()
|
||||||
|
"开始", "开始任务" -> if (screenMode == ScreenMode.START) triggerCapture()
|
||||||
|
"下一步", "继续识别", "继续任务" -> {
|
||||||
|
if (screenMode == ScreenMode.STEP_SUCCESS) triggerCapture()
|
||||||
|
}
|
||||||
|
"重拍", "重新拍照", "重新拍摄" -> {
|
||||||
|
if (screenMode == ScreenMode.FAILED) triggerCapture()
|
||||||
|
}
|
||||||
|
"完成任务" -> if (screenMode == ScreenMode.FINISHED) finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val photoCallbackId = UUID.randomUUID().toString()
|
||||||
|
private val photoCallback = object : PhotoFileCallback.Stub() {
|
||||||
|
override fun onTakePhoto(path: String) {
|
||||||
|
LogHelper.d("CompositeLayupTask onTakePhoto: $path")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCallbackId(): String = photoCallbackId
|
||||||
|
|
||||||
|
override fun onTakePhotoV2(path: String?, width: Int, height: Int) {
|
||||||
|
LogHelper.d("CompositeLayupTask onTakePhotoV2 width=$width height=$height path=$path")
|
||||||
|
if (path == null) {
|
||||||
|
if (isPhotoFallback) {
|
||||||
|
isPhotoFallback = false
|
||||||
|
takePhoto()
|
||||||
|
} else {
|
||||||
|
isCaptureInFlight = false
|
||||||
|
runOnUiThread {
|
||||||
|
updateHint("相机异常,请重试")
|
||||||
|
}
|
||||||
|
"相机异常".showMessage()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isCaptureInFlight = false
|
||||||
|
runOnUiThread {
|
||||||
|
updateHint("OCR识别中,请稍后...")
|
||||||
|
viewModel.recognize(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initData() {
|
||||||
|
super.initData()
|
||||||
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
bindClicks()
|
||||||
|
applyScreenMode(ScreenMode.START)
|
||||||
|
observeViewModel()
|
||||||
|
if (taskNoFromIntent.isBlank()) {
|
||||||
|
updateHint("任务编号缺失")
|
||||||
|
"任务编号缺失".showMessage()
|
||||||
|
} else {
|
||||||
|
viewModel.loadTaskDetail(taskNoFromIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindClicks() {
|
||||||
|
binding.btnAction.setOnClickListener {
|
||||||
|
when (screenMode) {
|
||||||
|
ScreenMode.START, ScreenMode.STEP_SUCCESS, ScreenMode.FAILED -> triggerCapture()
|
||||||
|
ScreenMode.FINISHED -> finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.btnSecondary.setOnClickListener {
|
||||||
|
if (screenMode == ScreenMode.FINISHED) {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeViewModel() {
|
||||||
|
viewModel.taskDetail.observe(this) { detail ->
|
||||||
|
if (detail != null) {
|
||||||
|
binding.tvTaskNo.text = "工号:${detail.taskNo.ifBlank { taskNoFromIntent }}"
|
||||||
|
binding.tvTaskName.text = detail.taskName.ifBlank { "铺贴任务列表" }
|
||||||
|
updateProgressText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.taskDetailError.observe(this) { msg ->
|
||||||
|
if (msg.isNotBlank()) {
|
||||||
|
updateHint(msg)
|
||||||
|
msg.showMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.recognizeState.observe(this) { state ->
|
||||||
|
when (state) {
|
||||||
|
CompositeLayupRecognizeState.LOADING -> {
|
||||||
|
binding.btnAction.isClickable = false
|
||||||
|
updateHint("OCR识别中,请稍后...")
|
||||||
|
}
|
||||||
|
CompositeLayupRecognizeState.SUCCESS -> {
|
||||||
|
binding.btnAction.isClickable = true
|
||||||
|
handleRecognizeResult()
|
||||||
|
}
|
||||||
|
CompositeLayupRecognizeState.FAILED -> {
|
||||||
|
binding.btnAction.isClickable = true
|
||||||
|
val error = viewModel.recognizeError.value?.ifBlank { "OCR识别失败" } ?: "OCR识别失败"
|
||||||
|
applyScreenMode(ScreenMode.FAILED, error)
|
||||||
|
}
|
||||||
|
else -> binding.btnAction.isClickable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRecognizeResult() {
|
||||||
|
val result = viewModel.recognizeResult.value ?: return
|
||||||
|
when {
|
||||||
|
result.success && result.finished -> applyScreenMode(ScreenMode.FINISHED)
|
||||||
|
result.success -> applyScreenMode(ScreenMode.STEP_SUCCESS)
|
||||||
|
else -> applyScreenMode(ScreenMode.FAILED, result.errorMessage.ifBlank { "识别铺贴错误,请重新拍取" })
|
||||||
|
}
|
||||||
|
viewModel.resetRecognizeState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun triggerCapture() {
|
||||||
|
if (isCaptureInFlight || taskNoFromIntent.isBlank()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isCaptureInFlight = true
|
||||||
|
binding.btnAction.isClickable = false
|
||||||
|
updateHint("拍照中,请稍后...")
|
||||||
|
takePhoto()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun takePhoto() {
|
||||||
|
isPhotoFallback = true
|
||||||
|
val fileName = "composite_layup_${System.currentTimeMillis()}.png"
|
||||||
|
val file = File(
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
|
||||||
|
fileName
|
||||||
|
)
|
||||||
|
GlassMediaServiceHelper.takePhoto(PhotoResolution.RESOLUTION_720P, file.absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateProgressText() {
|
||||||
|
binding.tvProgress.text = "任务进度:${viewModel.currentStepSeq}/${viewModel.totalSteps}"
|
||||||
|
binding.tvStepHint.text = "识别到当前层第${viewModel.currentStepSeq}层 / 共${viewModel.totalSteps}层"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyScreenMode(mode: ScreenMode, errorMessage: String = "") {
|
||||||
|
screenMode = mode
|
||||||
|
updateProgressText()
|
||||||
|
binding.groupStart.visibility = if (mode == ScreenMode.START) View.VISIBLE else View.GONE
|
||||||
|
binding.groupResult.visibility = if (mode == ScreenMode.START) View.GONE else View.VISIBLE
|
||||||
|
binding.btnSecondary.visibility = if (mode == ScreenMode.FINISHED) View.VISIBLE else View.GONE
|
||||||
|
when (mode) {
|
||||||
|
ScreenMode.START -> {
|
||||||
|
binding.resultIcon.setImageResource(R.mipmap.ocr_photo)
|
||||||
|
binding.resultTitle.text = "请开始本层铺贴工作"
|
||||||
|
binding.resultSubtitle.text = "语音输入“开始任务”或点击按钮后拍照识别"
|
||||||
|
binding.btnAction.text = "开工"
|
||||||
|
updateHint("左滑右滑选择任务,单击开工,开始该任务。")
|
||||||
|
}
|
||||||
|
ScreenMode.STEP_SUCCESS -> {
|
||||||
|
binding.resultIcon.setImageResource(R.mipmap.ocr_true)
|
||||||
|
binding.resultTitle.text = "识别铺贴层与料号信息正确"
|
||||||
|
binding.resultSubtitle.text = "请开始下一层铺贴工作"
|
||||||
|
binding.btnAction.text = "下一步"
|
||||||
|
updateHint("当前层识别通过,可进入下一层识别。")
|
||||||
|
}
|
||||||
|
ScreenMode.FAILED -> {
|
||||||
|
binding.resultIcon.setImageResource(R.mipmap.ocr_false)
|
||||||
|
binding.resultTitle.text = errorMessage.ifBlank { "识别铺贴错误,请重新拍取" }
|
||||||
|
binding.resultSubtitle.text = "请重新拍照识别"
|
||||||
|
binding.btnAction.text = "重拍"
|
||||||
|
updateHint(errorMessage.ifBlank { "识别失败,请重试" })
|
||||||
|
}
|
||||||
|
ScreenMode.FINISHED -> {
|
||||||
|
binding.resultIcon.setImageResource(R.mipmap.ocr_true)
|
||||||
|
binding.resultTitle.text = "恭喜完成全部铺贴逐层任务!"
|
||||||
|
binding.resultSubtitle.text = "请点击返回,回到铺贴任务列表开启下一任务"
|
||||||
|
binding.btnAction.text = "返回"
|
||||||
|
binding.btnSecondary.text = "完成任务"
|
||||||
|
updateHint("当前任务已全部完成。")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateHint(text: String) {
|
||||||
|
binding.hint.text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
isCaptureInFlight = false
|
||||||
|
binding.btnAction.isClickable = true
|
||||||
|
GlassMediaServiceHelper.addPhotoCallback(photoCallback)
|
||||||
|
OfflineCmdServiceHelper.addListenerCompositeLayup()
|
||||||
|
OfflineCmdServiceHelper.addOnLineListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
GlassMediaServiceHelper.removePhotoCallback(photoCallback)
|
||||||
|
OfflineCmdServiceHelper.removeListenerCompositeLayup()
|
||||||
|
OfflineCmdServiceHelper.removeOnLineListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_TASK_NO = "extra_task_no"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -124,13 +124,13 @@ class TaskListActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun routeToTask(item: TaskItem) {
|
private fun routeToTask(item: TaskItem) {
|
||||||
when (item.taskType) {
|
when {
|
||||||
"复材MES任务" -> startActivity(
|
item.taskType == "复材MES任务" -> startActivity(
|
||||||
Intent(this, FoActivity::class.java)
|
Intent(this, FoActivity::class.java)
|
||||||
.putExtra("aiDescription", item.displayDesc())
|
.putExtra("aiDescription", item.displayDesc())
|
||||||
.putExtra("taskType", item.taskType)
|
.putExtra("taskType", item.taskType)
|
||||||
)
|
)
|
||||||
"审核任务" -> startActivity(
|
item.taskType == "审核任务" -> startActivity(
|
||||||
Intent(this, ReviewActivity::class.java)
|
Intent(this, ReviewActivity::class.java)
|
||||||
.putExtra("taskId", item.id)
|
.putExtra("taskId", item.id)
|
||||||
.putExtra("taskType", item.taskType)
|
.putExtra("taskType", item.taskType)
|
||||||
@ -149,19 +149,19 @@ class TaskListActivity :
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
"天镜检验任务" -> startActivity(
|
item.taskType == "天镜检验任务" -> startActivity(
|
||||||
Intent(this, SprayingActivity::class.java)
|
Intent(this, SprayingActivity::class.java)
|
||||||
.putExtra("taskId", item.params.firstNotBlank("taskId", "task_id", "id"))
|
.putExtra("taskId", item.params.firstNotBlank("taskId", "task_id", "id"))
|
||||||
.putExtra("aiDescription", item.displayDesc())
|
.putExtra("aiDescription", item.displayDesc())
|
||||||
.putExtra("taskType", item.taskType)
|
.putExtra("taskType", item.taskType)
|
||||||
)
|
)
|
||||||
"检验任务", "产业大脑检验任务" -> startActivity(
|
item.taskType == "检验任务" || item.taskType == "产业大脑检验任务" -> startActivity(
|
||||||
Intent(this, InspectionActivity::class.java)
|
Intent(this, InspectionActivity::class.java)
|
||||||
.putExtra("glassTaskId", item.params.firstNotBlank("taskId", "task_id", "id"))
|
.putExtra("glassTaskId", item.params.firstNotBlank("taskId", "task_id", "id"))
|
||||||
.putExtra("taskName", item.params?.get("taskName").orEmpty())
|
.putExtra("taskName", item.params?.get("taskName").orEmpty())
|
||||||
.putExtra("taskNumber", item.params?.get("taskNumber").orEmpty())
|
.putExtra("taskNumber", item.params?.get("taskNumber").orEmpty())
|
||||||
)
|
)
|
||||||
"复材铺贴任务" -> startActivity(
|
item.taskType.contains("复材铺贴任务") -> startActivity(
|
||||||
Intent(this, CompositeLayupTaskActivity::class.java)
|
Intent(this, CompositeLayupTaskActivity::class.java)
|
||||||
.putExtra(
|
.putExtra(
|
||||||
CompositeLayupTaskActivity.EXTRA_TASK_NO,
|
CompositeLayupTaskActivity.EXTRA_TASK_NO,
|
||||||
|
|||||||
@ -0,0 +1,118 @@
|
|||||||
|
package com.nova.brain.glass.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.nova.brain.glass.MyApplication
|
||||||
|
import com.nova.brain.glass.model.data.CompositeLayupRecognizeResult
|
||||||
|
import com.nova.brain.glass.model.data.CompositeLayupTaskDetail
|
||||||
|
import com.nova.brain.glass.repository.Service4
|
||||||
|
import com.xuqm.base.di.manager.HttpManager
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
enum class CompositeLayupRecognizeState { IDLE, LOADING, SUCCESS, FAILED }
|
||||||
|
|
||||||
|
class CompositeLayupTaskVM : ViewModel() {
|
||||||
|
|
||||||
|
val taskDetail = MutableLiveData<CompositeLayupTaskDetail?>()
|
||||||
|
val taskDetailError = MutableLiveData<String>()
|
||||||
|
val recognizeState = MutableLiveData(CompositeLayupRecognizeState.IDLE)
|
||||||
|
val recognizeResult = MutableLiveData<CompositeLayupRecognizeResult?>()
|
||||||
|
val recognizeError = MutableLiveData<String>()
|
||||||
|
|
||||||
|
private val disposables = CompositeDisposable()
|
||||||
|
var taskNo: String = ""
|
||||||
|
var currentStepSeq: Int = 1
|
||||||
|
private set
|
||||||
|
var totalSteps: Int = 1
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun loadTaskDetail(taskNo: String) {
|
||||||
|
this.taskNo = taskNo
|
||||||
|
val disposable = HttpManager.getApi(MyApplication.appComponent4, Service4::class.java)
|
||||||
|
.queryTask(taskNo)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ response ->
|
||||||
|
if (response.success && response.data != null) {
|
||||||
|
bindTaskDetail(response.data)
|
||||||
|
} else {
|
||||||
|
taskDetailError.value = response.message.ifBlank { "获取任务详情失败" }
|
||||||
|
}
|
||||||
|
}, { e ->
|
||||||
|
taskDetailError.value = e.message ?: "获取任务详情失败"
|
||||||
|
})
|
||||||
|
disposables.add(disposable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recognize(photoPath: String) {
|
||||||
|
val file = File(photoPath)
|
||||||
|
if (!file.exists()) {
|
||||||
|
recognizeState.value = CompositeLayupRecognizeState.FAILED
|
||||||
|
recognizeError.value = "图片不存在"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (taskNo.isBlank()) {
|
||||||
|
recognizeState.value = CompositeLayupRecognizeState.FAILED
|
||||||
|
recognizeError.value = "任务编号为空"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recognizeState.value = CompositeLayupRecognizeState.LOADING
|
||||||
|
val taskNoBody = taskNo.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
val stepSeqBody = currentStepSeq.toString().toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
val requestFile = file.asRequestBody("application/octet-stream".toMediaTypeOrNull())
|
||||||
|
val filePart = MultipartBody.Part.createFormData("file", file.name, requestFile)
|
||||||
|
val disposable = HttpManager.getApi(MyApplication.appComponent4, Service4::class.java)
|
||||||
|
.ocrRecognize(taskNoBody, stepSeqBody, filePart)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ response ->
|
||||||
|
val result = response.data
|
||||||
|
if (response.success && result != null) {
|
||||||
|
recognizeResult.value = result
|
||||||
|
recognizeState.value = CompositeLayupRecognizeState.SUCCESS
|
||||||
|
if (result.success && !result.finished) {
|
||||||
|
currentStepSeq = (currentStepSeq + 1).coerceAtMost(totalSteps.coerceAtLeast(1))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
recognizeState.value = CompositeLayupRecognizeState.FAILED
|
||||||
|
recognizeError.value = response.message.ifBlank { "OCR识别失败" }
|
||||||
|
}
|
||||||
|
}, { e ->
|
||||||
|
recognizeState.value = CompositeLayupRecognizeState.FAILED
|
||||||
|
recognizeError.value = e.message ?: "OCR识别失败"
|
||||||
|
})
|
||||||
|
disposables.add(disposable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetRecognizeState() {
|
||||||
|
recognizeState.value = CompositeLayupRecognizeState.IDLE
|
||||||
|
recognizeResult.value = null
|
||||||
|
recognizeError.value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindTaskDetail(detail: CompositeLayupTaskDetail) {
|
||||||
|
taskDetail.value = detail
|
||||||
|
this.taskNo = detail.taskNo.ifBlank { taskNo }
|
||||||
|
totalSteps = detail.taskSteps.coerceAtLeast(detail.detailList?.size ?: 1).coerceAtLeast(1)
|
||||||
|
currentStepSeq = when {
|
||||||
|
detail.taskCurrentStep > 0 -> detail.taskCurrentStep
|
||||||
|
!detail.detailList.isNullOrEmpty() -> {
|
||||||
|
detail.detailList.firstOrNull { it.detailStatus != 9 }?.stepSeq
|
||||||
|
?: detail.detailList.last().stepSeq.coerceAtLeast(1)
|
||||||
|
}
|
||||||
|
else -> 1
|
||||||
|
}.coerceIn(1, totalSteps)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
disposables.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#00000000" />
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="#40FF5E" />
|
||||||
|
<corners android:radius="12dp" />
|
||||||
|
</shape>
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#4B7F5A" />
|
||||||
|
<corners android:radius="10dp" />
|
||||||
|
</shape>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#00000000" />
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="#40FF5E" />
|
||||||
|
</shape>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#050505" />
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="#16A5FF" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#4B7F5A" />
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
</shape>
|
||||||
@ -0,0 +1,194 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/app_color_black">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTaskHeader"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@drawable/bg_item"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="复材铺贴任务:逐层识别作业"
|
||||||
|
android:textColor="#ff40FF5E"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/content"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:background="@drawable/bg_task_title_selected"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tvTaskHeader">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTaskName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginVertical="3dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="铺贴任务1"
|
||||||
|
android:textColor="#ff40FF5E"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#ff40FF5E" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTaskNo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="任务编号:PT20260422001"
|
||||||
|
android:textColor="#ff40FF5E"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProgress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="任务进度:1/12"
|
||||||
|
android:textColor="#ff40FF5E"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvStepHint"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
|
android:layout_marginVertical="4dp"
|
||||||
|
android:text="当前识别层:第1层 / 共12层"
|
||||||
|
android:textColor="#ff40FF5E"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/baseRecyclerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="88dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/hint"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="单击或语音输入"开始",进入下一步"
|
||||||
|
android:textColor="#ff40FF5E"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/baseRecyclerView" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/groupStart"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/btnAction"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/hint">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="150dp"
|
||||||
|
android:src="@mipmap/ocr_photo" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/groupResult"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/btnAction"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/hint">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/resultIcon"
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="150dp"
|
||||||
|
android:src="@mipmap/ocr_true" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/resultTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="识别铺贴层与料号信息正确"
|
||||||
|
android:textColor="#ff40FF5E"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/resultSubtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="请开始下一层铺贴工作"
|
||||||
|
android:textColor="#ff40FF5E"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSecondary"
|
||||||
|
android:layout_width="138dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:background="@drawable/bg_composite_button_solid"
|
||||||
|
android:backgroundTint="@null"
|
||||||
|
android:text="完成任务"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="#ff40FF5E"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnAction"
|
||||||
|
android:layout_width="148dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginBottom="28dp"
|
||||||
|
android:background="@drawable/bg_composite_button_outline"
|
||||||
|
android:backgroundTint="@null"
|
||||||
|
android:text="开工"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="#ff40FF5E"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
||||||
正在加载...
在新工单中引用
屏蔽一个用户