From cf4458526bb9add74f6359493a691ab6c6b40487 Mon Sep 17 00:00:00 2001 From: dongliang Date: Fri, 8 May 2026 12:57:51 +0930 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BB=BB=E5=8A=A1=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E9=A1=B5=E8=AF=AD=E9=9F=B3=E6=92=AD=E6=8A=A5=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=20(REQ-20260508-0006)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 详情页右侧悬浮播放按钮(36dp半透明圆形) - 播报内容:任务名+地点+时间+积分(>0)+备注,空字段跳过 - 播放中按钮高亮+图标切换,再点停止 - 离开页面自动停止播放 - TTS播放期间抑制提示音,避免叠加 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../device/sensor/FiseVibrationController.kt | 9 ++- .../watch/ui/task/TaskDetailFragment.kt | 70 +++++++++++++++++++ app/src/main/res/drawable/bg_tts_button.xml | 6 ++ .../res/drawable/bg_tts_button_active.xml | 6 ++ app/src/main/res/drawable/ic_speaker.xml | 19 +++++ app/src/main/res/drawable/ic_speaker_stop.xml | 22 ++++++ .../main/res/layout/fragment_task_detail.xml | 12 ++++ 7 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/drawable/bg_tts_button.xml create mode 100644 app/src/main/res/drawable/bg_tts_button_active.xml create mode 100644 app/src/main/res/drawable/ic_speaker.xml create mode 100644 app/src/main/res/drawable/ic_speaker_stop.xml diff --git a/app/src/main/java/com/xiaoqu/watch/device/sensor/FiseVibrationController.kt b/app/src/main/java/com/xiaoqu/watch/device/sensor/FiseVibrationController.kt index 4d3a324..4edd0ea 100644 --- a/app/src/main/java/com/xiaoqu/watch/device/sensor/FiseVibrationController.kt +++ b/app/src/main/java/com/xiaoqu/watch/device/sensor/FiseVibrationController.kt @@ -24,7 +24,8 @@ import javax.inject.Singleton @Singleton class FiseVibrationController @Inject constructor( @ApplicationContext private val context: Context, - private val configManager: VibrationConfigManager + private val configManager: VibrationConfigManager, + private val edgeTtsManager: com.xiaoqu.watch.service.manager.EdgeTtsManager ) : VibrationController { /** 系统振动器 */ @@ -98,9 +99,11 @@ class FiseVibrationController @Inject constructor( } } - // 音频:系统级开关 + 用户级开关 + 有音频文件 - if (voiceOk) { + // 音频:系统级开关 + 用户级开关 + 有音频文件 + TTS 未在播放 + if (voiceOk && !edgeTtsManager.isPlaying) { playAudio(pattern.audioResId, configManager.voiceVolume) + } else if (voiceOk && edgeTtsManager.isPlaying) { + Timber.d("振动: TTS 播放中,跳过提示音") } } diff --git a/app/src/main/java/com/xiaoqu/watch/ui/task/TaskDetailFragment.kt b/app/src/main/java/com/xiaoqu/watch/ui/task/TaskDetailFragment.kt index 7075b60..5faf544 100644 --- a/app/src/main/java/com/xiaoqu/watch/ui/task/TaskDetailFragment.kt +++ b/app/src/main/java/com/xiaoqu/watch/ui/task/TaskDetailFragment.kt @@ -13,6 +13,7 @@ import com.xiaoqu.watch.databinding.FragmentTaskDetailBinding import com.xiaoqu.watch.network.ApiResult import com.xiaoqu.watch.network.api.TaskApi import com.xiaoqu.watch.network.safeApiCall +import com.xiaoqu.watch.service.manager.EdgeTtsManager import com.xiaoqu.watch.service.manager.NfcTaskManager import com.xiaoqu.watch.ui.common.BaseFragment import com.xiaoqu.watch.ui.widget.QuTipDialog @@ -30,6 +31,7 @@ class TaskDetailFragment : BaseFragment() { @Inject lateinit var taskApi: TaskApi @Inject lateinit var nfcTaskManager: NfcTaskManager + @Inject lateinit var edgeTtsManager: EdgeTtsManager /** 当前任务数据 */ private var taskDetail: TaskDetail? = null @@ -53,6 +55,9 @@ class TaskDetailFragment : BaseFragment() { findNavController().popBackStack() } + // TTS 播放按钮 + binding.btnTts.setOnClickListener { toggleTts() } + // 从导航参数获取任务 ID val taskId = arguments?.getLong("taskId", 0) ?: 0 if (taskId > 0) { @@ -284,4 +289,69 @@ class TaskDetailFragment : BaseFragment() { } } } + + // ===== TTS 语音播报 ===== + + /** 切换播放/停止 */ + private fun toggleTts() { + if (edgeTtsManager.isPlaying) { + stopTts() + } else { + startTts() + } + } + + /** 开始播报当前任务内容 */ + private fun startTts() { + val detail = taskDetail ?: return + + // 拼接播报文本:有值的字段才读,空字段跳过 + val parts = mutableListOf() + if (detail.name.isNotBlank()) parts.add("任务:${detail.displayName}") + if (detail.positionText.isNotBlank()) parts.add("地点:${detail.positionText}") + if (detail.sendTime.isNotBlank()) parts.add("时间:${detail.sendTime}") + if (detail.point > 0) parts.add("积分:${detail.pointText}") + if (detail.content.isNotBlank()) parts.add("备注:${detail.content}") + + if (parts.isEmpty()) return + + val text = parts.joinToString(",") + Timber.d("TTS 播报: $text") + + // 更新按钮状态为播放中 + updateTtsButton(playing = true) + + // 播放完成回调:恢复按钮 + edgeTtsManager.onComplete = { + activity?.runOnUiThread { updateTtsButton(playing = false) } + } + + edgeTtsManager.speak(text) { errorMsg -> + Timber.w("TTS 播报失败: $errorMsg") + updateTtsButton(playing = false) + } + } + + /** 停止播报 */ + private fun stopTts() { + edgeTtsManager.stop() + updateTtsButton(playing = false) + } + + /** 更新 TTS 按钮的 UI 状态 */ + private fun updateTtsButton(playing: Boolean) { + binding.btnTts.setImageResource( + if (playing) R.drawable.ic_speaker_stop else R.drawable.ic_speaker + ) + binding.btnTts.setBackgroundResource( + if (playing) R.drawable.bg_tts_button_active else R.drawable.bg_tts_button + ) + } + + override fun onDestroyView() { + // 离开页面自动停止播放 + edgeTtsManager.stop() + edgeTtsManager.onComplete = null + super.onDestroyView() + } } diff --git a/app/src/main/res/drawable/bg_tts_button.xml b/app/src/main/res/drawable/bg_tts_button.xml new file mode 100644 index 0000000..40625ab --- /dev/null +++ b/app/src/main/res/drawable/bg_tts_button.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/bg_tts_button_active.xml b/app/src/main/res/drawable/bg_tts_button_active.xml new file mode 100644 index 0000000..b88c5ba --- /dev/null +++ b/app/src/main/res/drawable/bg_tts_button_active.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_speaker.xml b/app/src/main/res/drawable/ic_speaker.xml new file mode 100644 index 0000000..3db204b --- /dev/null +++ b/app/src/main/res/drawable/ic_speaker.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_speaker_stop.xml b/app/src/main/res/drawable/ic_speaker_stop.xml new file mode 100644 index 0000000..4b4527a --- /dev/null +++ b/app/src/main/res/drawable/ic_speaker_stop.xml @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_task_detail.xml b/app/src/main/res/layout/fragment_task_detail.xml index 0801f49..9e2f4fa 100644 --- a/app/src/main/res/layout/fragment_task_detail.xml +++ b/app/src/main/res/layout/fragment_task_detail.xml @@ -101,6 +101,18 @@ + + +