Rodger-Wang
|
f2dfa09d97
|
client:EMAI 耳机与车载共用位置日期,定位只有市名时用高德补路名;Xcode 打开前把生成的 SPM 包对齐到 iOS 16.0,避免反复报平台版本不够。构建号 1.0.2+26
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2 days ago |
Rodger-Wang
|
2a913260e6
|
车载香薰:会话带上用户位置与当前时间,填百炼提示词里的 ${location} / ${date}
- 定位插件新增 getCurrentPlace:安卓高德定位(华为上高德只回坐标时退回系统 Geocoder),
iOS CoreLocation + CLGeocoder;不在插件里弹权限框,失败回原因
- DeviceLocationService:启动/回前台刷新、10 分钟节流、落盘缓存;香薰首次连上(前台)申请一次
「使用期间」定位权限,拒绝后不再追问;建会话只读缓存,不拖慢唤醒
- BailianMultimodalService.start:仅对登记的应用(车载香薰)带
client_info.location{latitude,longitude,city_name} 与 biz_params.user_prompt_params{location,date},
date 精确到分;拿不到地名时用坐标描述。EMAI 耳机的 Start 报文不变
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
2 weeks ago |
Rodger-Wang
|
4ec8932aef
|
车载香薰 AI 对话:唤醒问候、按键插话、自动回连、退出聊天、前后台与状态同步(均只对香薰生效,恒玄耳机流程不变)
- 会话:服务端放弃回复(requestAccepted)时停播解门控,修掉互等 90s 的卡死;会话中再按唤醒键=打断重听;
唤醒后用应用音色随机回一句「我在」;退出(0x07 03)无应答不再干等 2s;设备上线复位 AI 状态,
防 App 被杀后设备停在 AI 模式、再唤醒 0 帧上行;Android 会话期间开前台服务,防切后台被冻结断 WS
- 指令:extra_info.commands 为 JSON 字符串时也能解出 quit(新车载应用下发形状)
- 连接:香薰断线/冷启动自动回连(带退避、OTA 与主动断开不回连);回连带上记住的 MAC;
设备断开后界面显示最后连接的那台(DeviceHub 落盘 lastUsedVendor)
- iOS 经典蓝牙判断按 MAC 首字节±1/设备名匹配,连着就不再弹配对提示
- Android 播放器:车载会话(playbackOnly)回复间垫静音,防 A2DP 流挂起吞掉「我在」
- 聊天页:设备对话中底部按钮可「改用手机」;离开正显示香薰对话的页面时结束会话并通知设备
- 车载助手百炼应用 ID 改为 mm_127ddb82ba1244a99d2feaef0c1f
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
2 weeks ago |
Rodger-Wang
|
d6892eb3d6
|
后台管理下载落地页;实名认证显隐与游客登录同一个口子
下载落地页(版本与分发页)
- 「下载与说明」区顶部新增「下载页」一行:编辑 / 保存 / 查看 + 地址输入框
- 编辑:CodeMirror 编辑线上 HTML,改动自动存浏览器草稿;保存才发布到分发桶
(服务端 PutObject,强制 text/html;charset=utf-8 + no-cache),地址写入 app_release.download_page
- 查看:右侧抽屉预览,可切「当前编辑内容 / 线上版本」、手机 / 电脑尺寸
- 地址只能改文件名,域名与 /<应用>/ 目录由服务端卡死(TestParseDistPageURL)
- 新接口 api_getdistpage / api_savedistpage;admin 新增 codemirror 依赖
实名认证显隐
- 能看到游客登录的那一版(送审版本)才显示实名认证并按实名限制功能;
其余版本实名整个隐藏,功能不受实名限制。不分 Android / iOS
- 服务端:comm.AppRelease.ShowIdVerify(= ShowTourists)→ DBChannelApp.idverify 下发
- 客户端:VersionUpdateService.showIdVerify → IdVerifyGuard 隐藏即放行;
「我的」页实名行、卡片「未认证」角标跟着显隐;老服务端无 idverify 时退回 tourists
- 后台「游客显隐控制」页说明改为同时控制游客登录与实名认证
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
6f1686a8fd
|
client: EaiCar 设备设置面板(按能力出现、按设备回的 items 渲染)
「设备 → 设备管理」里 EaiCar 那一行整块可点(解绑按钮除外),弹底部面板拉 0x15
显示并可改:启停 0x0D / 音量 0x0B / 亮度 0x0A / 行车感应 0x11 / 摇摆 0x14 /
随机表情频次 0x13 / 随机播一个表情 0x06。
- 面板是厂商无关的:只认 DeviceCapability.settingsPanel 与 DeviceFeatures.settings*
通用键,按 settings.query 返回的 items 画控件,缺哪项不画哪项。EaiCar 插件把
通用键映射成字节(EaiCarSession.settingsCommand,纯函数,金测试逐字节钉死)。
以后耳机/支架要露设置,各自插件声明即可。
- 状态只有 0x15 一个来源:六条写命令只回 0x64 成功码,不回显新值也没有主动上报。
插件写成功后本地回填并派 settings.changed;面板从不自己推断状态。原先
_applySummary 解完 14 字节只留固件和电量,这次整份留下。
- 0x11 双向语义不同:AA 11 是行车感应开关,CC 11 是设备上报 AI 唤醒,按帧头分发。
- 「随机表情」设备只有频次(9 档秒数);「现在随机播一个」没有原生命令,
由 app 在 0x00~0x09 里挑一个发 0x06。
- 未连接的 EaiCar 行也可点(按设备名猜品类),进去是「未连接 / 去连接」;
其它品类的行 onTap 仍为 null,行为一字不差。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
c1dcfcfc60
|
client: 车载香薰 Dart 侧整体改名 Smartcar → EaiCar
目录、类名、文案键、路由、测试全部改成 EaiCar。刻意保留:原生插件 device_smartcar、
持久化厂商键 'smartcar'、模块 id 'smartcarChat'、GetStorage 两个键(改了会丢老装机的编排/白名单)。
广播名准入白名单本来就只认 EaiCar,旧名 Smartcar 在测试里仍作为被拒绝的样本保留。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
7ce56f693d
|
client: 通话翻译音频链路修复(iOS GATT 写入、后台保活、状态竞态)
iOS 上恒玄走的是 GATT over BR/EDR 而非 BLE,之前按 BLE 模型处理导致一连串问题。
本次改动都有真机数据支撑(2026-09-21 压测)。
GATT 写入(bluetooth_manager)
- 写类型改为优先 .withoutResponse。ATT 是请求-响应协议,带应答写每包等 ACK,
追不上下行 50 包/秒,表现为音频越听越滞后。
- canSendWriteWithoutResponse 只作观测、不作门槛。它是 CoreBluetooth 为 BLE
设计的提示,在 BR/EDR GATT 上不反映真实能力(实测 100% 为 false 却零丢弃);
拿它当闸门会死锁:不写就不回调,不回调就永不翻真,实测 548 包只出去 11 个。
- 控制命令无条件直发、不排队。排到积压音频后面会让 AA 09/AA 06/queryCallState
全部发不出去,曾被误判成「固件换了命令号」。
- 不再对标准电池服务 0x180F 做任何 ATT 操作:它在 Echo-one 上是空壳(值恒为 0),
而挂在它上面的读/订阅会把整条 ATT 通道堵死。
后台存活(translation)
- 通话翻译期间持有 BackgroundKeepalive。两路音频全走耳机 GATT,手机侧没有
AVAudioSession 的实际 I/O,UIBackgroundModes=audio 不成立,切后台几十秒后
上行被掐断:WebSocket 还连着,但服务端收不到语音,靠默认 VAD 每 30 秒强切一句,
吐出空译文或幻觉 —— 表现为「切后台后只剩译文、原文全没」。
- BackgroundKeepalive 不再无条件把音频类别改成 .playback,避免踩掉通话翻译
正在跑的 .playAndRecord 录音链路。
状态竞态(azure_speech / device_hub)
- markReady 的就绪状态记在插件上、建桥时继承。建桥与 AST 异步初始化是两条独立
路径,初始化先完成时那句 optional chaining 打在 nil 上静默丢失,之后 ready
恒为 false、上行帧全积在环形缓冲里,表现为「打开通话翻译没反应,再开一次才好」。
- DeviceHub.inCall 改以会话快照为准。事件会随 worker 在断连时被 dispose 而丢失,
重连后 ever 又不回调当前值,且后续重复赋同值不触发,永远不会自愈。
可观测性
- 下行发送器与 GATT 写入的日志从 NSLog 改为 os_log(release/profile 包里
idevicesyslog 只收得到 os_log),并经 DeviceSession.diagnostics() 暴露给 Dart,
写入文件日志后可用 devicectl 直接拉取,不依赖 usbmuxd。
- 新增 inCall 变化、闸门拦截、通话状态翻转、前后台切换四处观测点。
其它
- DeviceSession 新增 diagnostics()(默认空表);SmartcarSession 因是 implements
而非 extends,同步补了实现。
- CLAUDE.md 记录本次全部实测结论,含 BB 8E 为通话状态权威、BB 02/03 是噪声
(已知问题,本次未改判定)。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
8b50e52572
|
client: 通话翻译字幕错位/总结残留/退不出去/切后台被冻结,及 B 路说话人门限
真机实测(Android,恒玄耳机)逐条定位后修,原生能改的都在原生层改。
字幕乱(AliyunBailianE2EHelper,Android/iOS 同改)
- 原文流与译文流各自按到达顺序自己发号,两条流的切句本来就不一致,off-by-one
一旦发生就一路错到底。改成认服务端的 item_id:译文帧的 item_id 经 itemParent
(response item → input item)映射回原文那条,取不到才退回本地发号。
- response.audio_transcript 的 text 是累计值、delta 才是增量,此前一律当增量
append,于是出现「Hello,Hello,Hello,」。改成按字段名分支:有 delta 就追加,
只有 text 就整段替换。
- connectionGeneration 守住 onOpen/onMessage/onFailure/onClosed(iOS 用 activeTask
弱引用按任务身份判),旧 socket 的迟到回调不再打进新会话;stopContinuousConversation
同步清 webSocket/isStarted。
翻译结束后「总结内容」还留在屏幕上
- AliyunAstCallback.onSessionFinished 会把整场累计文本当作一条 translated 再发
一次,表现就是一大坨总结。不再发;原文缓存改为按 utteranceId 存,上限 32 条。
结束后无法再翻译、返回按钮像被遮挡
- 退出时那颗延时 Get.delete 会误杀刚重新进来的页面。按 tag 记活跃页面数
(_PageAliveMarker + _aliveCount),还有人用就跳过销毁。
重进页面提示「未在通话中」
- 耳机对 0xD1/0xD2(进/出通话模式)的应答被当成通话状态上报,把 isCallOngoing
改掉了。这两个 type 改为只记日志,不动通话状态。
切后台功能被关掉
- 前台服务类型加 phoneCall:microphone|phoneCall (132),ROM 的进程冻结不再掐断
通话翻译。startForeground 带类型失败时回退到仅 microphone。stopService 改用
context.stopService,后台也能调。
- AST 掉线自动重连(1s/2s/4s,最多 3 次),回到前台按 _astHealthy 补一次;
重连后的新会话用 800ms 宽限期,否则会被原来的 2s 窗口吞掉重试。
B 路说话人门限(PeerSpeechGate,新增 + 11 个单测)
- 声音复刻配的是 frequency=once,只在会话开头刻一次音色。B 路会话一启动就建立,
此时对端多半还没开口,先收到的是机主的串音(实测 B 路 ASR 配 language=en 却
识别出机主刚说的中文),于是把机主音色刻成了「对端音色」,两个人听起来一样。
- 未开门期间音频进环形缓冲,开门瞬间整段补发——对端先开口时第一句的开头一个字
都不能丢。fallbackFrames 到时强行开门:门限太严会让对端整通电话不被翻译,
太松只是音色可能刻错,后者轻得多。
- 每次会话重建(含断线重连)都 reset,否则重连后照样刻到串音。
已知未尽:Android 14+ 会拒绝 phoneCall 这个前台服务类型并回退成仅 microphone,
那里的冻结保护不生效;rmsThreshold=600 尚未在真机上标定(上一轮跑到了兜底强开)。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
b61c4c91d7
|
client: 修助手闹钟的「同一条两张卡」与「日期落在昨天」
两处都是真机实测踩到的(2026-09-19):
- 拾忆页重复卡片:双写迁移期同一条闹钟本地与服务端各有一份,投影层只信本地
的 synced 标志,而那只是服务端事实的影子拷贝——上传成功后进程被杀、重装、
清缓存都会把它打回 unsynced,于是凭空多一条(一张能编辑、点另一张说没同步
到服务器)。改用服务端自己带的答案:client_key / source_id 存的就是指令 id,
对上了就不再投影(pendingLocalReminders,两个键都认,历史数据里有只写一个的)。
顺带补上 _uploadReminder 成功后的 reminders.refresh()——改的是 RxList 元素
内部字段,不通知没人知道,syncPending() 那条补传路径本来就 refresh 了。
- 闹钟定到过去:模型下发的 date 偶尔早一天(会话 Start 没告诉百炼今天几号),
客户端原样信了,闹钟落到昨天那一页、而且永远不会响。rollForwardPastDate 把
过去的一次性闹钟挪到就近的未来。只管一次性的——重复闹钟的 date 是锚点,落在
过去是正常的,挪了会把第一次触发推迟一周。治本要在百炼控制台注入当前时间。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
f9b9c8c72d
|
client: 翻译默认归档进语音纪要;列表改本地优先;登录按包收敛;EMAI 走 A2DP
## 通话/同声/面对面翻译 → 语音纪要
三种翻译模式默认边翻边录,结束时把音频**连同已有转写**一起归档
(rtype=VOICETRANSLAT),服务端跳过识别、直接用智能模板(General-Autopilot)总结,
在会议纪要里就能看到。转写段的构造见 translation_export.dart(纯函数,带金测试):
- 时间是**相对录音起点的毫秒**并扣掉暂停区间——面对面是逐句按住说话、松开就暂停的,
不扣的话越往后偏得越多;写成绝对时间戳会让点一句跳到音频末尾之外;
- 说话人用 Speaker_0/Speaker_1,与服务端转写同一口径(转写页直接把这个字段当文案显示,
裸 0/1 会和别的纪要长得不一样,喂给 LLM 抽待办时也会混出 Speaker2 这种负责人);
- AST socket 断开后 _terminateCallOnError 会取消事件订阅,重开时收不到任何转写
(真机上表现为「有日志但转写 0 段」),改成重新订阅。
## 语音纪要列表:服务端是真源、本地 sqlite 是缓存
列表接口改回骨架字段后(服务端同批),客户端改成本地优先渲染 + 后台对齐:
- Synchrodata 重写:拉骨架 → upsert → 删掉「本地有服务端没有」的记录(连本地音频、
上传队列、轮询任务一起清)。**业务错误时 data 是 null 不是空列表**,判空错了
会把本地整个清空;
- 详情进页面时才拉整条;离线操作队列那套上行是死代码(服务端接口整个注释掉了),删除。
## 登录按包收敛
苹果包只留苹果+游客、安卓包只留微信+游客、谷歌包只留邮箱+游客。
判定抽成 resolveLoginMethods 纯函数(login_methods.dart + 8 条测试),
邮箱包还要锁死国家区号选择。
## EMAI 与其它
- 手机侧触发的对话走 A2DP 而不是语音处理通道:iOS 上 A2DP 输出与 voiceChat 模式
互斥,开了后者蓝牙耳机会当场切到手机扬声器。按当前实际输出路由决定要不要开
voiceProcessing/回声消除;
- EMAI「开始对话」闪退:探测输入格式时 new 了一个一次性 AVAudioEngine 去读 inputNode,
配置变更时崩在 AVAudioIONodeImpl(EXC_BAD_ACCESS)。改成直接问 AVAudioSession;
- 语音纪要文件名可改:每行右侧加铅笔图标,复用已有的重命名弹窗(原来只有长按菜单,
实际入口已不可达);
- 拾忆上报 IANA 时区名:DateTime.now().timeZoneName 给的是「CST」这种缩写,
服务端 LoadLocation 解析不了、静默退回容器时区,海外用户的提醒时刻会全按北京时间算。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
41e3a023a3
|
client: 蓝牙名白名单(EaiRec/EaiCar);连接即按账号登记绑定;iOS 崩溃加固;弱网翻译超时
- 设备名闸门:支架只认 EaiRec/EAIMAR/Pad Note,车载香薰只认 EaiCar,固件名对不上直接不连;
一次性清掉此前被归一化名字污染的配对记录;Smartcar 资源与 agent 命名统一改为 EaiCar
- 绑定登记:新增 DeviceBindRegistry(按 uid|MAC 去重),支架也进 verifyConnected,
登录态从无到有时补报一次,换账号不再漏切绑定
- iOS 崩溃加固:补 NSPhotoLibraryAddUsageDescription(保存思维导图/统计图必崩);
StsAgent 先配音频会话再 installTap 并校验格式;支架录音两处 installTap 加 0Hz 守卫;
AzureTtsHelper 播放队列改加锁队列(三线程裸改 Array,release 下崩);
MicrophoneCapture 主线程不再用信号量等权限弹窗(看门狗误杀)
- 网络:connectionError 给中文文案;实时翻译请求 12s 超时,不再跟 30s 默认值等
- 附:AI 助手中枢设计文档 v0.2(未动代码)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
4 weeks ago |
Rodger-Wang
|
c14580d7db
|
client: 设备默认图去背;AI 页编排跟随设备;香薰 AI 回复走蓝牙;OTA 状态如实显示
设备默认图
- default.png / default_icon.png 是 assets/images/devices 里仅有的两张**不透明带底图**
(892×1152 竖版海报 + 浅蓝渐变底),而设备页展示区是 1.14:1 的横宽框,
BoxFit.contain 后成了中间一条窄的蓝色卡片、左右大片留白,深色模式下尤其突兀。
改成去背透明(948×828 / 176×154),主体自己铺满框、周围就是页面背景,代码零适配。
- device_images.dart 记下这条约定,免得以后又丢一张竖版带底图进来。
AI 页按设备铺编排(连着香薰却显示全量功能)
- _load() 的一次性迁移会改 enabledIds 却**不更新基线 _lastAppliedKey**,
尤其 _migrateDefaultLayout 会把 defaultEnabled 里缺的全部 append 回来 ——
香薰编排铺成 3 项,重启后被追加成 11 项,基线还停在 3 项,
于是 applyDeviceDefaults 判定「用户动过编排」,从此再也不铺,且怎么重连都恢复不了。
现在:当前编排若是自动铺的就整个跳过这些迁移;已经坏掉的装机由
_rebaseDeviceDefaultsBaseline 一次性把基线抬到现状(判据收紧到迁移留下的特征形态)。
- AgentDefaults.bind 补跑一次当前状态:GetX 的 ever 拿不到绑定那一刻已有的值,
而设备是被动回连的,完全可能赶在 bind 之前就绪。
- 断开设备后回退到 DeviceHub.lastKnownVendor:设备图和「设备管理」在断连后都显示
最近那台设备,AI 页却停在上一台铺的编排里,于是出现「图是耳机、功能是香薰」。
为此给 DeviceHub 加 lastKnownVendor,并把它与 lastKnownName 抽成同一个来源 ——
各算各的就还会自相矛盾。
- test/agent_module_defaults_test.dart 6 个用例守这条规则。
车载香薰唤醒 AI 后回复从手机扬声器出来
- 下行本来就设计成走经典蓝牙 A2DP,坏在共用的音频会话上:defaultToSpeaker 把输出
钉在内置扬声器,选项里只有 allowBluetooth(HFP)而没有 allowBluetoothA2dp
(playAndRecord 下 A2DP 输出默认是关的)。而这条链路采音在设备上走 BLE opus,
手机压根不录音,用 playAndRecord 本身就是错配。
- EmaiAudioSession 分档:香薰走新的 playbackOnly(.playback 类别,输出跟随系统路由),
EMAI 页「按住说话」仍是 playAndRecord(去掉 defaultToSpeaker 会退回听筒,更糟),
只补上 allowBluetoothA2dp。原生 PcmSpeakerPlayer 明确不碰 AVAudioSession,改这一处即可。
固件升级页
- 后台没给产品配过固件时,页面显示的是绿色的「已是最新」——这是假话,会让人以为
手上固件是最新的而不再去查。改成三态:找不到产品配置 / 服务端暂无固件版本 / 有新版本·已是最新。
新文案 otaNoServerVersion 已补齐 41 个语言文件(i18n_audit total_missing=0)。
- 产品找到但版本为空时打一条点名 warn,不再和「拉取失败」混在一起。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
4 weeks ago |
Rodger-Wang
|
e00b617025
|
services,admin,client: 服务配置按应用隔离、应用参数下线、客户端凭据走加密 v3
后台
- 服务配置去掉「全局默认」层,svc_config / svc_region_override / echomeet_orch
/ echomeet_orch_setting / echomeet_template 五张表统一按 app_name 隔离,
启动迁移 migrate_scope.go 幂等分配(首个应用原地改名保 id,其余拷贝);
写接口 app_name 必填(scope_check.go)。
- 类别落库 svc_category,可增删改;内置 11 个 id 不可改不可删,运行时语义仍只挂 comm.SvcCat*。
- 「应用参数」标签页下线:业务库 config 表不再随 user_getappconfig 下发,
migrate_appparams.go 把 AGENT_TYPE / MOBILE_ELF_* 迁到服务 llm_mobile_elf、
iapCustomerServiceQQ 迁到 app_params,COMPUTE_*/NEWUSER_GIFT_* 留在 config 表不下发,
无人读的音乐/公码/导航/COS_*/MeetServers 删除,其余键保留并在启动日志告警。
- 删除 console appcfg/* 与 api 模块的 get/add/update/delconfig、唤醒音四个接口(无调用方)。
- 修 getmcpservers 仍查全局层导致 MCP 类服务永远读不到。
客户端
- 凭据改走结构化通道:user_getappconfig_v3 整体 AES-CBC 加密(api_crypto.dart,
拦截器须排在 AuthInterceptor 之前),AppConfig.cred(svcId, field, envKey) 取值,
老键名只作兜底;lib 内 AppConfig.env( 已降到 0 处调用。
- 图片翻译的 ALIBABA_VL_MODEL / ENDPOINT 改挂 ast_alibaba。
启动日志
- 删除 sys/auth/firebase(google_auth 的重复实现,零调用方,且同一错误刷两行)。
- api 模块不再每次启动无条件插入默认超管(Duplicate entry 'admin')。
- sys/auth/* 五个包的 logger 名从复制来的 sys.tavily 各改各名;
google_auth 启动时即判断配置是否可用。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
4 weeks ago |
Rodger-Wang
|
27cabe1bde
|
client: 设备内核抽象、Smartcar 车载香薰接入与语音纪要等一批改动
工作区里累积的 Flutter 客户端改动,随本轮一并入库。主要涉及:
- lib/devices/ 设备内核:DeviceHub / DevicePlugin / DeviceSession 抽象,
恒玄(bes)、EaiRec 支架(holder)、Smartcar 车载香薰(smartcar) 各自的插件实现,
业务层不再直接依赖任何厂商类;分层边界由 test/devices/ 下的测试守着
- local_plugins/device_plugin_interface、device_smartcar、recorder_holder:
纯 Dart 契约与原生侧实现
- lib/modules/ 能力模块注册表、语音纪要、OTA 升级等模块
- test/ 新增设备协议、Ogg Opus 时长、模块注册表、路由等金测试
- ios/sync_spm_platform.sh:修 Xcode 直接构建时 SPM 平台版本被打回 13.0 的问题
验证:dart analyze lib 无 error(264 条 warning 均为 HEAD 上既有的 unused_import /
avoid_print 等 lint,非本次引入)。未跑真机与打包。
注:这批不是本次会话中由 Claude 编写的,此处仅做入库。
|
4 weeks ago |
Rodger-Wang
|
46c815eb87
|
修拾忆页第一次打开就报错:解包口径写错 + 红条抑制漏了 memory_list
两个都是我上一轮引入的,表现叠在一起就是「第一次打开拾忆界面抛异常」。
## 1) MemoryService 的解包口径写错(严重,后端一部署就全线失效)
DioManager.request 结尾是 `return response.data;` —— 返回的是 **data 本身**,
不是 Response。全仓既有调用方都直接下标取字段(my_devices 的 response['devices']、
bes_device_auth 的 resp['device'])。
而 MemoryService._data 写的是 `resp.data`。因为 request 的返回类型是 dynamic,
**编译与 dart analyze 都不报错**;后端还没部署时返回 null,正好走空分支,
所以本地一路绿灯。一旦服务端真的返回成功,每一次 memory 调用都会抛
NoSuchMethodError —— 也就是说这个功能在部署之后才会开始全面失效。
已改成 `if (resp is Map) return Map<String, dynamic>.from(resp);`,
并加 test/memory_service_unwrap_test.dart 把四种形状(成功有数据 / 成功空 {} /
业务错误 null / 传进来个对象)都钉住,任何人改回 .data 都会红。
## 2) 红条抑制名单漏了 memory_list
上一轮只加了 memory_today / memory_upcoming,**漏了 memory_list** —— 而它正是
打开拾忆页第一个调的接口(TodoTab.initState → refreshFromServer)。
后端没部署时网关返回 NoFindServiceHandleFunc,AuthInterceptor 就把
"[Handle Http] no found handle memory_list" 原样弹给用户。
改成按 `/api/home/memory_` 前缀整条抑制,读写两种情况都成立:
读在打开页面与切前台时自动调,页面自己有行内空态与「显示的是本地缓存」提示;
写的调用点各自弹了本地化的 memSaveFailed / memDeleteFailed / memCompleteFailed,
不挡的话会弹两条。逐个列举正是漏掉 memory_list 的原因,改前缀。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
1 month ago |
Rodger-Wang
|
2d1ee0b783
|
apps/client 首次入库(Flutter 移动端)
从 /Users/yunyanzhineng/work/eaimar_client-main 搬入,未带原 .git,历史留在原仓库。
3215 个文件 / 约 118MB,其中约 40MB 是构建必需的预编译 SDK(azure_speech 的
MicrosoftCognitiveServicesSpeech.xcframework、device_jieli 的 JL_BLEKit 等)。
build/ (5.2G)、ios/Pods/ (311M)、android/.gradle/ (120M) 已由 .gitignore 挡掉。
本次入库的代码包含两项刚做完的改动:
1) 游客登录入口接上服务端开关。此前 _checkGuestLoginConfig() 恒定写死 true,
入口根本不读配置。现改为取服务端算好的结论,并把入口所在的 Builder 改成 Obx
——配置是异步拿到的,Builder 只渲染 onInit 时的初值 false,入口再也不会出现。
拿不到配置时按隐藏处理:默认显示会在送审期把不该露的入口露给审核员。
⚠️ 已发布的老客户端关不掉(写死 true),只对本版及以后生效。
2) iOS 上架合规修复:
- 移除 ATT/IDFA:删掉 Info.plist 的 NSUserTrackingUsageDescription、
SKAdNetworkItems、NSAdvertisingAttributionReportEndpoint,删掉 splash 里的
授权请求与 app_tracking_transparency 依赖。本 App 从不读取 IDFA,也没有任何
广告/归因 SDK,弹框纯粹劝退用户,还要在 App Store Connect 申报追踪数据类型。
- ios/Podfile 里那段 config.build_settings.delete('NSUserTrackingUsageDescription')
是无效代码(该 key 在 Info.plist 里,不是 build setting),换成注释说明。
- 通讯录符号(ITMS-90683 被拒原因)随之一并解决:permission_handler 走 SPM 后
按 Info.plist 有无对应 key 决定编不编,判定结果被 SwiftPM 按内容哈希缓存,
改完 plist 必须清 DerivedData 与 manifests 缓存才会重新求值。
另附 GOOGLE_PLAY_RELEASE_CHECKLIST.md:Google Play 上架阻塞项的实测梳理
(targetSdk 36 与 Billing 8 的 2026-08-31 期限、16KB 页对齐不合规的两个 .so、
支付回退逻辑的下架风险)。
⚠️ android/app/sign/ 下的 eaimar.keystore 与 Eaimar.properties(含明文签名密码)
随本次提交入库,与本仓库"私有仓库下接受真实凭据"的既有取舍一致。
仓库若要公开,这两个必须先摘出去。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
1 month ago |