v0.8.1 界面六条:删离线入口 / 标语统一 / 最近纪念日 / 图标真上色 / 真头像 / 日程四修
用户 2026-10-18 六条(全部已在真机逐条验证):
① 登录页「先不登录 / 离线使用」彻底删除
用户:「把那些不登录、看离线数据之类的提示全部去掉。以后就不再拥有这样的
功能了,把这些功能也全部删除。」
⇒ 两个按钮 + onSkip + allowSkip 参数 + Index 里的 skip 分支一起删净。
这是产品决定(不是隐藏):该路径本身就是安全缺口,v58 上了整库加密后更要堵死。
② 标语统一为「一家人用岁月写成的一本书」
用户:「这也是我们"家书"这个名字的由来。所以你不要再乱换这个标语了,
只要是想出现标语或者副标题的地方,就用这样一句话。」
· 端上 FB_DEFAULT_SLOGAN 是唯一定义处(登录页副标题 + 抽屉回落都用它);
· 抽屉左上角原来是家庭名,现在显示**标语**;
· 三层优先级(个人 slogan → 家庭 motto → 应用默认口号)**在服务端算**
(Family::slogan,与网页端首页 heroSlogan 同一套),/login 与 /me 都返回。
用户还说:「如果这个用户自己有标语……这个地方就是用户自己自定义的标语。」
真机实测:登录者杨永全自己写了「修身,齐家」⇒ 抽屉就显示他那句。
③ 首页新增「最近纪念日」(放在近期任务之后、往年的今天之前)
用户:「未来最近的三个纪念日,包括每个人的生日,以及手动填写的各个纪念日。
以倒计时形式展示:距离哪个纪念日还有多少天,距离谁谁谁的生日还有多少天。」
· /home.recent_days = Anniversary::upcoming(fam,pid,370) 取前 3 ——
取满一年:窗口太小会让"刚过完生日"的家庭一个都取不到,模块直接空着;
· 数据源 all() = 派生生日 + 自建纪念日,正好覆盖用户说的两样;
· 倒计时:0 天显示「就是今天」(写「还有 0 天」读起来像已经过了)。
④ 悬浮栏图标也上色(用户:「除了下面的汉字,上面图标的颜色也变成跟汉字一样的颜色」)
🔴🔴 真根因两层,都是"无报错失效":
· 第一层:Image.fillColor() **只对 SVG 生效**,而本工程 30+ 图标**全是 PNG**
⇒ 全工程 24 处图标染色一直是空操作(文字走 fontColor 所以只有文字变了)。
· 第二层(真机才现形):改用 colorFilter 后,矩阵写成了"系数乘 RGB"
(R' = R_原 × 系数) —— 对白色图标成立,但本工程图标是**纯黑**,
黑 × 任何系数 = 仍是黑 ⇒ 真机上图标像素依旧是 (0,0,0)。
⇒ 最终形态:`fbTint` 用 **alpha 当载体**(R' = (r/255) × A_原):
系数是归一化约定(官方灰度示例系数和为 1,量纲确定);
形状完全由原图 alpha 决定 ⇒ 黑/白图标都成立,描边抗锯齿自然过渡。
真机像素实测:日程项图标与文字同为 (176,120,64)≈#B0763A;
未选中项图标 (44,39,35) 深灰(不再是纯黑)。
⚠️ 全工程 24 处 fillColor 一并改掉;FbIconButton.tint 类型改成 string,
让"误传 Resource"变成编译错误而不是又一个静默失效。
⑤ 顶栏:真实头像 + 拉开按钮间距
用户:「右上角的头像应该是真实的……现在提醒的按钮距离头像的按钮太近了。」
· /home 给 me_avatar、/login 给 avatar_url(avatar_url() 按线上存储驱动拼好);
· 三颗按钮(同步 / 铃铛 / 头像)统一用 FbMetric.gapTopActions = 8。
⑥ 日程四处
· 「日期下面有几个点」⇒ 月视图与周视图的圆点全部删除,
连 FbDot / dots() / count / dotColor 一起清(不留死代码);
· 「农历全部都是初一」⇒ 真根因是端上 lunarOf() **硬编码 return '初一'**,
而服务端 lunarRange() 本来就每天给真实农历(title)⇒ 改取 title;
· 「列表这个视图,今天的数据缺一条」⇒ 服务端 /calendar 的 from/to **默认只给本月**,
而列表视图要显示 -7 ~ +60 天 ⇒ 端上显式传范围
(loadModule 新增 pathOverride 参数);
· 「点击了某一天之后并不明显,应该把这一天的颜色变深,而今天用一个较浅的颜色」
⇒ 照 SyncCalendar:选中 = 深色实底白字、今天 = 浅色底。
真机实测:17 号(选中)bg=#FFB0763A,18 号(今天)bg=#FFF5F0EA。
另:FbKeyStore 补上一个真机抓到的 HUKS 坑 —— isKeyItemExist 在密钥不存在时
**会抛 12000011**(不是返回 false),首次运行必被误判成"本机不支持 HUKS"
⇒ 永久明文降级。改成把"不存在"当正常分支,且降级态每次启动重试一次
(把同一把 DEK 重新用 KEK 包起来,不换密钥以免重下全部附件)。
验证:hvigor BUILD SUCCESSFUL;_fb_arkts_guard.js 328/0;
_fb_v59ui_falsify.py 19 项注入 23/0;_fb_home_api.py 60/0;
六条已在真机逐条取像素/布局树确认。
This commit is contained in:
@@ -24,9 +24,10 @@
|
||||
* 自己用 Stack + 位移动画做,行为完全可控。
|
||||
*/
|
||||
|
||||
import { FbColors, FbIcon, FbMetric } from '../common/Theme';
|
||||
import { FbColors, FbIcon, FbMetric, fbTint, FbTint } from '../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../common/Store';
|
||||
import { FbModuleDef, FB_MODULES, fbDefaultNavModules, fbThemeVars } from '../model/Modules';
|
||||
import { FB_DEFAULT_SLOGAN } from '../model/DemoData';
|
||||
import { FbAvatar } from './Widgets';
|
||||
|
||||
/**
|
||||
@@ -54,7 +55,22 @@ export struct FbDrawer {
|
||||
/** 抽屉里列出的模块(接口接上后由 user_settings('nav_modules') 下发) */
|
||||
@Local items: FbModuleDef[] = fbDefaultNavModules();
|
||||
|
||||
/** 品牌区:书名(家书)+ 家庭名 */
|
||||
/**
|
||||
* 品牌区:书名(家书)+ 标语。
|
||||
*
|
||||
* 🔴🔴 v59(用户 2026-10-18):副标题从原来的**家庭名**(「我的家」/「杨家」)
|
||||
* 换成**标语**。用户原话:
|
||||
* 「进到系统里面之后,打开抽屉,左上角有家书的名字,上面写了"我的家"。
|
||||
* 这个地方也换成我们的标语。」
|
||||
* 「如果这个用户自己有标语,他可以写一句自己的标语;如果他有自己的标语,
|
||||
* 这个地方就是用户自己自定义的标语。如果他没有标语,就用我们给他的这个标语。」
|
||||
* ⇒ 三层优先级:**个人 slogan → 家庭 motto → 应用默认口号**。
|
||||
* 这个拼接**不在端上做** —— 服务端 `Family::slogan()` 已经算好并放在
|
||||
* `session.slogan` 里(网页端首页是同一个函数,两边永远一致)。
|
||||
* 端上只在"还没拿到"时回落 `FB_DEFAULT_SLOGAN`。
|
||||
* ⚠️ 别再改回 `familyTitle` —— 那个字段现在只用于别处(如"某某的家"这类文案),
|
||||
* 而且用户明确说这一格要放标语。
|
||||
*/
|
||||
@Builder
|
||||
brandHeader() {
|
||||
Row({ space: 10 }) {
|
||||
@@ -68,10 +84,12 @@ export struct FbDrawer {
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
.letterSpacing(1)
|
||||
Text(this.app.session.familyTitle)
|
||||
// 标语:用户自己的 → 家庭的 → 应用默认(服务端已算好,见上面说明)
|
||||
Text(this.sloganText())
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.muted)
|
||||
.maxLines(1)
|
||||
.maxLines(2)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
@@ -81,6 +99,12 @@ export struct FbDrawer {
|
||||
.padding({ left: 18, right: 12, top: 16 + this.app.statusBarH, bottom: 10 })
|
||||
}
|
||||
|
||||
/** 该显示的标语(会话里没有就回落应用默认口号,**永远不空**) */
|
||||
private sloganText(): string {
|
||||
const s: string = this.app.session.slogan;
|
||||
return s !== '' ? s : FB_DEFAULT_SLOGAN;
|
||||
}
|
||||
|
||||
/** 一个模块行 */
|
||||
@Builder
|
||||
moduleRow(m: FbModuleDef) {
|
||||
@@ -91,7 +115,7 @@ export struct FbDrawer {
|
||||
.width(18)
|
||||
.height(18)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(this.isActive(m.key) ? Color.White : fbThemeVars(m.color).brand)
|
||||
.colorFilter(fbTint(this.isActive(m.key) ? FbTint.white : fbThemeVars(m.color).brand))
|
||||
}
|
||||
.width(34)
|
||||
.height(34)
|
||||
@@ -172,7 +196,7 @@ export struct FbDrawer {
|
||||
.height(17)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
// 未实现的项图标也变灰,明示"暂不可用"
|
||||
.fillColor(route === '' ? FbColors.faint : FbColors.muted)
|
||||
.colorFilter(fbTint(route === '' ? FbTint.faint : FbTint.muted))
|
||||
Text(label)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(route === '' ? FbColors.faint : FbColors.ink)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* 而不是让点击没反应 —— 后者用户会以为卡了。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric, FbIcon } from '../common/Theme';
|
||||
import { FbColors, FbMetric, FbIcon, fbTint, FbTint } from '../common/Theme';
|
||||
|
||||
/** 贴底卡片的手势区缓冲(vp)—— 同其它浮层 */
|
||||
const FB_SHEET_BOTTOM_GAP: number = 16;
|
||||
@@ -107,7 +107,7 @@ export struct FbModuleSettingsSheet {
|
||||
.width(17)
|
||||
.height(17)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(it.action === '' ? FbColors.faint : FbColors.muted)
|
||||
.colorFilter(fbTint(it.action === '' ? FbTint.faint : FbTint.muted))
|
||||
Column({ space: 1 }) {
|
||||
Text(it.label)
|
||||
.fontSize(FbMetric.fontBody)
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
* 只是缺那几项详情专属字段,界面不会空。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric } from '../common/Theme';
|
||||
import { FbColors, FbMetric, fbTint, FbTint } from '../common/Theme';
|
||||
import { FbRosterPerson, FbJobRow, FbContactRow } from '../data/FbPersonRepo';
|
||||
import { FbAvatar, FbTag } from './Widgets';
|
||||
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||
@@ -261,7 +261,7 @@ export struct FbPersonSheet {
|
||||
.width(15)
|
||||
.height(15)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
Column({ space: 2 }) {
|
||||
Text(label)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
|
||||
@@ -31,10 +31,21 @@
|
||||
* 否则最后一块内容会被悬浮栏压住(悬浮 ≠ 可以挡内容)。
|
||||
*/
|
||||
|
||||
import { FbColors, FbIcon, FbMetric } from '../common/Theme';
|
||||
import { FbColors, FbIcon, FbMetric, fbTint } from '../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../common/Store';
|
||||
import { FbModuleDef, fbModuleOf } from '../model/Modules';
|
||||
|
||||
/**
|
||||
* 未选中时的图标 / 文字色。
|
||||
*
|
||||
* 🔴 从 muted(#7E6B5A) 加深到 #4A3F35:
|
||||
* 原先在白底上的对比度只有约 2.3:1(远低于 WCAG 的 4.5:1),
|
||||
* 加上悬浮栏材质半透,用户就觉得"和下面的内容糊在一起、看不清"。
|
||||
* ⚠️ 图标与文字**必须用同一个常量**:用户 2026-10-18 要的就是"两处颜色一致",
|
||||
* 一处改、另一处忘了改,正是这次报的那个问题。
|
||||
*/
|
||||
const INACTIVE_TINT: string = '#4A3F35';
|
||||
|
||||
/** 快捷栏高度(vp)—— 取设计规范的 56 */
|
||||
const DOCK_H: number = 56;
|
||||
/**
|
||||
@@ -301,20 +312,24 @@ export struct FbDockItem {
|
||||
Image(FbIcon.ui(this.icon))
|
||||
.width(20)
|
||||
.height(20)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
// ⚠️ fillColor 必须同一类型:选中时用模块色(string),未选中也用 string。
|
||||
// 三目里两侧类型不一致(Resource 与 string 混用)会报
|
||||
// "Type 'string | Resource' is not assignable to type 'string'"。
|
||||
//
|
||||
// 🔴 未选中色从 muted(#7E6B5A) 加深到 #4A3F35:
|
||||
// 原先在白底上的对比度只有约 2.3:1(远低于 WCAG 的 4.5:1),
|
||||
// 加上材质半透,用户就觉得"和下面的内容糊在一起、看不清"。
|
||||
// 🔴 选中色 = **该模块自己的设定色**(用户 2026-10-17 明确要求),
|
||||
// 不是统一的品牌红 —— 这样"我在日程"和"我在任务"一眼能分清。
|
||||
.fillColor(this.active ? this.accent : '#4A3F35')
|
||||
/* 🔴🔴 v59(用户 2026-10-18):「现在下面的悬浮框只是那个汉字的颜色变了,
|
||||
上面的图标颜色没有变。我的要求是:除了下面的汉字,上面图标的颜色
|
||||
也变成跟汉字一样的颜色,变成彩色的。」
|
||||
⇒ 真根因:`fillColor()` **只对 SVG 生效**,而本工程图标**全是 PNG**
|
||||
(SDK `image.d.ts` 原话:"This attribute applies only to SVG images.
|
||||
To set the fill color for a PNG image, use `colorFilter`")。
|
||||
所以原来那行 `fillColor(...)` 一直是**空操作** —— 文字走 fontColor
|
||||
是真的生效,图标就一直是原图的黑色。
|
||||
⇒ 改用 `colorFilter(fbTint(...))`:用颜色矩阵把单色 PNG 染成目标色,
|
||||
并保留原图 alpha(描边抗锯齿不能丢)。详见 `Theme.fbTint` 的说明。
|
||||
|
||||
⚠️ 不用 `renderMode(ImageRenderMode.Template)`:
|
||||
它在本工程里**与 PNG 搭配同样不产生着色效果**(那套是给 SVG 用的
|
||||
语义),留着只会让人以为"已经处理了颜色"。 */
|
||||
.colorFilter(fbTint(this.active ? this.accent : INACTIVE_TINT))
|
||||
Text(this.label)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.active ? this.accent : '#4A3F35')
|
||||
.fontColor(this.active ? this.accent : INACTIVE_TINT)
|
||||
.fontWeight(this.active ? FontWeight.Medium : FontWeight.Normal)
|
||||
.maxLines(1)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* ⚠️ 顶栏**不放**「设置」—— 与 Web 端一致:设置收在抽屉底部的账号区里。
|
||||
*/
|
||||
|
||||
import { FbColors, FbIcon, FbMetric } from '../common/Theme';
|
||||
import { FbColors, FbIcon, FbMetric, FbTint, fbTint } from '../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../common/Store';
|
||||
import { FbSyncState, SYNC_DONE, SYNC_FAIL, SYNC_RUNNING, fbSyncState } from '../common/FbSyncStore';
|
||||
import { fbModuleOf } from '../model/Modules';
|
||||
@@ -113,14 +113,19 @@ export struct FbTopBar {
|
||||
Image(FbIcon.ui('check'))
|
||||
.width(17)
|
||||
.height(17)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.muted)
|
||||
/* ⚠️ 图标染色必须走 `colorFilter` —— 本工程图标全是 PNG,
|
||||
而 `fillColor` **只对 SVG 生效**(SDK image.d.ts 原话)。
|
||||
详见 `Theme.fbTint` 的说明。 */
|
||||
.colorFilter(fbTint(FbTint.muted))
|
||||
}
|
||||
}
|
||||
.width(36)
|
||||
.height(36)
|
||||
.backgroundColor(Color.Transparent)
|
||||
.stateEffect(true)
|
||||
// 与右侧铃铛拉开(用户 2026-10-18:「提醒的按钮距离头像的按钮太近了」,
|
||||
// 三颗按同一个间距排,见 FbMetric.gapTopActions 的说明)
|
||||
.margin({ left: FbMetric.gapTopActions })
|
||||
.onClick(() => {
|
||||
this.syncSheet = true;
|
||||
})
|
||||
@@ -136,13 +141,20 @@ export struct FbTopBar {
|
||||
})
|
||||
|
||||
// 通知铃铛(未读角标)
|
||||
//
|
||||
// 🔴 v59(用户 2026-10-18):「现在提醒的按钮距离头像的按钮太近了。」
|
||||
// ⇒ 两个按钮之间给一段**明确的**间距(原来它们各自 36 宽、紧挨着,
|
||||
// 角标又挂在铃铛右上角、视觉上直接压到头像上)。
|
||||
// ⚠️ 用 `margin.left/right` 拉开而不是塞一个 `Blank()`:
|
||||
// 镜像布局(右手握时整行反序)下 Blank 的弹性会跑到另一侧,
|
||||
// 间距就没了;固定 margin 两边对称、镜像后仍然成立。
|
||||
Stack({ alignContent: Alignment.TopEnd }) {
|
||||
Button({ type: ButtonType.Circle }) {
|
||||
Image(FbIcon.ui('bell'))
|
||||
.width(18)
|
||||
.height(18)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.muted)
|
||||
/* ⚠️ 同上:PNG 图标染色必须用 colorFilter(fillColor 对 PNG 无效)。 */
|
||||
.colorFilter(fbTint(FbTint.muted))
|
||||
}
|
||||
.width(36)
|
||||
.height(36)
|
||||
@@ -162,13 +174,23 @@ export struct FbTopBar {
|
||||
.margin({ top: 2, right: 0 })
|
||||
}
|
||||
}
|
||||
// 与左边的同步图标、右边的头像各拉开一段(用户:太近了)
|
||||
.margin({ left: FbMetric.gapTopActions, right: FbMetric.gapTopActions })
|
||||
|
||||
// 头像(点开账号菜单)
|
||||
//
|
||||
// 🔴 v59(用户 2026-10-18):「右上角的头像应该是真实的。」
|
||||
// ⇒ 传 `imageUrl`(真实头像图;服务端 `avatar_url()` 给的)。
|
||||
// 没传过头像的人拿到空串 ⇒ `FbAvatar` 自带色块字回落,
|
||||
// 所以这里不需要额外判断("两者都要有"是 FbAvatar 的既有设计)。
|
||||
FbAvatar({
|
||||
name: this.app.session.me.name,
|
||||
color: this.app.session.me.avatarColor,
|
||||
dia: 32,
|
||||
fontSizeVp: 14
|
||||
fontSizeVp: 14,
|
||||
imageUrl: this.app.session.me.avatarUrl !== undefined
|
||||
? this.app.session.me.avatarUrl
|
||||
: ''
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* ⚠️ 所有 struct 加 `Fb` 前缀(arkts-no-decl-merging:同 hap 内顶层名不得重复)。
|
||||
*/
|
||||
|
||||
import { FbColors, FbIcon, FbMetric } from '../common/Theme';
|
||||
import { FbColors, FbIcon, FbMetric, fbTint, FbTint } from '../common/Theme';
|
||||
|
||||
/**
|
||||
* 圆点 / 色块(时间轴的点、日历的日程小点、看板的色标…)。
|
||||
@@ -91,7 +91,7 @@ export struct FbCheckCircle {
|
||||
.width(this.dia * 0.66)
|
||||
.height(this.dia * 0.66)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(Color.White)
|
||||
.colorFilter(fbTint(FbTint.white))
|
||||
} else if (this.doing) {
|
||||
// 进行中:中心一个白点
|
||||
Column()
|
||||
@@ -123,7 +123,7 @@ export struct FbEmptyBox {
|
||||
.height(34)
|
||||
.opacity(0.35)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
Text(this.title)
|
||||
.fontSize(FbMetric.fontBody)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
@@ -320,7 +320,13 @@ export struct FbTag {
|
||||
export struct FbIconButton {
|
||||
@Param icon: Resource = $r('app.media.fb_ic_gear');
|
||||
/** 图标色:ResourceColor 同时接受 Resource($r 语义色)与 '#RRGGBB' 字符串 */
|
||||
@Param tint: ResourceColor = FbColors.muted;
|
||||
/**
|
||||
* 图标颜色。**必须是 hex 字符串**(不是 `Resource`)——
|
||||
* `colorFilter(fbTint(...))` 只吃 hex(颜色矩阵的入参)。
|
||||
* ⚠️ 传 `FbColors.*`(Resource)会编译报错 `ResourceColor is not assignable to string`,
|
||||
* 那是**故意的**:逼调用方用 `FbTint.*` 这组与 color.json 同值的常量。
|
||||
*/
|
||||
@Param tint: string = FbTint.muted;
|
||||
/** 直径 */
|
||||
@Param dia: number = 36;
|
||||
@Event onTap: () => void = () => {
|
||||
@@ -331,8 +337,8 @@ export struct FbIconButton {
|
||||
Image(this.icon)
|
||||
.width(18)
|
||||
.height(18)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(this.tint)
|
||||
/* ⚠️ 本工程图标是 PNG ⇒ 必须 colorFilter(fillColor 只对 SVG 生效)。 */
|
||||
.colorFilter(fbTint(this.tint))
|
||||
}
|
||||
.width(this.dia)
|
||||
.height(this.dia)
|
||||
@@ -368,7 +374,7 @@ export struct FbPrimaryButton {
|
||||
.width(16)
|
||||
.height(16)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(Color.White)
|
||||
.colorFilter(fbTint(FbTint.white))
|
||||
}
|
||||
Text(this.label)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
|
||||
Reference in New Issue
Block a user