diff --git a/app/build.gradle b/app/build.gradle index 34baf20..5136be5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -56,7 +56,7 @@ android { buildConfigField("String", "API_COOKIE", "\"__itrace_wid=87125211-8742-4f12-b5ca-32b9b6c860e4; locale=zh-Hans; _webtracing_device_id=t_13501877-b9b303fc-d3f52eb530e026b0\"") buildConfigField("String", "API_ENVIRONMENT", "\"2\"") buildConfigField("String", "API_CURRENT_USER_ID", "\"rokid\"") - // 任务列表+审核+fo + // 任务列表+审核+fo+决策中心 buildConfigField("String", "API_BASE_URL_1", "\"http://10.230.4.80:12119\"") // 意图识别 buildConfigField("String", "API_BASE_URL_2", "\"http://10.230.4.80\"") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ff79028..39bb21f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -78,6 +78,9 @@ + diff --git a/app/src/main/java/com/nova/brain/glass/ui/CompositeLayupResultActivity.kt b/app/src/main/java/com/nova/brain/glass/ui/CompositeLayupResultActivity.kt new file mode 100644 index 0000000..b46e5ce --- /dev/null +++ b/app/src/main/java/com/nova/brain/glass/ui/CompositeLayupResultActivity.kt @@ -0,0 +1,319 @@ +package com.nova.brain.glass.ui + +import android.content.Intent +import android.os.Environment +import android.os.Handler +import android.os.Looper +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.nova.brain.glass.R +import com.nova.brain.glass.databinding.ActivityCompositeLayupResultBinding +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.model.ItemItem +import com.nova.brain.glass.viewmodel.CompositeLayupResultState +import com.nova.brain.glass.viewmodel.CompositeLayupResultVM +import com.rokid.security.glass3.sdk.base.data.media.PhotoResolution +import com.rokid.security.system.server.media.callback.PhotoFileCallback +import com.xuqm.base.adapter.CommonAdapter +import com.xuqm.base.adapter.ViewHolder +import com.xuqm.base.extensions.showMessage +import com.xuqm.base.ui.BaseActivity +import java.io.File +import java.util.UUID + +class CompositeLayupResultActivity : BaseActivity() { + + override fun getLayoutId(): Int = R.layout.activity_composite_layup_result + override fun fullscreen(): Boolean = true + + private val viewModel: CompositeLayupResultVM by lazy { + ViewModelProvider(this)[CompositeLayupResultVM::class.java] + } + + private enum class UiMode { + RECOGNIZE_SUCCESS, + RECOGNIZE_FAILED, + LAYUP_PROMPT, + LAYUP_WORKING, + CONFIRM_FINISH, + COMPLETE + } + + private val taskNo: String by lazy { intent.getStringExtra(EXTRA_TASK_NO).orEmpty() } + private val taskName: String by lazy { intent.getStringExtra(EXTRA_TASK_NAME).orEmpty() } + private val partNo: String by lazy { intent.getStringExtra(EXTRA_PART_NO).orEmpty() } + private val photoPath: String by lazy { intent.getStringExtra(EXTRA_PHOTO_PATH).orEmpty() } + private val stepSeq: Int by lazy { intent.getIntExtra(EXTRA_STEP_SEQ, 1) } + private val totalSteps: Int by lazy { intent.getIntExtra(EXTRA_TOTAL_STEPS, 1) } + private val direction: String by lazy { intent.getStringExtra(EXTRA_DIRECTION).orEmpty() } + private val vacuum: String by lazy { intent.getStringExtra(EXTRA_VACUUM).orEmpty() } + private val uiHandler = Handler(Looper.getMainLooper()) + + private var isPhoto = false + private var uiMode = UiMode.RECOGNIZE_SUCCESS + + private val layupWorkingRunnable: Runnable = Runnable { + if (uiMode == UiMode.LAYUP_PROMPT) { + applyMode(UiMode.LAYUP_WORKING) + } + } + private val completeRunnable: Runnable = Runnable { + if (uiMode == UiMode.COMPLETE) { + goTaskList() + } + } + + private val listener: OfflineCmdListener = object : OfflineCmdListener { + override fun onOfflineCmd(cmd: String) { + runOnUiThread { + when (cmd) { + "退出", "返回", "退回" -> finish() + "重拍", "重新拍照", "重新拍摄" -> if (uiMode == UiMode.RECOGNIZE_FAILED) retake() + "开始铺贴" -> if (uiMode == UiMode.RECOGNIZE_SUCCESS) startLayup() + "确认并继续", "继续" -> if (uiMode == UiMode.CONFIRM_FINISH) confirmAndContinue() + "否" -> if (uiMode == UiMode.CONFIRM_FINISH) applyMode(UiMode.LAYUP_WORKING) + "完成", "完成任务" -> if (uiMode == UiMode.CONFIRM_FINISH && stepSeq >= totalSteps) finishCurrentTask() + "返回任务列表" -> if (uiMode == UiMode.COMPLETE) goTaskList() + } + } + } + } + + private val photoCallbackId = UUID.randomUUID().toString() + private val photoCallback: PhotoFileCallback = object : PhotoFileCallback.Stub() { + override fun onTakePhoto(path: String) = Unit + + override fun getCallbackId(): String = photoCallbackId + + override fun onTakePhotoV2(path: String?, width: Int, height: Int) { + if (path == null) { + if (isPhoto) { + isPhoto = false + takePhoto() + } else { + binding.hint.text = "单击或语音输入“重新拍照”,进入下一步" + "相机异常".showMessage() + } + return + } + startActivity(Intent(this@CompositeLayupResultActivity, CompositeLayupResultActivity::class.java).apply { + putExtra(EXTRA_PHOTO_PATH, path) + putExtra(EXTRA_TASK_NO, taskNo) + putExtra(EXTRA_TASK_NAME, taskName) + putExtra(EXTRA_PART_NO, partNo) + putExtra(EXTRA_STEP_SEQ, stepSeq) + putExtra(EXTRA_TOTAL_STEPS, totalSteps) + putExtra(EXTRA_DIRECTION, direction) + putExtra(EXTRA_VACUUM, vacuum) + }) + finish() + } + } + + override fun initData() { + super.initData() + window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + binding.baseRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false) + binding.baseRecyclerView.adapter = actionAdapter + binding.main.setOnClickListener { + if (uiMode == UiMode.LAYUP_WORKING) { + applyMode(UiMode.CONFIRM_FINISH) + } + } + viewModel.resultState.observe(this) { state -> + when (state) { + CompositeLayupResultState.LOADING -> { + binding.hint.text = "OCR识别中,请稍后..." + renderActions(emptyList()) + } + CompositeLayupResultState.SUCCESS -> { + val result = viewModel.recognizeResult.value ?: return@observe + if (result.success) { + applyMode(UiMode.RECOGNIZE_SUCCESS) + } else { + applyMode(UiMode.RECOGNIZE_FAILED, result.errorMessage.ifBlank { "识别失败,请重试!" }) + } + } + CompositeLayupResultState.FAILED -> { + applyMode(UiMode.RECOGNIZE_FAILED, viewModel.errorMessage.value ?: "识别失败,请重试!") + } + else -> Unit + } + } + if (taskNo.isNotBlank() && photoPath.isNotBlank()) { + viewModel.recognize(taskNo, stepSeq, photoPath) + } + } + + private fun applyMode(mode: UiMode, errorMessage: String = "") { + uiMode = mode + uiHandler.removeCallbacks(layupWorkingRunnable) + uiHandler.removeCallbacks(completeRunnable) + binding.icon.visibility = android.view.View.VISIBLE + when (mode) { + UiMode.RECOGNIZE_SUCCESS -> { + binding.icon.setImageResource(R.mipmap.ocr_true) + binding.title.text = "铺贴层与零件信息正确" + binding.subtitle1.text = "识别到这是${stepSeq}/${totalSteps}张" + binding.subtitle2.text = buildInstruction() + renderActions(listOf("开始铺贴")) + binding.hint.text = "单击或语音输入“开始铺贴”,进入下一步" + } + UiMode.RECOGNIZE_FAILED -> { + binding.icon.setImageResource(R.mipmap.ocr_false) + binding.title.text = errorMessage + binding.subtitle1.text = "识别到这是${stepSeq}/${totalSteps}张" + binding.subtitle2.text = "请重新拿取" + renderActions(listOf("重新拍照")) + binding.hint.text = "单击或语音输入“重新拍照”,进入下一步" + } + UiMode.LAYUP_PROMPT -> { + binding.icon.visibility = android.view.View.INVISIBLE + binding.title.text = "请进行铺贴工作" + binding.subtitle1.text = "完成后,滑动唤醒眼镜!" + binding.subtitle2.text = "" + renderActions(emptyList()) + binding.hint.text = "" + uiHandler.postDelayed(layupWorkingRunnable, 5000L) + } + UiMode.LAYUP_WORKING -> { + binding.icon.visibility = android.view.View.INVISIBLE + binding.title.text = "正在铺贴第${stepSeq}张" + binding.subtitle1.text = buildWorkingInstruction() + binding.subtitle2.text = "" + renderActions(emptyList()) + binding.hint.text = "" + } + UiMode.CONFIRM_FINISH -> { + binding.icon.visibility = android.view.View.INVISIBLE + binding.title.text = "请确认是否已完成第${stepSeq}/${totalSteps}张铺贴" + binding.subtitle1.text = "" + binding.subtitle2.text = "" + renderActions(listOf(if (stepSeq >= totalSteps) "完成" else "确认并继续", "否")) + binding.hint.text = "点击或语音输入对应按钮,继续流程" + } + UiMode.COMPLETE -> { + binding.icon.setImageResource(R.mipmap.ocr_true) + binding.title.text = "恭喜完成当前铺贴任务!" + binding.subtitle1.text = "3S后自动返回铺贴任务界面" + binding.subtitle2.text = "" + renderActions(listOf("返回任务列表")) + binding.hint.text = "" + uiHandler.postDelayed(completeRunnable, 3000L) + } + } + } + + private fun buildInstruction(): String { + val vacuumText = if (vacuum.isBlank()) "" else "请真空铺贴、" + val directionText = if (direction.isBlank()) "" else "纹理方向:$direction" + return (vacuumText + directionText).ifBlank { "请开始铺贴" } + } + + private fun buildWorkingInstruction(): String { + val vacuumText = if (vacuum.isBlank()) "" else "请真空铺贴、" + val directionText = if (direction.isBlank()) "" else "纹理方向$direction、" + return "${vacuumText}${directionText}撕衬纸\n完成后,滑动唤醒眼镜!" + } + + private fun renderActions(actions: List) { + actionAdapter.setmDatas(actions.map(::ItemItem)) + binding.baseRecyclerView.visibility = if (actions.isEmpty()) android.view.View.INVISIBLE else android.view.View.VISIBLE + binding.baseRecyclerView.layoutParams = binding.baseRecyclerView.layoutParams.apply { + height = dpToPx(if (actions.size > 1) 152 else 88) + } + } + + private fun startLayup() { + applyMode(UiMode.LAYUP_PROMPT) + } + + private fun retake() { + binding.hint.text = "拍照中,请稍后..." + isPhoto = true + takePhoto() + } + + private fun takePhoto() { + 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 confirmAndContinue() { + startActivity(Intent(this, CompositeLayupTaskActivity::class.java).apply { + putExtra(CompositeLayupTaskActivity.EXTRA_TASK_NO, taskNo) + putExtra(CompositeLayupTaskActivity.EXTRA_STEP_SEQ, stepSeq + 1) + }) + finish() + } + + private fun finishCurrentTask() { + applyMode(UiMode.COMPLETE) + } + + private fun goTaskList() { + startActivity(Intent(this, TaskListActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + }) + finish() + } + + private fun dpToPx(dp: Int): Int = + (dp * resources.displayMetrics.density).toInt() + + override fun onResume() { + super.onResume() + GlassMediaServiceHelper.addPhotoCallback(photoCallback) + OfflineCmdServiceHelper.addListenerCompositeLayup() + OfflineCmdServiceHelper.addOnLineListener(listener) + } + + override fun onPause() { + super.onPause() + uiHandler.removeCallbacks(layupWorkingRunnable) + uiHandler.removeCallbacks(completeRunnable) + GlassMediaServiceHelper.removePhotoCallback(photoCallback) + OfflineCmdServiceHelper.removeListenerCompositeLayup() + OfflineCmdServiceHelper.removeOnLineListener(listener) + } + + override fun onDestroy() { + super.onDestroy() + window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + + private val actionAdapter: CommonAdapter = + object : CommonAdapter(R.layout.item_manual_result_action) { + override fun convert(holder: ViewHolder, item: ItemItem, position: Int) { + holder.setText(R.id.text, item.text) + .setClickListener(R.id.actionRoot) { + when (item.text) { + "重新拍照" -> retake() + "开始铺贴" -> startLayup() + "确认并继续" -> confirmAndContinue() + "否" -> applyMode(UiMode.LAYUP_WORKING) + "完成" -> finishCurrentTask() + "返回任务列表" -> goTaskList() + } + } + } + } + + companion object { + const val EXTRA_PHOTO_PATH = "extra_photo_path" + const val EXTRA_TASK_NO = "extra_task_no" + const val EXTRA_TASK_NAME = "extra_task_name" + const val EXTRA_PART_NO = "extra_part_no" + const val EXTRA_STEP_SEQ = "extra_step_seq" + const val EXTRA_TOTAL_STEPS = "extra_total_steps" + const val EXTRA_DIRECTION = "extra_direction" + const val EXTRA_VACUUM = "extra_vacuum" + } +} diff --git a/app/src/main/java/com/nova/brain/glass/ui/CompositeLayupTaskActivity.kt b/app/src/main/java/com/nova/brain/glass/ui/CompositeLayupTaskActivity.kt index 0ccc085..016fe85 100644 --- a/app/src/main/java/com/nova/brain/glass/ui/CompositeLayupTaskActivity.kt +++ b/app/src/main/java/com/nova/brain/glass/ui/CompositeLayupTaskActivity.kt @@ -2,11 +2,6 @@ package com.nova.brain.glass.ui import android.content.Intent import android.os.Environment -import android.os.Handler -import android.os.Looper -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.nova.brain.glass.R import com.nova.brain.glass.databinding.ActivityCompositeLayupTaskBinding @@ -14,84 +9,49 @@ 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.model.ItemItem -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.adapter.CommonAdapter +import com.xuqm.base.adapter.BasePagedAdapter +import com.xuqm.base.adapter.CommonPagedAdapter import com.xuqm.base.adapter.ViewHolder import com.xuqm.base.common.LogHelper import com.xuqm.base.extensions.showMessage -import com.xuqm.base.ui.BaseActivity +import com.xuqm.base.ui.BaseListFormLayoutNormalActivity import java.io.File import java.util.UUID -class CompositeLayupTaskActivity : BaseActivity() { +class CompositeLayupTaskActivity : + BaseListFormLayoutNormalActivity() { + override fun getLayoutId(): Int = R.layout.activity_composite_layup_task override fun fullscreen(): Boolean = true + override fun getRecyclerOrientation(): Int = RecyclerView.VERTICAL - private enum class ScreenMode { - TASK_INFO, - CAPTURE, - RECOGNIZE_SUCCESS, - RECOGNIZE_FAILED, - LAYUP_PROMPT, - LAYUP_WORKING, - CONFIRM_FINISH, - COMPLETE - } - - private val viewModel: CompositeLayupTaskVM by lazy { - ViewModelProvider(this)[CompositeLayupTaskVM::class.java] - } private val taskNoFromIntent: String by lazy { intent.getStringExtra(EXTRA_TASK_NO).orEmpty() } - private val mainHandler = Handler(Looper.getMainLooper()) + private val stepSeqFromIntent: Int by lazy { + intent.getIntExtra(EXTRA_STEP_SEQ, 0) + } - private var isPhotoFallback = false + private var isPhoto = false private var isCaptureInFlight = false - private var screenMode = ScreenMode.TASK_INFO - private var autoReturned = false + private var hasNavigatedNextPage = false - private val layupWorkingRunnable = Runnable { - if (screenMode == ScreenMode.LAYUP_PROMPT) { - applyScreenMode(ScreenMode.LAYUP_WORKING) - } - } - - private val completeRunnable = Runnable { - if (screenMode == ScreenMode.COMPLETE && !autoReturned) { - autoReturned = true - goTaskList() - } - } - - private val listener = object : OfflineCmdListener { + private val listener: OfflineCmdListener = object : OfflineCmdListener { override fun onOfflineCmd(cmd: String) { runOnUiThread { when (cmd) { "退出", "返回", "退回" -> finish() - "开始", "开始任务" -> if (screenMode == ScreenMode.TASK_INFO) startCaptureFlow() - "重新拍照", "重拍", "重新拍摄" -> { - if (screenMode == ScreenMode.RECOGNIZE_FAILED) startCaptureFlow() - } - "开始铺贴" -> if (screenMode == ScreenMode.RECOGNIZE_SUCCESS) startLayup() - "确认并继续", "继续" -> if (screenMode == ScreenMode.CONFIRM_FINISH) confirmAndContinue() - "否" -> if (screenMode == ScreenMode.CONFIRM_FINISH) backToWorking() - "完成", "完成任务" -> { - if (screenMode == ScreenMode.CONFIRM_FINISH && viewModel.canFinishAfterCurrentStep()) { - finishCurrentTask() - } - } - "返回任务列表" -> if (screenMode == ScreenMode.COMPLETE) goTaskList() + "开始", "拍照", "开始拍照", "开始任务" -> startCapture() } } } } private val photoCallbackId = UUID.randomUUID().toString() - private val photoCallback = object : PhotoFileCallback.Stub() { + private val photoCallback: PhotoFileCallback = object : PhotoFileCallback.Stub() { override fun onTakePhoto(path: String) { LogHelper.d("CompositeLayupTask onTakePhoto: $path") } @@ -101,293 +61,128 @@ class CompositeLayupTaskActivity : BaseActivity(R.layout.item_manual_result_action) { - override fun convert(holder: ViewHolder, item: ItemItem, position: Int) { - holder.setText(R.id.text, item.text) - .setClickListener(R.id.actionRoot) { - when (item.text) { - "开始任务" -> startCaptureFlow() - "重新拍照" -> startCaptureFlow() - "开始铺贴" -> startLayup() - "确认并继续" -> confirmAndContinue() - "否" -> backToWorking() - "完成" -> finishCurrentTask() - "返回任务列表" -> goTaskList() - } - } + val taskDetail = viewModel.taskDetail.value + val currentDetail = viewModel.currentDetail() + val currentTaskStepSeq = currentDetail?.stepSeq ?: viewModel.currentStepSeq + isCaptureInFlight = false + hasNavigatedNextPage = true + GlassMediaServiceHelper.removePhotoCallback(photoCallback) + OfflineCmdServiceHelper.removeListenerCompositeLayup() + OfflineCmdServiceHelper.removeOnLineListener(listener) + startActivity(Intent(this@CompositeLayupTaskActivity, CompositeLayupResultActivity::class.java).apply { + putExtra(CompositeLayupResultActivity.EXTRA_PHOTO_PATH, path) + putExtra(CompositeLayupResultActivity.EXTRA_TASK_NO, viewModel.taskNo) + putExtra(CompositeLayupResultActivity.EXTRA_TASK_NAME, taskDetail?.taskName.orEmpty()) + putExtra(CompositeLayupResultActivity.EXTRA_PART_NO, taskDetail?.partNo.orEmpty()) + putExtra(CompositeLayupResultActivity.EXTRA_STEP_SEQ, currentTaskStepSeq) + putExtra(CompositeLayupResultActivity.EXTRA_TOTAL_STEPS, viewModel.totalSteps) + putExtra(CompositeLayupResultActivity.EXTRA_DIRECTION, currentDetail?.direction.orEmpty()) + putExtra(CompositeLayupResultActivity.EXTRA_VACUUM, currentDetail?.vacuum.orEmpty()) + }) + finish() } } override fun initData() { super.initData() window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - binding.baseRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false) - binding.baseRecyclerView.adapter = actionAdapter - binding.main.setOnClickListener { - if (screenMode == ScreenMode.LAYUP_WORKING) { - applyScreenMode(ScreenMode.CONFIRM_FINISH) - } - } - observeViewModel() - applyScreenMode(ScreenMode.TASK_INFO) - if (taskNoFromIntent.isBlank()) { - val message = "任务编号缺失" - updateHint(message) - message.showMessage() - } else { - viewModel.loadTaskDetail(taskNoFromIntent) - } - } - - private fun observeViewModel() { + binding.tvTaskHeader.text = "复材铺贴任务" viewModel.taskDetail.observe(this) { detail -> - if (detail != null) { - binding.tvTaskName.text = detail.taskName.ifBlank { "铺贴任务" } - renderTaskInfo() - applyScreenMode(ScreenMode.TASK_INFO) + if (detail == null) return@observe + val currentDetail = viewModel.currentDetail() + binding.tvTaskName.text = currentDetail?.ply?.ifBlank { + detail.taskName.ifBlank { "铺贴任务" } + } ?: detail.taskName.ifBlank { "铺贴任务" } + binding.content1.text = "零件号:${detail.partNo.ifBlank { "-" }}" + binding.content2.text = "任务编号:${detail.taskNo.ifBlank { taskNoFromIntent }}" + binding.content3.text = "任务进度:${viewModel.currentStepSeq}/${detail.taskSteps}" + binding.hint.text = "单击或语音输入“开始”,进入下一步" + } + viewModel.taskDetailError.observe(this) { message -> + if (message.isNotBlank()) { + binding.hint.text = "单击或语音输入“开始”,进入下一步" + message.showMessage() } } - viewModel.taskDetailError.observe(this) { msg -> - if (msg.isNotBlank()) { - updateHint(msg) - msg.showMessage() - } - } - viewModel.recognizeState.observe(this) { state -> - when (state) { - CompositeLayupRecognizeState.LOADING -> { - updateHint("OCR识别中,请稍后...") - renderActions(emptyList()) - } - CompositeLayupRecognizeState.SUCCESS -> handleRecognizeResult() - CompositeLayupRecognizeState.FAILED -> { - val error = viewModel.recognizeError.value?.ifBlank { "OCR识别失败" } ?: "OCR识别失败" - applyScreenMode(ScreenMode.RECOGNIZE_FAILED, error) - } - else -> Unit - } - } - } - - private fun renderTaskInfo() { - val detail = viewModel.taskDetail.value ?: return - binding.content1.text = "零件号:${detail.partNo.ifBlank { "-" }}" - binding.content2.text = "任务编号:${detail.taskNo.ifBlank { taskNoFromIntent }}" - binding.content3.text = "任务进度:${viewModel.currentProgressText()}" - } - - private fun handleRecognizeResult() { - val result = viewModel.recognizeResult.value ?: return - if (result.success) { - applyScreenMode(ScreenMode.RECOGNIZE_SUCCESS) + if (taskNoFromIntent.isBlank()) { + binding.hint.text = "任务编号缺失" } else { - applyScreenMode( - ScreenMode.RECOGNIZE_FAILED, - result.errorMessage.ifBlank { "识别失败,请重试!" } - ) - } - viewModel.resetRecognizeState() - } - - private fun startCaptureFlow() { - if (isCaptureInFlight || taskNoFromIntent.isBlank()) { - return - } - isCaptureInFlight = true - applyScreenMode(ScreenMode.CAPTURE) - 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 startLayup() { - applyScreenMode(ScreenMode.LAYUP_PROMPT) - } - - private fun backToWorking() { - applyScreenMode(ScreenMode.LAYUP_WORKING) - } - - private fun confirmAndContinue() { - if (viewModel.canFinishAfterCurrentStep()) { - finishCurrentTask() - return - } - if (viewModel.moveToNextStep()) { - renderTaskInfo() - applyScreenMode(ScreenMode.TASK_INFO) + viewModel.loadTaskDetail(taskNoFromIntent, stepSeqFromIntent.takeIf { it > 0 }) } } - private fun finishCurrentTask() { - applyScreenMode(ScreenMode.COMPLETE) - } - - private fun applyScreenMode(mode: ScreenMode, errorMessage: String = "") { - screenMode = mode - mainHandler.removeCallbacks(layupWorkingRunnable) - mainHandler.removeCallbacks(completeRunnable) - renderTaskInfo() - binding.groupStart.visibility = if (mode == ScreenMode.TASK_INFO) View.VISIBLE else View.GONE - binding.groupCapture.visibility = if (mode == ScreenMode.CAPTURE) View.VISIBLE else View.GONE - binding.groupResult.visibility = - if (mode == ScreenMode.RECOGNIZE_SUCCESS || mode == ScreenMode.RECOGNIZE_FAILED) View.VISIBLE else View.GONE - binding.groupWork.visibility = - if (mode == ScreenMode.LAYUP_PROMPT || mode == ScreenMode.LAYUP_WORKING) View.VISIBLE else View.GONE - binding.groupConfirm.visibility = if (mode == ScreenMode.CONFIRM_FINISH) View.VISIBLE else View.GONE - binding.groupComplete.visibility = if (mode == ScreenMode.COMPLETE) View.VISIBLE else View.GONE - - when (mode) { - ScreenMode.TASK_INFO -> { - binding.captureMessage.text = "请对准下一张复材铺贴物料上的字符内容" - binding.startIcon.setImageResource(R.mipmap.ocr_photo) - renderActions(listOf("开始任务")) - updateHint("单击或语音输入\"开始\",进入下一步") - } - ScreenMode.CAPTURE -> { - binding.captureMessage.text = "请对准复材铺贴物料上的字符内容" - renderActions(emptyList()) - updateHint("拍照中,请稍后...") - } - ScreenMode.RECOGNIZE_SUCCESS -> { - val currentDetail = viewModel.currentDetail() - binding.resultIcon.setImageResource(R.mipmap.ocr_true) - binding.resultTitle.text = "铺贴层与零件信息正确" - binding.resultSubtitle1.text = "识别到这是${viewModel.currentProgressText()}张" - binding.resultSubtitle2.text = buildRecognizeInstruction(currentDetail?.vacuum, currentDetail?.direction) - renderActions(listOf("开始铺贴")) - updateHint("单击或语音输入\"开始铺贴\",进入下一步") - } - ScreenMode.RECOGNIZE_FAILED -> { - binding.resultIcon.setImageResource(R.mipmap.ocr_false) - binding.resultTitle.text = errorMessage.ifBlank { "识别失败,请重试!" } - binding.resultSubtitle1.text = "识别到这是${viewModel.currentProgressText()}张" - binding.resultSubtitle2.text = "请重新拿取" - renderActions(listOf("重新拍照")) - updateHint("单击或语音输入\"重新拍照\",进入下一步") - } - ScreenMode.LAYUP_PROMPT -> { - binding.workText.text = "请进行铺贴工作\n完成后,滑动唤醒眼镜!" - renderActions(emptyList()) - updateHint("") - mainHandler.postDelayed(layupWorkingRunnable, 5000L) - } - ScreenMode.LAYUP_WORKING -> { - binding.workText.text = buildWorkingInstruction() - renderActions(emptyList()) - updateHint("") - } - ScreenMode.CONFIRM_FINISH -> { - binding.confirmTitle.text = "请确认是否已完成第${viewModel.currentProgressText()}张铺贴" - val primary = if (viewModel.canFinishAfterCurrentStep()) "完成" else "确认并继续" - renderActions(listOf(primary, "否")) - updateHint("点击或语音输入对应按钮,继续流程") - } - ScreenMode.COMPLETE -> { - autoReturned = false - binding.completeSubtitle.text = "3S后自动返回铺贴任务界面" - renderActions(listOf("返回任务列表")) - updateHint("") - mainHandler.postDelayed(completeRunnable, 3000L) - } - } - } - - private fun renderActions(actions: List) { - actionAdapter.setmDatas(actions.map(::ItemItem)) - binding.baseRecyclerView.layoutParams = binding.baseRecyclerView.layoutParams.apply { - height = dpToPx( - when { - actions.isEmpty() -> 88 - actions.size == 1 -> 88 - else -> 152 - } - ) - } - binding.baseRecyclerView.visibility = if (actions.isEmpty()) View.INVISIBLE else View.VISIBLE - } - - private fun updateHint(text: String) { - binding.hint.text = text - binding.hint.visibility = if (text.isBlank()) View.INVISIBLE else View.VISIBLE - } - - private fun buildRecognizeInstruction(vacuum: String?, direction: String?): String { - val vacuumText = if (vacuum.isNullOrBlank()) "" else "请真空铺贴、" - val directionText = if (direction.isNullOrBlank()) "" else "纹理方向$direction" - return (vacuumText + directionText).ifBlank { "请开始铺贴" } - } - - private fun buildWorkingInstruction(): String { - val detail = viewModel.currentDetail() - val stepText = "正在铺贴第${viewModel.currentStepSeq}张" - val vacuumText = if (detail?.vacuum.isNullOrBlank()) "" else "请真空铺贴、" - val directionText = if (detail?.direction.isNullOrBlank()) "" else "纹理方向${detail?.direction}、" - return "$stepText\n${vacuumText}${directionText}撕衬纸\n完成后,滑动唤醒眼镜!" - } - - private fun goTaskList() { - startActivity(Intent(this, TaskListActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) - }) - finish() - } - - private fun dpToPx(dp: Int): Int = - (dp * resources.displayMetrics.density).toInt() - override fun onResume() { super.onResume() + hasNavigatedNextPage = false + isCaptureInFlight = false GlassMediaServiceHelper.addPhotoCallback(photoCallback) - OfflineCmdServiceHelper.addListenerCompositeLayup() OfflineCmdServiceHelper.addOnLineListener(listener) + OfflineCmdServiceHelper.addListenerCompositeLayup() } override fun onPause() { super.onPause() - mainHandler.removeCallbacks(layupWorkingRunnable) - mainHandler.removeCallbacks(completeRunnable) - GlassMediaServiceHelper.removePhotoCallback(photoCallback) OfflineCmdServiceHelper.removeListenerCompositeLayup() OfflineCmdServiceHelper.removeOnLineListener(listener) + GlassMediaServiceHelper.removePhotoCallback(photoCallback) } override fun onDestroy() { + isCaptureInFlight = false + hasNavigatedNextPage = false super.onDestroy() window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } + private fun startCapture() { + if (isCaptureInFlight || hasNavigatedNextPage || taskNoFromIntent.isBlank()) { + return + } + binding.hint.text = "拍照中,请稍后..." + isPhoto = true + isCaptureInFlight = true + takePhoto() + } + + private fun takePhoto() { + val fileName = "composite_layup_${System.currentTimeMillis()}.png" + val publicPicturesDir = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + val file = File(publicPicturesDir, fileName) + GlassMediaServiceHelper.takePhoto(PhotoResolution.RESOLUTION_720P, file.absolutePath) + } + + private val adapter: BasePagedAdapter = + object : CommonPagedAdapter(R.layout.item_photo) { + override fun convert(holder: ViewHolder, item: ItemItem, position: Int) { + holder.setText(R.id.text, item.text) + .setClickListener(R.id.photo) { + if (item.text == "开始任务") { + startCapture() + } + } + } + } + + override fun adapter(): BasePagedAdapter = adapter + companion object { const val EXTRA_TASK_NO = "extra_task_no" + const val EXTRA_STEP_SEQ = "extra_step_seq" } } diff --git a/app/src/main/java/com/nova/brain/glass/ui/WelcomeActivity.kt b/app/src/main/java/com/nova/brain/glass/ui/WelcomeActivity.kt index 7f2b02d..21b9faa 100644 --- a/app/src/main/java/com/nova/brain/glass/ui/WelcomeActivity.kt +++ b/app/src/main/java/com/nova/brain/glass/ui/WelcomeActivity.kt @@ -56,8 +56,8 @@ class WelcomeActivity : BaseActivity() { binding.tv.setOnClickListener { runWithNetwork { // triggerRecognize() -// startActivity(Intent(this, TaskListActivity::class.java)) - startActivity(Intent(this, ChatActivity::class.java)) + startActivity(Intent(this, TaskListActivity::class.java)) +// startActivity(Intent(this, ChatActivity::class.java)) } } } diff --git a/app/src/main/java/com/nova/brain/glass/viewmodel/CompositeLayupResultVM.kt b/app/src/main/java/com/nova/brain/glass/viewmodel/CompositeLayupResultVM.kt new file mode 100644 index 0000000..b93eaf6 --- /dev/null +++ b/app/src/main/java/com/nova/brain/glass/viewmodel/CompositeLayupResultVM.kt @@ -0,0 +1,68 @@ +package com.nova.brain.glass.viewmodel + +import androidx.lifecycle.MutableLiveData +import com.nova.brain.glass.MyApplication +import com.nova.brain.glass.model.ItemItem +import com.nova.brain.glass.model.data.CompositeLayupRecognizeResult +import com.nova.brain.glass.repository.Service4 +import com.xuqm.base.di.manager.HttpManager +import com.xuqm.base.viewmodel.BaseListViewModel +import com.xuqm.base.viewmodel.callback.Response +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 CompositeLayupResultState { IDLE, LOADING, SUCCESS, FAILED } + +class CompositeLayupResultVM : BaseListViewModel() { + val resultState = MutableLiveData(CompositeLayupResultState.IDLE) + val recognizeResult = MutableLiveData() + val errorMessage = MutableLiveData() + + private val disposables = CompositeDisposable() + + override fun loadData(page: Int, onResponse: Response) { + onResponse.onResponse(arrayListOf()) + } + + fun recognize(taskNo: String, stepSeq: Int, photoPath: String) { + val file = File(photoPath) + if (!file.exists()) { + resultState.value = CompositeLayupResultState.FAILED + errorMessage.value = "图片不存在" + return + } + resultState.value = CompositeLayupResultState.LOADING + val taskNoBody = taskNo.toRequestBody("text/plain".toMediaTypeOrNull()) + val stepSeqBody = stepSeq.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 -> + if (response.success && response.data != null) { + recognizeResult.value = response.data + resultState.value = CompositeLayupResultState.SUCCESS + } else { + resultState.value = CompositeLayupResultState.FAILED + errorMessage.value = response.message.ifBlank { "OCR识别失败" } + } + }, { e -> + resultState.value = CompositeLayupResultState.FAILED + errorMessage.value = e.message ?: "OCR识别失败" + }) + disposables.add(disposable) + } + + override fun onCleared() { + super.onCleared() + disposables.clear() + } +} diff --git a/app/src/main/java/com/nova/brain/glass/viewmodel/CompositeLayupTaskVM.kt b/app/src/main/java/com/nova/brain/glass/viewmodel/CompositeLayupTaskVM.kt index 78c9246..cd2878e 100644 --- a/app/src/main/java/com/nova/brain/glass/viewmodel/CompositeLayupTaskVM.kt +++ b/app/src/main/java/com/nova/brain/glass/viewmodel/CompositeLayupTaskVM.kt @@ -2,12 +2,14 @@ package com.nova.brain.glass.viewmodel import androidx.lifecycle.MutableLiveData import com.nova.brain.glass.MyApplication +import com.nova.brain.glass.model.ItemItem import com.nova.brain.glass.model.data.CompositeLayupRecognizeResult import com.nova.brain.glass.model.data.CompositeLayupDetailItem 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 com.xuqm.base.viewmodel.BaseListViewModel +import com.xuqm.base.viewmodel.callback.Response import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.schedulers.Schedulers @@ -19,7 +21,7 @@ import java.io.File enum class CompositeLayupRecognizeState { IDLE, LOADING, SUCCESS, FAILED } -class CompositeLayupTaskVM : ViewModel() { +class CompositeLayupTaskVM : BaseListViewModel() { val taskDetail = MutableLiveData() val taskDetailError = MutableLiveData() @@ -36,15 +38,19 @@ class CompositeLayupTaskVM : ViewModel() { var lastRecognizeFinished: Boolean = false private set - fun loadTaskDetail(taskNo: String) { + override fun loadData(page: Int, onResponse: Response) { + onResponse.onResponse(arrayListOf(ItemItem("开始任务"))) + } + + fun loadTaskDetail(taskNo: String, stepOverride: Int? = null) { 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) + if (response.code == 200 && response.data != null) { + bindTaskDetail(response.data, stepOverride) } else { taskDetailError.value = response.message.ifBlank { "获取任务详情失败" } } @@ -101,10 +107,7 @@ class CompositeLayupTaskVM : ViewModel() { fun currentDetail(): CompositeLayupDetailItem? = taskDetail.value?.detailList ?.sortedBy { it.stepSeq } - ?.firstOrNull { it.stepSeq == currentStepSeq } - ?: taskDetail.value?.detailList - ?.sortedBy { it.stepSeq } - ?.getOrNull((currentStepSeq - 1).coerceAtLeast(0)) + ?.getOrNull((currentStepSeq - 1).coerceAtLeast(0)) fun currentProgressText(): String = "${currentStepSeq}/${totalSteps}" @@ -121,16 +124,13 @@ class CompositeLayupTaskVM : ViewModel() { return true } - private fun bindTaskDetail(detail: CompositeLayupTaskDetail) { + private fun bindTaskDetail(detail: CompositeLayupTaskDetail, stepOverride: Int? = null) { taskDetail.value = detail this.taskNo = detail.taskNo.ifBlank { taskNo } totalSteps = detail.taskSteps.coerceAtLeast(detail.detailList?.size ?: 1).coerceAtLeast(1) currentStepSeq = when { + stepOverride != null && stepOverride > 0 -> stepOverride 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) lastRecognizeFinished = currentStepSeq >= totalSteps diff --git a/app/src/main/res/layout/activity_composite_layup_result.xml b/app/src/main/res/layout/activity_composite_layup_result.xml new file mode 100644 index 0000000..fdaad1e --- /dev/null +++ b/app/src/main/res/layout/activity_composite_layup_result.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_composite_layup_task.xml b/app/src/main/res/layout/activity_composite_layup_task.xml index e47f62f..f347c15 100644 --- a/app/src/main/res/layout/activity_composite_layup_task.xml +++ b/app/src/main/res/layout/activity_composite_layup_task.xml @@ -55,7 +55,7 @@ android:layout_height="wrap_content" android:layout_marginStart="15dp" android:layout_marginTop="4dp" - android:text="零件号:20293989-001" + android:text="零件号:" android:textColor="#ff40FF5E" android:textSize="14sp" /> @@ -65,7 +65,7 @@ android:layout_height="wrap_content" android:layout_marginStart="15dp" android:layout_marginTop="4dp" - android:text="任务编号:PT20260422001" + android:text="任务编号:" android:textColor="#ff40FF5E" android:textSize="14sp" /> @@ -75,7 +75,7 @@ android:layout_height="wrap_content" android:layout_marginStart="15dp" android:layout_marginVertical="4dp" - android:text="任务进度:1/12" + android:text="任务进度:" android:textColor="#ff40FF5E" android:textSize="14sp" /> @@ -84,8 +84,8 @@ android:id="@+id/baseRecyclerView" android:layout_width="0dp" android:layout_height="88dp" - android:layout_marginTop="24dp" android:clipToPadding="false" + android:layout_marginTop="12dp" android:overScrollMode="never" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -93,238 +93,17 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_spraying.xml b/app/src/main/res/layout/activity_spraying.xml index f089fdd..ba8012f 100644 --- a/app/src/main/res/layout/activity_spraying.xml +++ b/app/src/main/res/layout/activity_spraying.xml @@ -103,13 +103,5 @@ android:text="单击或语音输入“开始”,进入下一步" android:textColor="#ff40FF5E" android:textSize="14sp"/> - -