AskaEth
|
27b6910009
|
feat(chat): 附件芯片可点开预览,并给「用其它应用打开 / 保存到下载目录」两条出口
点一枚附件芯片此前完全没有反应。现在点它会开一个 App 内预览弹层,弹层里再给两条出口。
- FileProvider(authority = ${applicationId}.fileprovider,res/xml/file_paths.xml 只暴露
cacheDir/documents/):把 file:// 交给别的 App 在 API 24+ 会抛 FileUriExposedException,
必须走 content://;因此 cacheDir 下真正要交出去的那一层才被开出来。
- 新增 data/repository/DocumentCache.kt:缓存路径推导 / 命中判定 / 原子落盘是可单测的纯逻辑;
DocumentCacheStore 把「服务端按存储名下载」与「本地待发附件」两条来源汇成同一份文件 + 字节。
服务端存储名是内容寻址(sha256[:32]+ext)且读侧响应 immutable,所以命中即永不失效、命中时
一个请求都不发;先写 .part 再改名、失败删干净——半截文件被下一次「命中」当成完整内容,
是最坏的一种静默错误。
- 新增 domain/chat/DocumentPreview.kt(纯逻辑):截断(首 256 KB 或 5000 行,先到者为准;
字节切点不切坏多字节 UTF-8)、正文分块(200 行一块)、来源判定(服务端 vs 本地,只有一处)、
打开用的 MIME(只用 DTO 里字节嗅探所得的 mime,绝不按扩展名猜)、「没有 App 能开」的裁决与
文案、取字节失败的分类文案。
- 新增 ui/chat/DocumentPreviewDialog.kt:Compose Dialog + Miuix 内容。正文用 LazyColumn 分块渲染
(5000 行不塞进单个 Text),每块各自 SelectionContainer;加载态与失败态分明(失败给重试,
绝不渲染成空文件);截断明写「只显示前 N,完整内容请用…」,不静默截断;复用 DocumentChips
显示类型/体积/字数/状态,不另写一套标签;status 为 unreadable 不挡预览(那是模型读不到,
文件本体一直在)。
- ui/chat/MessageBubble.kt:芯片整枚可点(点击区是那枚芯片本身,不是里面的两行文字),
并带 contentDescription「查看附件 <name>」。
- ui/chat/ChatScreen.kt:预览状态 + 取字节 + SAF 保存 + ACTION_VIEW 打开。没有 App 能开时
如实告知并指路保存,绝不静默失败。
契约缺口(需登记 02):服务端按内容寻址落盘,DTO 里没有原始文件名,保存到下载目录只能用
<32hex>.<ext>——弹层里已把这一点写在界面上,没有替用户编一个假名字。
测试 567 → 610(+29 DocumentPreviewTest / +14 DocumentCacheTest),0 失败 0 错误。
|
2026-10-04 19:21:31 +08:00 |
|
AskaEth
|
ae4e4e53f6
|
feat(share): 接入系统分享入口 —— 分享文本 / 图片进会话(未登录先暂存,登录后落地)
主人 2026-10-04 提出的「分享到 App」的客户端半边。触发面就是系统分享菜单的
ACTION_SEND / ACTION_SEND_MULTIPLE;发送侧**完全复用既有路径**(短文本 WS、
长文本与图片 REST、大图先 /upload),不另起一套。
- Manifest:MainActivity 加 SEND / SEND_MULTIPLE 的 intent-filter,MIME 只声明
text/plain 与 image/*。刻意不声明 */*:对话路径上今天没有「文件」这个表示
(协同 02/F1 三缺),声明 */* 等于给出一个点进去必然失败的入口;收窄之后非图片
文件干脆不进分享菜单。另加 launchMode=singleTop 与 label(分享菜单里显示 Cyrene,
图标沿用应用启动图标,不另设一份会漂移的来源)。
- MainActivity:冷启动(onCreate)与热启动(onNewIntent + setIntent)两条路都接。
冷启动只在 savedInstanceState==null 时接 intent —— Activity 因旋转 / 深色切换重建
时系统会把同一个 intent 再送一遍,照单全收就会把同一条分享发第二次。
- 新增 domain/share(纯逻辑,可单测):
- ShareRules.extract:文本 / 单图 / 多图 / 空内容 / 只有 MIME 没有内容 /
EXTRA_STREAM 缺席时回落到 ClipData;去重与空白条目在**截断之前**处理
(重复项既不该挤掉真图,也不该被算成「截掉了」)。一次最多接 4 张,超出的
条数如实计数,不静默丢。正文一个字都不加(来源标记走独立 RoleSystem 消息 +
SenderName,拼进正文即违反红线,见 02/F4)。
- ShareRules.cacheFileName:文件名只由本端 token + 序号 + MIME 白名单扩展名构成,
外部 URI 的路径 / 显示名绝不参与(纯函数里连本端 token 都不信,非法字符折成 _)。
- PendingShareHolder:待落地内容的队列,take() 是原子出队 —— 一条内容只能被消费
一次(消费它的地方都会被重建:重组、切会话、Activity 重建),同时不把先后两条
挤在同一窗口的那次分享丢掉。
- 新增 app/ShareIntake:进程级单例(不塞 AppContainer,避免碰别的并行改动)。
图片在收到的那一刻就拷进 cacheDir 并换成**本端 file://**(content:// 的读授权活不过
登录 / 切页,拷不动的那几张如实计入「未发送」);进程启动清一次缓存目录
(那份副本只对产生它的进程有意义),运行期间不回收 —— 发送失败回滚到输入框之后
仍要读它。
- ChatViewModel:sendShared 与输入框的 send 共用 emitAndDeliver(同一套乐观气泡 /
幂等键 / 回滚),差别只有「不动用户草稿」(分享不是用户按的发送键)。一条消息只能
带一张图 ⇒ N 张图 = N 条消息,第一条带分享的正文;顺序在同一个协程里串起来。
长文本不截断,由既有 ChatSendRouting 判据改走 REST(WS 帧 4096 字节是断连不是报错)。
- ChatScreen:仅当**当前会话**的组合存在时认领(已访问过的会话 VM 都还活着,UI 层
认证才不会让旧会话抢走),落地前先等 WS 连接、上限 3 秒(冷启动必然撞上握手;超时
也照发,让「没网」退化成内容回到输入框这种看得见的结果,而不是停在看不见的地方)。
截断 / 读不到 / 空内容用 shareNotice 如实告知(8 秒后自动收走),与 error 分开。
- 测试 +23(ShareExtractionTest 18 / PendingShareHolderTest 5):文本、单图、多图、
空、只有 MIME、ClipData 兜底与去重、截断计数、超大文本不截断且落 REST、
文件名不可穿越、原子出队(并发 64 取 64 条不重不漏)。assembleDebug 绿,
testDebugUnitTest 353 → 376 全绿(failures/errors 皆 0)。
|
2026-10-04 18:05:27 +08:00 |
|
AskaEth
|
5ac0ea3c3b
|
fix(chat): 输入法弹出时输入框被顶到顶部
窗口默认 adjustUnspecified 会先做 pan,叠加 imePadding 后高度被算两次;改为 adjustResize。
|
2026-09-08 21:42:36 +08:00 |
|
AskaEth
|
171f6ff59a
|
fix(net): 放行明文 HTTP,修复自部署地址报「网络不可用」
Android 9+ 默认禁止 cleartext,OkHttp 抛 UnknownServiceException 被映射为无网。新增 network_security_config.xml(放行 cleartext + 信任用户 CA),并给该异常单独的提示文案。
|
2026-09-08 20:56:06 +08:00 |
|
AskaEth
|
84de51c7b7
|
feat: 初始化 Android 客户端(M0 骨架 + M1 登录 + M2 基础模块)
M0:Gradle 9.7.1 / AGP 9.2.1 / Kotlin 2.4 / Miuix 0.9.3,minSdk 33,compileSdk 37。
M1:登录页(服务器地址可配、服务端/本地错误分流、EncryptedSharedPreferences、自动登录与静默重登、401 登出)。
M2 基础模块:消息内容解析器(<p>/<action>/<narration> + 流式增量)、WebSocket 客户端、REST 数据层与分页去重。
45 个单元测试全绿,真机验证通过(登录页 + 启动图标)。
devdocs/ 收录设计文档、前后端协同事项与细化方案。
|
2026-09-08 20:40:58 +08:00 |
|