Files
Cyrene-For-Android/app
AskaEth 576dd58a6b fix(w80): 让「画不画语音形态」只剩「键在且 status 不是 failed」两条判据
后端 M57-M59 预告(devdocs/02 第 1255/1260 行我们的答复):`voice` 键在不再等于
「这是一段能播的声音」。合成降级 / 入站转写失败的行 `status = "failed"`,正文是一句话,
`fallbackReason` 里给出降级原因——按老判据会被画成一个点下去必然播不了的语音气泡。

判据拆成两问,各自落在一处(domain/chat/VoiceMessage.kt):
- `hasVoice` = `voice` 键存在 —— 「这条**来自**一段声音」;
- `isPlayableVoice` = `status != "failed"` —— 认不出的 status 一律当可播(fail-open:
  多画一枚语音形态最坏是点了播不出来,有既有失败文案兜着;反方向会把真有副本的语音
  静默吞成纯文字,用户再也点不到);
- `isVoiceMessage` = 两者相与 —— 「画不画语音形态」。

failed 行改画「正文 + 一行灰色小标」:`voiceFallbackNote` 给固定前半句 + 服务端
`fallbackReason` **原样**(不猜闭集、不做映射——取值集合后端尚未定稿;缺席/空白时退化成
固定那一句,不白屏)。新组件 `VoiceFallbackNote` 用 footnote2 +
onSurfaceVariantSummary,与语音形态旁边那几句脚注同一档。

`isContentlessUserMessage` 挂 `hasVoice` 而**不**是 `isVoiceMessage`:failed 行虽没有语音
形态,但有正文和那行小标,同样不是「什么都没有」——判错了屏幕上会多一句假的
「内容没有被传过来」。`messageTimeCarrier` 同理按 `hasVoice` 挑落脚点。

DTO 上补第八个键 `fallbackReason`(只在 failed 时有,`explicitNulls = false` 时不写出),
所以转写成功的行键集仍是原来七个。

验证:`assembleDebug` + `testDebugUnitTest` 全绿,142 个测试文件 / 1766 个用例 / 0 失败
(基线 1755,新增 11 条;VoiceMessageTest 20、VoiceDtoTest 13、WsEventPersistedVoiceTest 7)。
2026-10-27 21:22:28 +08:00
..