feat: 添加拍药盒功能和语音直达入口

- 实现拍药盒扫描流程,支持本地OCR识别药品信息
- 在日记页面添加拍药盒和记症状的三选一入口
- 优化按钮点击区域,确保符合苹果HIG最小命中区标准
- 添加用药记录到时间线的独立分类显示
- 实现长按+号语音直达功能,支持语音意图分类跳转
- 更新项目配置文件,启用代码分析和死代码剥离选项
- 增加多项本地化字符串支持新功能
```
This commit is contained in:
link2026
2026-06-13 09:16:25 +08:00
parent f58d6064ba
commit 6c6a950140
30 changed files with 1856 additions and 64 deletions

View File

@@ -0,0 +1,85 @@
import Testing
import Foundation
import SwiftData
@testable import
/// MedicationScanService.parseMedicationsJSON (JSON )
struct MedicationScanServiceTests {
@Test func parsesStandardObject() throws {
let raw = """
{"medications":[{"name":"","strength":"80mg×7","usage":""}]}
"""
let meds = try MedicationScanService.parseMedicationsJSON(raw)
#expect(meds.count == 1)
#expect(meds[0].name == "缬沙坦胶囊")
#expect(meds[0].strength == "80mg×7粒")
#expect(meds[0].entryText == "缬沙坦胶囊 80mg×7粒")
}
@Test func parsesBareArrayWithFence() throws {
let raw = """
```json
[{"name":"","strength":"0.5g×30","usage":",1,2"}]
```
"""
let meds = try MedicationScanService.parseMedicationsJSON(raw)
#expect(meds.count == 1)
#expect(meds[0].entryText == "二甲双胍缓释片 0.5g×30片 · 口服,一次1片,一日2次")
}
@Test func parsesChineseKeysAndDedupes() throws {
let raw = """
{"medications":[
{"":"","":"100mg","":""},
{"name":"","strength":"100mg","usage":""}
]}
"""
let meds = try MedicationScanService.parseMedicationsJSON(raw)
#expect(meds.count == 1)
}
@Test func emptyNameRowsAreDropped() throws {
let raw = #"{"medications":[{"name":"","strength":"10mg","usage":""}]}"#
let meds = try MedicationScanService.parseMedicationsJSON(raw)
#expect(meds.isEmpty)
}
@Test func trailingCommaIsRepaired() throws {
let raw = #"{"medications":[{"name":"","strength":"10mg×6","usage":"",},]}"#
let meds = try MedicationScanService.parseMedicationsJSON(raw)
#expect(meds.count == 1)
#expect(meds[0].name == "氯雷他定片")
}
@Test func invalidJSONThrows() {
#expect(throws: (any Error).self) {
try MedicationScanService.parseMedicationsJSON("识别不出来,抱歉")
}
}
}
/// 线(tab )
@MainActor
struct MedicationTimelineTests {
private func makeContext() throws -> ModelContext {
let schema = Schema([DiaryEntry.self])
let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
return ModelContext(try ModelContainer(for: schema, configurations: [config]))
}
@Test func medicationTaggedDiaryMapsToMedicationKind() throws {
let ctx = try makeContext()
let med = DiaryEntry(content: "缬沙坦胶囊 80mg×7粒", tags: [DiaryEntry.medicationTag])
let plain = DiaryEntry(content: "今天睡得不错")
ctx.insert(med); ctx.insert(plain)
try ctx.save()
let medEntry = TimelineEntry.from(diary: med)
#expect(medEntry.kind == .medication)
#expect(medEntry.title == "缬沙坦胶囊 80mg×7粒")
#expect(TimelineEntry.from(diary: plain).kind == .diary)
}
}