跨端: 抽出基础面/玻璃组件(Motion + Surface)+ 三处硬弹的弹层补过渡
用户两条要求,各自都指向"决策被复制到各处"这个病根:
① 「应该定义一个基础玻璃容器给各个组件引用」
② 「很多页面还是不够精致……封装为基础组件供所有页面使用。基础组件包含动画」
★ 玻璃不透明的真根因(前几轮都没找到,这次是像素证据定的)
实测模拟器 3184×2232、壁纸 aurora + 压暗 37:
Navigation [229,140,3156,2204] ← 255,255,255(整块内容区)
y=2210(Navigation 之外那条缝) ← 226,225,235(壁纸清楚可见)
那条缝是决定性的:**壁纸层本身是好的**,白是因为上面盖了不透明的壳。
链路上一共三层,逐层修:
· `Navigation` 外壳:不设背景 ⇒ 系统默认不透明白,把里面已透明的窗格整片盖住;
· navBar 内容层(列表栏根容器):同样没有背景;
· **卡片**:铺的是 `Theme.surface`(实体系统色)。
修后:缝隙 203,213,228 / 卡片 191,204,220 —— 壁纸透得出来。
★ 新增基础组件(common/)
· `Surface.ets` —— `GlassPane`(正文面)/ `GlassCard`(嵌套面)/ `PageHeader` / `Pressable`
· `Motion.ets` —— `Motion.dur()`:接系统「减弱动态效果」
(`accessibility.isAnimationReduceEnabledSync()`,API 23 正好够用)。
WebUI 侧有 `prefers-reduced-motion` 硬约束(index.css:1266),
鸿蒙这边**改造前一次都没调过** —— 系统里关了动画,我们照样动。这是无障碍义务。
· `Theme.cardMaterial` —— 卡片材质令牌。不是拍脑袋选的:
`COMPONENT_THIN` 实测卡片 252,254,254(吃掉 94% 壁纸,就是用户看到的"白卡");
`BACKGROUND_THIN` → 185,190,201,壁纸透得出来且文字对比度仍够。
★ 判定形状按"玻璃从例外变成默认"重写(判据 C 条)
原来是"允许出现玻璃的位置"白名单,逐处登记。那个形状在"玻璃是少数例外"时成立,
但用户要的是**全部玻璃化** ⇒ 白名单退化成"把每处抄一遍",且挡不住新写的裸枚举。
改成**规则**:材质档次只能来自 `Theme.navMaterial` / `Theme.cardMaterial`
(或 `BlurStyle.NONE`),页面里出现裸 `BlurStyle.XXX` 即红;
辅助方法(`this.materialOf()`)体内也查,否则等于开了后门。两条变异都验证咬得住。
★ 判据 C 条自身的两个 bug(都被本次触发)
· 嵌套判定**没比文件**:`c.at`/`o.at` 是各文件自己的字符下标,直接比大小
于是判出"MainPage 内含 SettingsPage 的玻璃"这种物理上不可能的红。
假红比漏报更坏 —— 会让人去改本来对的代码(本次差一点)。
· 参数抽取用 `[^)]*`:`this.materialOf()` 里含一层 `()`,在内层括号处截断,
把合法写法读成 `this.materialOf(` 判红。改成配对括号抽取。
★ 出现/消失动画:用户点名的三处硬弹
这些挂在 `if (cond)` 上,条件一变整块出现/消失,原来一帧过渡都没有 ——
而代码上"看着像挂了动画"(外层页面有 transition),所以最容易漏。
· `MainPage` 账号选择器下拉 → `menuIn()`(从上往下落的浮层档)
· `AdminUsersPage` 新建用户表单 → `paneRiseIn()`(就地展开档)
★ 过渡必须挂在**调用点的 Column**:`@Builder` 返回 void,链不上修饰符。
第一版写进了 `CreateForm()` 内部,是新判据抓出来的。
· `SettingsPage` 新增账号弹层 → `paneRiseIn()`(与回复/转发弹层同一挂法)
新增判据「出现/消失的那类元素真的挂了过渡」:**枚举所有条件挂载的浮层/展开块**
(不是数数 —— 数数挡不住"新增第四处又漏了"),变异验证过。
★ 顺手修的两处真嵌套玻璃
`SettingsPage` 的账号列表与密钥列表都是"卡里面的一段列表",两层都铺材质 =
WebUI 用 `.bg-white .bg-white { backdrop-filter: none }` 明确禁止的嵌套
(alpha 相乘 0.82×0.82=0.97 把壁纸吃光)。去掉内层材质,玻璃只留一层。
★ 判据放宽一处(不是放水)
`harmony-contacts` 那句写死 `pushUrl`,判的是"tryRestore 有没有跳转",
与用哪个原语无关。修 LoginPage 返回栈时改成 `replaceUrl` 后它误报了;
改成 `(pushUrl|replaceUrl)`,原语该用哪个由 `harmony-nav` 那条专门判。
判据:files=32 ran=32 checks=518 pass=518 fail=0 red=0;
baseline 7/7✓(第 6 次重算,两个文件逐个 `git diff --quiet HEAD` 取证为有意编辑)。
This commit is contained in:
60
client/harmony/entry/src/main/ets/common/Motion.ets
Normal file
60
client/harmony/entry/src/main/ets/common/Motion.ets
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 动效开关 —— **全仓唯一**决定"动不动"的地方。
|
||||
*
|
||||
* ── 为什么要收成一个地方(用户 2026-09-19:「基础组件包含动画」)──
|
||||
*
|
||||
* WebUI 侧有一条硬约束(`client/electron/src/index.css:1266`):
|
||||
*
|
||||
* @media (prefers-reduced-motion: reduce) {
|
||||
* .rise-in, .animate-menu-in, … { animation: none !important; }
|
||||
* }
|
||||
*
|
||||
* 它上面的注释写着:「尊重系统设置:关了动画就一点都不动(前庭功能敏感的人
|
||||
* 会被位移动画伤到)」。这是**无障碍义务**,不是观感偏好。
|
||||
*
|
||||
* 鸿蒙侧对应能力是 `accessibility.isAnimationReduceEnabledSync()`
|
||||
* (`@ohos.accessibility.d.ts`,`@since 23`;本项目 `compatibleSdkVersion 6.1.0(23)`
|
||||
* 正好够用)。改造前**一次都没调过** —— 系统里关了动画,我们这套照样动。
|
||||
*
|
||||
* ★ 为什么不是"在每处动画前写 if":那种写法一定会有人漏(本仓的老毛病)。
|
||||
* 收成一个 `dur()` 入口,漏掉的人写不出"忘了判断"的代码 ——
|
||||
* 他必须显式地**绕过**这个函数才可能漏,而绕过是看得见的。
|
||||
*
|
||||
* ★ 缓存:这是系统查询,不能每个组件每次 `build()` 都问一遍。
|
||||
* 代价是运行中改系统设置不会立刻生效 —— 可接受,因为改这个设置的人
|
||||
* 本来就期望重启一次;而每帧同步调系统 API 的开销是实打实的。
|
||||
*/
|
||||
import { accessibility } from '@kit.AccessibilityKit';
|
||||
|
||||
export class Motion {
|
||||
private static cached: boolean | undefined = undefined;
|
||||
|
||||
/**
|
||||
* 系统是否要求「减弱动态效果」。
|
||||
*
|
||||
* 读不到时**按 '不减弱' 处理** —— 不能因为查询失败就让整个界面静默不动,
|
||||
* 那等于把一次 API 异常放大成"应用看起来坏了"。
|
||||
*/
|
||||
static reduced(): boolean {
|
||||
if (Motion.cached === undefined) {
|
||||
try {
|
||||
Motion.cached = accessibility.isAnimationReduceEnabledSync();
|
||||
} catch (e) {
|
||||
Motion.cached = false;
|
||||
}
|
||||
}
|
||||
return Motion.cached === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把"想要的时长"按系统开关折一下。
|
||||
*
|
||||
* ★ 返回 `0` 而**不是**"跳过整个动画块":ArkUI 里 `animateTo` 传 0ms
|
||||
* 等于状态直接跳到位,语义与 CSS `animation: none` 一致
|
||||
* (**终态相同、只是没有中间帧**),而代码里不必到处写
|
||||
* `if (Motion.reduced())` 的分支 —— 那种写法一定会有人漏一半。
|
||||
*/
|
||||
static dur(want: number): number {
|
||||
return Motion.reduced() ? 0 : want;
|
||||
}
|
||||
}
|
||||
365
client/harmony/entry/src/main/ets/common/Surface.ets
Normal file
365
client/harmony/entry/src/main/ets/common/Surface.ets
Normal file
@ -0,0 +1,365 @@
|
||||
/**
|
||||
* 基础「面」组件 —— 全仓**唯一**决定"一块背景长什么样"的地方。
|
||||
*
|
||||
* ── 为什么要有这个文件(2026-09-19 用户的架构判断)──
|
||||
*
|
||||
* 用户原话:「组件没有实现全部玻璃化,应该定义一个基础玻璃容器给各个组件引用」
|
||||
* 「很多页面还是不够精致……我的建议是封装为基础组件供所有页面使用。
|
||||
* 基础组件包含动画」。
|
||||
*
|
||||
* 盘过现状,那两句话指向同一个病根:**"面"的决策被复制到了 13 个地方**。
|
||||
* 改造前全仓的"透还是不透"是这样写的(散在各页,逐字重复):
|
||||
*
|
||||
* .backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
*
|
||||
* 每加一个页面就要重新想一遍"这里要不要透",而**想漏一次就是一个 bug**
|
||||
* —— 平板上的壁纸被整片挡住,根因正是宽屏分支漏抄了 `bgActive`(提交 7421ad7)。
|
||||
* 圆角/内边距也跟着各写各的,同一个"卡片"在四个页面里有四种写法 ⇒ 用户说的"不精致"。
|
||||
*
|
||||
* ── 规格来源:WebUI 的玻璃**只有一层模糊**(读出来的,不是猜的)──
|
||||
*
|
||||
* `client/electron/src/index.css` 里 `backdrop-filter: blur(...)` 只出现 **2 次**
|
||||
* (`.glass-control` 8px / `.narrow-nav` 18px),显式 `backdrop-filter: none` 有 **6 处**,
|
||||
* 包括这一条:
|
||||
*
|
||||
* html[data-bg='on'] .bg-white .bg-white { /* 嵌套面板 */
|
||||
* background-color: rgb(var(--glass-base) / var(--bg-glass-inner));
|
||||
* backdrop-filter: none; /* ★ 不叠第二层模糊 */
|
||||
* }
|
||||
*
|
||||
* 那条规则上面的注释就是规格原文:
|
||||
*
|
||||
* 原则是「玻璃只该出现一次」:最外层负责遮罩与模糊,
|
||||
* 里面几层只留一点点色调(保住正文对比度)。
|
||||
*
|
||||
* 所以本文件把"面"分两档,而不是给每处一个开关:
|
||||
*
|
||||
* · `Pane`(正文面)—— 最外层,**只有这一档吃系统材质**(`BlurStyle`),壁纸透过它。
|
||||
* · `Card`(嵌套面)—— 面板**里面**的卡片,**不吃材质**,只留色调。
|
||||
* 硬编码"不吃"而不留给调用方决定,正是 WebUI 用 `.bg-white .bg-white` 强制的同一件事:
|
||||
* 嵌套玻璃不是"少一层好看",而是 `0.82 × 0.82 = 0.97` 把壁纸彻底吃掉。
|
||||
*/
|
||||
import { Theme } from './Theme';
|
||||
import { AmIcon } from './Icons';
|
||||
import { Motion } from './Motion';
|
||||
|
||||
/**
|
||||
* 「面」的层级。**只有两档,不是调色板** ——
|
||||
* 多一档就多一个"该用哪个"的判断题,而 WebUI 的规格正好就是两档。
|
||||
*/
|
||||
export enum SurfaceDepth {
|
||||
/** 正文面:最外层面板(列表窗格、内容窗格、整页的卡片容器)。背景开启时吃系统材质。 */
|
||||
Pane = 0,
|
||||
/** 嵌套面:面板**里面**的卡片、分组、条目。不吃材质。 */
|
||||
Card = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* 玻璃容器。页面里凡是"一块有底色的矩形"**都走这里**,
|
||||
* 不要再各写 `.backgroundColor(...)`。
|
||||
*
|
||||
* 用法:
|
||||
*
|
||||
* GlassPane({ depth: SurfaceDepth.Pane, active: this.bgActive }) {
|
||||
* // 内容
|
||||
* }.padding(12) // ← 注意:GlassPane 带 @BuilderParam,链式修饰符合法
|
||||
*/
|
||||
@Component
|
||||
export struct GlassPane {
|
||||
/** 层级:正文面吃材质,嵌套面只留色调。默认正文面(最常用那一档)。 */
|
||||
@Prop depth: number = SurfaceDepth.Pane;
|
||||
/**
|
||||
* 背景是否开启(= 壁纸已铺)。
|
||||
*
|
||||
* ★ 由调用方传 `bgActive` 而**不是**组件自己读 `AppStorage`:
|
||||
* 全仓 13 处这个三元散在各页,就因为每处都得自己知道这个状态。
|
||||
* 收进来后,"哪里该透"只剩这一个判断点,漏抄不再可能。
|
||||
*/
|
||||
@Prop active: boolean = false;
|
||||
/**
|
||||
* 圆角(vp)。负数 = 不设圆角(给整屏容器用)。
|
||||
*
|
||||
* ★ 用负数当"不设"而不是 `0`:`0` 是合法的"直角",
|
||||
* 与"这层不该有圆角修饰符"是两件事。
|
||||
*/
|
||||
@Prop radius: number = Theme.glassRadius;
|
||||
/**
|
||||
* 是否裁切溢出内容。
|
||||
*
|
||||
* 默认 `true`(圆角必须配裁切才成立);但**要溢出悬挂元素的**容器
|
||||
* (如浮起的主按钮、贴边徽标)必须显式传 `false`,否则挂件会被切掉。
|
||||
*/
|
||||
@Prop doClip: boolean = true;
|
||||
|
||||
/** 内容 */
|
||||
@BuilderParam content: () => void = this.emptyContent;
|
||||
|
||||
/** `@BuilderParam` 的兜底:空面。 */
|
||||
@Builder
|
||||
emptyContent() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 这一档该用什么背景色 —— **整个文件只有这一个函数在做这件事**。
|
||||
*
|
||||
* `Pane` 分两种情形:
|
||||
* · 背景开启 ⇒ `Color.Transparent`:让 `backgroundBlurStyle` 的材质自己显形。
|
||||
* 这里若再给不透明底色,材质就被盖住了(模糊了但看不见 —— 与平板那个
|
||||
* "壁纸被挡住"的 bug 是同一个形状的错)。
|
||||
* · 背景关闭 ⇒ 系统卡片底色。
|
||||
*
|
||||
* `Card` **永远**是实体色:它负责"只留色调",且绝不吃材质。
|
||||
*/
|
||||
private surfaceColor(): ResourceColor {
|
||||
if (this.depth === SurfaceDepth.Pane) {
|
||||
return this.active ? Color.Transparent : Theme.surface;
|
||||
}
|
||||
return Theme.surface;
|
||||
}
|
||||
|
||||
/** `Pane` + 背景开启 ⇒ 系统材质;其余 ⇒ `NONE`(嵌套面不吃材质)。 */
|
||||
private materialOf(): BlurStyle {
|
||||
if (this.depth === SurfaceDepth.Pane && this.active) {
|
||||
return Theme.navMaterial;
|
||||
}
|
||||
return BlurStyle.NONE;
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
this.content()
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(this.surfaceColor())
|
||||
.backgroundBlurStyle(this.materialOf())
|
||||
.borderRadius(this.radius >= 0 ? this.radius : 0)
|
||||
.clip(this.doClip)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 页头 —— 返回按钮 + 标题。
|
||||
*
|
||||
* 改造前**四个页面各写了一个返回按钮**,而且是四种尺寸:
|
||||
*
|
||||
* AdminUsersPage:343 Text('‹').fontSize(24) .width(40).height(40)
|
||||
* MainPage:1876 Text('‹').fontSize(24) .width(36).height(36)
|
||||
* MailDetailPage:564 Text('‹').fontSize(24) .width(36).height(36)
|
||||
* CalendarPage:1171 Button('‹')
|
||||
*
|
||||
* 三件事同时错:① 用**字符**当图标(本仓明令禁止 —— 字形随字体变、基线对不齐,
|
||||
* 正是不精致的直接来源),而 `ICON_PATHS` 里**早就有** `chevronLeft`;
|
||||
* ② 同一个东西四种尺寸;③ 触控区各不相同。收进这里以后只剩一个定义。
|
||||
*/
|
||||
@Component
|
||||
export struct PageHeader {
|
||||
/** 标题 */
|
||||
@Prop title: string = '';
|
||||
/** 左侧返回按钮是否显示(根页面不显示) */
|
||||
@Prop showBack: boolean = true;
|
||||
/** 返回动作 */
|
||||
onBack: () => void = () => {};
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
if (this.showBack) {
|
||||
Row() {
|
||||
AmIcon({ iconName: 'chevronLeft', iconSize: 20, iconColor: Theme.accentFor() })
|
||||
}
|
||||
.width(HeaderMetrics.HIT)
|
||||
.height(HeaderMetrics.HIT)
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.borderRadius(Theme.radiusControl)
|
||||
.onClick(() => { this.onBack(); })
|
||||
}
|
||||
Text(this.title)
|
||||
.fontSize(HeaderMetrics.TITLE_FONT)
|
||||
.fontColor(Theme.textPrimary)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.layoutWeight(1)
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: HeaderMetrics.PAD_H, right: HeaderMetrics.PAD_H })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 页头几何。
|
||||
*
|
||||
* `HIT` 是**触控区下限**:WebUI 的返回按钮是 `p-1.5` + 16px 图标 ≈ 28px,
|
||||
* 手机上偏小;取 36vp 与本仓既有的可点区下限同值
|
||||
* —— `MainPage` 折叠头部那次修复正是 14px 的点按区会把手势条拉下来。
|
||||
*/
|
||||
class HeaderMetrics {
|
||||
static readonly HIT: number = 36;
|
||||
static readonly PAD_H: number = 12;
|
||||
/** 与 WebUI `text-base font-semibold` 对应的标题字号 */
|
||||
static readonly TITLE_FONT: number = 16;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可点容器 —— 把「命中区」「按压反馈」收成一处。
|
||||
*
|
||||
* ── 为什么(用户 2026-09-19:「很多页面还是不够精致」)──
|
||||
*
|
||||
* 盘出来的两个硬事实:
|
||||
*
|
||||
* ① 全仓 **117 处 `onClick`,按压反馈 0 处**(`stateStyles` / `onTouch` 一次都没用)。
|
||||
* 手指按下去界面上**什么都不动** —— 这是"不精致"最直接的来源:
|
||||
* 用户无法确认"我点到了没有",只能等结果。
|
||||
*
|
||||
* ② WebUI 侧有一个专门的 `.tap` 类(`index.css:630`)干这件事,注释是规格原文:
|
||||
*
|
||||
* 「保证 44x44 的触摸命中区,但不改变视觉尺寸。移动端 44x44 是通行下限
|
||||
* (Apple HIG 与 Material 都取这个数),而这些图标/小字按钮视觉上只有
|
||||
* 15-24px 高 …… 改用居中的透明伪元素扩大命中区:视觉一像素不动,手指够得到。」
|
||||
*
|
||||
* ArkUI 没有伪元素,等价做法是把**命中区**与**视觉**分开:外层给足 `hit`,
|
||||
* 内容自己按视觉尺寸排。所以本组件把 `hit` 收进来 —— 免得每个调用点
|
||||
* 再各自拍一个 36/40/44。
|
||||
*
|
||||
* ★ 按压用 `animateTo` + 显式 `@State`,**不用 `stateStyles`**:
|
||||
* `StateStyles` 的字段是 `any`(ArkTS 禁用),而且它**不做补间**
|
||||
* (瞬切,观感就是"闪一下")。WebUI 那边是
|
||||
* `transition: background-color var(--dur-fast)` = 120ms 补间,
|
||||
* 要对齐就得走 `animateTo`。
|
||||
*/
|
||||
@Component
|
||||
export struct Pressable {
|
||||
/**
|
||||
* 命中区边长(vp)。默认 `TapSpec.HIT`(44,跟 WebUI `.tap` 同值)。
|
||||
*
|
||||
* ★ 拿它当"最小边长"而不是"固定尺寸":调用方要更大的块(整行可点)
|
||||
* 应传 `width: '100%'`,命中区仍然不小于这个数。
|
||||
*/
|
||||
@Prop hit: number = TapSpec.HIT;
|
||||
/** 宽度;`-1` = 取 `hit` 的方形 */
|
||||
@Prop boxWidth: number = -1;
|
||||
/** 圆角;负数 = 不设 */
|
||||
@Prop radius: number = TapSpec.RADIUS;
|
||||
/** 按压时的底色。默认取系统"按压态"色。 */
|
||||
@Prop pressColor: ResourceColor = Theme.surfaceMuted;
|
||||
/** 常态底色;`Color.Transparent` = 不铺底 */
|
||||
@Prop baseColor: ResourceColor = Color.Transparent;
|
||||
/** 点击动作 */
|
||||
onTap: () => void = () => {};
|
||||
/** 内容 */
|
||||
@BuilderParam content: () => void = this.emptyContent;
|
||||
|
||||
@Builder
|
||||
emptyContent() {
|
||||
}
|
||||
|
||||
/** 按压态:0 = 未按,1 = 按下。驱动 `animateTo` 的补间。 */
|
||||
@State private pressT: number = 0;
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
this.content()
|
||||
}
|
||||
.width(this.boxWidth >= 0 ? this.boxWidth : this.hit)
|
||||
.height(this.hit)
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.borderRadius(this.radius >= 0 ? this.radius : 0)
|
||||
.backgroundColor(this.pressT > 0 ? this.pressColor : this.baseColor)
|
||||
.onTouch((e: TouchEvent) => {
|
||||
/*
|
||||
* `TouchType.Down` → 进入按压态;`Up`/`Cancel` → 退出。
|
||||
* ★ `Cancel` 必须一起处理:手势被父容器抢走(列表滚动)时不会来 `Up`,
|
||||
* 只处理 `Up` 会让按钮**永远停在按下的样子**。
|
||||
*/
|
||||
const down = e.type === TouchType.Down;
|
||||
this.getUIContext().animateTo({
|
||||
duration: Motion.dur(Theme.durFast),
|
||||
curve: Theme.easeOutSoft
|
||||
}, () => {
|
||||
this.pressT = down ? 1 : 0;
|
||||
});
|
||||
})
|
||||
.onClick(() => { this.onTap(); })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触摸目标的几何规格。
|
||||
*
|
||||
* `HIT = 44` 是**抄来的**、不是拍的:WebUI `.tap` 的注释写明
|
||||
* 「移动端 44x44 是通行下限(Apple HIG 与 Material 都取这个数)」。
|
||||
* 本仓原先各页写的是 36 / 40,两个都不够。
|
||||
*/
|
||||
class TapSpec {
|
||||
static readonly HIT: number = 44;
|
||||
static readonly RADIUS: number = 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* 玻璃卡片 —— **列表里每一张卡**的那一层「面」。
|
||||
*
|
||||
* ── 为什么卡片要单独一档(设备像素证据)──
|
||||
*
|
||||
* 外壳修好后(`Navigation` + navBar 内容层改透明),实测像素仍然分两段:
|
||||
*
|
||||
* x=235–245 面板缝隙 231,236,244 ← 壁纸透出来了 ✓
|
||||
* x=260–1121 **卡片** 255,255,255 ← 完全不透 ✗
|
||||
* x=1130–1145 右侧缝隙 232,234,242 ← 壁纸透出来了 ✓
|
||||
*
|
||||
* 也就是说"玻璃不透明"的**最后一层**就是卡片本身:它铺的是
|
||||
* `Theme.surface`(`sys.color.ohos_id_color_list_card_bg`),那是个**实体**色。
|
||||
*
|
||||
* WebUI 的规格(`index.css:1641` 与 `.glass-card`)把这件事说得非常明确:
|
||||
*
|
||||
* html[data-bg='on'] .comm-pane > .bg-white { background-color: transparent; }
|
||||
* html[data-bg='on'] .glass-card {
|
||||
* background-color: rgb(var(--glass-base) / var(--glass-card-wall-a)); /* 0.78 */
|
||||
* border-color: rgb(var(--hairline-fg));
|
||||
* }
|
||||
*
|
||||
* 注释原文:「面板退成透明,**每一项自己是一张玻璃卡**(圆角 + 半透明 +
|
||||
* 细边框)…… 这样每项都是独立可点、可拖、可单独高亮的一块。」
|
||||
*
|
||||
* ── 为什么用系统材质而不是手写 alpha ──
|
||||
*
|
||||
* WebUI 那张卡是 `rgb(255 255 255 / 0.78)`,照抄到 ArkTS 就是 `#C7FFFFFF`
|
||||
* —— 而 8 位半透明色是本仓**明令禁止**的(`cross-client-theme` 的 B 条:
|
||||
* 「半透明属于系统材质/语义色的职责」),理由也成立:写死 alpha 等于
|
||||
* 替系统猜深色该怎么办,而 WebUI 自己在深色下要把这个 alpha 从 0.78 改到 0.04。
|
||||
*
|
||||
* 所以这里走 `backgroundBlurStyle`:**同一份代码在深浅两套主题下由系统给出
|
||||
* 正确的浓度**,这正是"用系统方案"要的东西。
|
||||
*
|
||||
* ★ 卡片这层**必须**吃材质,和 `Pane`(正文面)刚好相反 ——
|
||||
* 在列表这个形状里,卡片才是"浮在壁纸上的那一层",而容器只是让位。
|
||||
* WebUI 用后代选择器把这套关系写死(`.comm-pane > .bg-white` 透明、
|
||||
* 再往下每项 `.glass-card`),本文件把同一件事表达成两个组件。
|
||||
*/
|
||||
@Component
|
||||
export struct GlassCard {
|
||||
/** 壁纸是否开启。关着时不铺材质(退回实体系统色,与原来观感一致) */
|
||||
@Prop active: boolean = false;
|
||||
/** 圆角(vp)。默认 WebUI `--radius-card` = 14 */
|
||||
@Prop radius: number = Theme.glassRadius;
|
||||
/** 是否画细边框(WebUI `.glass-card` 有 `1px solid var(--hairline-fg)`) */
|
||||
@Prop hairline: boolean = true;
|
||||
/** 内容 */
|
||||
@BuilderParam content: () => void = this.emptyContent;
|
||||
|
||||
@Builder
|
||||
emptyContent() {
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
this.content()
|
||||
}
|
||||
.width('100%')
|
||||
/* 透明底 + 材质:材质自己带色调,再铺一层实色就把材质盖住了 */
|
||||
.backgroundColor(this.active ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.active ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(this.radius)
|
||||
.border({ width: this.hairline ? 1 : 0, color: Theme.border })
|
||||
.clip(true)
|
||||
}
|
||||
}
|
||||
@ -33,6 +33,7 @@
|
||||
*/
|
||||
|
||||
import { curves } from '@kit.ArkUI';
|
||||
import { Motion } from './Motion';
|
||||
|
||||
export class Theme {
|
||||
// ─────────────── 表面:系统语义色 ───────────────
|
||||
@ -176,6 +177,23 @@ export class Theme {
|
||||
*/
|
||||
static readonly navMaterial: BlurStyle = BlurStyle.COMPONENT_THICK;
|
||||
|
||||
/**
|
||||
* **卡片**的材质档次。
|
||||
*
|
||||
* 与 `navMaterial` 分开而不是共用:导航条是**厚重**的一层浮条(长期压住内容),
|
||||
* 卡片是**轻薄**的一块列表项。两者同档,"每项都是一张卡"的层次感就没了
|
||||
* —— WebUI 那边两者也确实是不同的 alpha(面板 0.88 / 卡 0.78,`index.css`)。
|
||||
*
|
||||
* 用 `BACKGROUND_THIN`(不是 `COMPONENT_THIN`):设备实测(模拟器 3184×2232、
|
||||
* 壁纸 aurora + 压暗 37)卡片采样:
|
||||
* · `COMPONENT_THIN` → `252,254,254`,而它两侧缝隙是 `203,213,228`
|
||||
* ⇒ 卡片把壁纸吃掉了 94%,看着仍是"白卡",用户报的就是这个;
|
||||
* · `BACKGROUND_THIN` → `185,190,201`,壁纸透得出来,且文字对比度仍够。
|
||||
* `BACKGROUND_*` 系是"背景景深"材质,本来就为"垫在内容下面"设计,
|
||||
* 与卡片这个用途正好对上。
|
||||
*/
|
||||
static readonly cardMaterial: BlurStyle = BlurStyle.BACKGROUND_THIN;
|
||||
|
||||
// ─────────────── 圆角:系统尺寸 ───────────────
|
||||
|
||||
/** 卡片圆角:系统"卡片"圆角(不再与 WebUI 的 14vp 绑死 —— 允许各自跟随系统) */
|
||||
@ -701,8 +719,8 @@ export class Theme {
|
||||
return TransitionEffect.asymmetric(
|
||||
TransitionEffect.opacity(0).combine(
|
||||
TransitionEffect.translate({ y: Theme.riseInOffset })
|
||||
).animation({ duration: Theme.durRise, curve: Theme.easeRise }),
|
||||
TransitionEffect.opacity(0).animation({ duration: Theme.durFast, curve: Theme.easeRise })
|
||||
).animation({ duration: Motion.dur(Theme.durRise), curve: Theme.easeRise }),
|
||||
TransitionEffect.opacity(0).animation({ duration: Motion.dur(Theme.durFast), curve: Theme.easeRise })
|
||||
);
|
||||
}
|
||||
|
||||
@ -723,7 +741,7 @@ export class Theme {
|
||||
TransitionEffect.translate({ y: -4 })
|
||||
).combine(
|
||||
TransitionEffect.scale({ x: 0.985, y: 0.985 })
|
||||
).animation({ duration: Theme.durMenu, curve: Theme.easeRise });
|
||||
).animation({ duration: Motion.dur(Theme.durMenu), curve: Theme.easeRise });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -739,8 +757,8 @@ export class Theme {
|
||||
return TransitionEffect.asymmetric(
|
||||
TransitionEffect.opacity(0).combine(
|
||||
TransitionEffect.translate({ x: forward ? '12%' : '-12%' })
|
||||
).animation({ duration: Theme.durCal, curve: Theme.easeRise }),
|
||||
TransitionEffect.opacity(0).animation({ duration: Theme.durFast, curve: Theme.easeRise })
|
||||
).animation({ duration: Motion.dur(Theme.durCal), curve: Theme.easeRise }),
|
||||
TransitionEffect.opacity(0).animation({ duration: Motion.dur(Theme.durFast), curve: Theme.easeRise })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,7 +312,25 @@ struct AdminUsersPage {
|
||||
.width('100%').padding({ left: 12, right: 12, top: 8 })
|
||||
|
||||
if (this.showCreate) {
|
||||
this.CreateForm()
|
||||
/*
|
||||
* ★ 2026-09-19 补(用户:「元素的出现消失动画呢?」)。
|
||||
*
|
||||
* 这个表单挂在 `if (this.showCreate)` 上,原来硬弹出来。
|
||||
* 它是**就地展开**的一块(不是浮层)⇒ 用 `paneRiseIn()` 的上浮方向
|
||||
* (与 WebUI 里"就地展开"的 `.rise-in` 同一档),不是 `menuIn()`
|
||||
* (那是从上往下落的浮层档)。
|
||||
*
|
||||
* ★ 过渡挂在**这里的容器**上,而不是 `CreateForm()` 内部:
|
||||
* `@Builder` 的调用返回 void,修饰符链不上(`this.CreateForm().transition(...)`
|
||||
* 编译不过);而且挂在内部时**调用点看不见**,
|
||||
* "这个 if 有没有动画"要靠追进 Builder 才知道 —— 判据就是这么发现第一版写错的。
|
||||
* 包一层 `Column` 不改版式:`CreateForm` 的根本来就是 `width('100%')`。
|
||||
*/
|
||||
Column() {
|
||||
this.CreateForm()
|
||||
}
|
||||
.width('100%')
|
||||
.transition(Theme.paneRiseIn())
|
||||
}
|
||||
|
||||
if (this.loading && this.users.length === 0) {
|
||||
|
||||
@ -426,7 +426,8 @@ struct InboxTab {
|
||||
}
|
||||
.width('100%').height(46)
|
||||
.padding({ left: 14, right: 12 })
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.border({ width: 1, color: Theme.border })
|
||||
}
|
||||
@ -472,8 +473,25 @@ struct InboxTab {
|
||||
}, (acct: AccountInfo) => acct.id)
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.border({ width: { bottom: 1 }, color: Theme.border })
|
||||
/*
|
||||
* ★ 2026-09-19 补(用户:「元素的出现消失动画呢?」)。
|
||||
*
|
||||
* 这个下拉原来是**硬弹**出来的:它挂在 `if (this.showAccountPicker)` 上,
|
||||
* 条件一变就整块出现/消失,中间一帧过渡都没有 —— 而它正是
|
||||
* "出现/消失"最典型的一处(用户点一下按钮,东西凭空冒出来)。
|
||||
*
|
||||
* 用 `menuIn()`:下移 4vp + 缩到 0.985 + 淡入,与 WebUI
|
||||
* `@keyframes menu-in` 逐值对齐(`Theme.menuIn` 的注释里有出处)。
|
||||
* 方向是**从上往下**(`translateY(-4px)` → 0)—— 下拉从触发点下方展开,
|
||||
* 从上方"落"下来;与 `paneRiseIn` 的**上浮**方向相反,别混。
|
||||
*
|
||||
* `.transition()` 在这里**会播**(与日历那个常驻窗格不同):
|
||||
* 它是 `if` 包着的,条件为真时才挂载 —— 正是 transition 的触发条件。
|
||||
*/
|
||||
.transition(Theme.menuIn())
|
||||
}
|
||||
|
||||
if (this.loading) {
|
||||
@ -609,7 +627,8 @@ struct InboxTab {
|
||||
.width('100%').height(78)
|
||||
.padding({ left: 10, right: 12 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.border({ width: 1, color: Theme.border })
|
||||
.clip(true)
|
||||
@ -933,7 +952,8 @@ struct SentTab {
|
||||
this.SentRow(m)
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.margin({ bottom: 6 })
|
||||
.onClick(() => { this.openMail(m); })
|
||||
@ -1135,7 +1155,8 @@ struct PermissionTab {
|
||||
}
|
||||
.width('100%').alignItems(HorizontalAlign.Start)
|
||||
.padding(12)
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.margin({ bottom: 8 })
|
||||
}
|
||||
@ -1462,6 +1483,26 @@ struct CommPage {
|
||||
}
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
/*
|
||||
* ★★ 2026-09-19 修(用户:「你这玻璃也不透明啊」)。
|
||||
*
|
||||
* 这一层是 `Navigation` 的 **navBar 内容**(列表那一栏的根容器),
|
||||
* 原来**没有背景** ⇒ 由系统给的 `NavBar` 外壳显出不透明白。实测:
|
||||
*
|
||||
* NavBar [229,140,1149,2204] ← 255,255,255(整条列表栏)
|
||||
* 右栏同高位置 ← 187,190,191(**壁纸透出来了**)
|
||||
*
|
||||
* 外层 `Navigation` 的壳修好之后,列表栏自己还盖着一块白。
|
||||
* 内外两处都要透明,链路才通(Navigation → navBar 内容 → 卡片)。
|
||||
*
|
||||
* ★ 这里**不加**材质:WebUI 的同位置规则(`index.css:1641`)给列表容器
|
||||
* 恰恰是 `background-color: transparent`,注释写明
|
||||
* 「面板退成透明,**每一项自己是一张玻璃卡**」。
|
||||
* 玻璃在**卡片**那一层,容器只负责让位 —— 若这里也铺材质,
|
||||
* 卡片就成了"玻璃套玻璃",正是 WebUI 用
|
||||
* `.bg-white .bg-white { backdrop-filter: none }` 明确禁止的事。
|
||||
*/
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.pageBg)
|
||||
|
||||
/*
|
||||
* 悬浮的圆形加号(用户点名要的形状):56 圆、品牌色、右下角。
|
||||
@ -1495,6 +1536,20 @@ struct CommPage {
|
||||
.minContentWidth(360)
|
||||
.hideTitleBar(true)
|
||||
.width('100%').height('100%')
|
||||
/*
|
||||
* ★★ 2026-09-19 修(用户:「你这玻璃也不透明啊」)。
|
||||
*
|
||||
* 这一行原来**没有** —— `Navigation` 不设背景时用系统默认(不透明白),
|
||||
* 于是它把里面已经透明的窗格整片盖住了。实测(模拟器 3184×2232):
|
||||
*
|
||||
* Navigation [229,140,3156,2204] ← 255,255,255(整块内容区)
|
||||
* y=2210(Navigation 之外那条缝) ← 226,225,235(**壁纸清楚可见**)
|
||||
*
|
||||
* 那条缝是决定性的证据:**壁纸层本身是好的**,白是因为它上面盖了
|
||||
* 一个不透明的 `Navigation`。此前一直在调窗格自己的 `bgActive`,
|
||||
* 而真正挡住的是外面这层壳。
|
||||
*/
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.pageBg)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1831,7 +1886,8 @@ struct ContactsTab {
|
||||
* 不要把一个"按当时字号的估算值"钉成常量 ——
|
||||
* 字号、行高、动作行的存在与否都会变,而常量不会跟着变。
|
||||
*/
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.clip(true)
|
||||
/* 点卡片 = 打开这条会话(WebUI `ContactPanel` 的 onOpen) */
|
||||
@ -1860,6 +1916,9 @@ struct ContactsTab {
|
||||
.minContentWidth(360)
|
||||
.hideTitleBar(true)
|
||||
.width('100%').height('100%')
|
||||
/* 同 CommPage 的 Navigation:不设背景时系统默认不透明白,会把里面
|
||||
已经透明的窗格盖住。联系人页是同一形状,同一修法。 */
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.pageBg)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1921,7 +1980,8 @@ struct ContactsTab {
|
||||
}
|
||||
.width('100%').alignItems(HorizontalAlign.Start)
|
||||
.padding({ left: 12, right: 12, top: 10, bottom: 10 })
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.border({ width: 1, color: Theme.border })
|
||||
}
|
||||
|
||||
@ -650,7 +650,16 @@ export struct SettingsPane {
|
||||
}, (account: AccountInfo) => account.id)
|
||||
}
|
||||
.width('100%').margin({ top: 10 })
|
||||
.backgroundColor(Theme.surface)
|
||||
/*
|
||||
* ★ 2026-09-19:这里**去掉**材质(原来加了,是错的)。
|
||||
*
|
||||
* 它是"个人资料"那张**卡里面**的一段列表 —— 即 `卡 > 列表`。
|
||||
* 两层都铺材质就是 WebUI 用后代选择器明确禁止的嵌套玻璃
|
||||
* (`.bg-white .bg-white { backdrop-filter: none }`):
|
||||
* alpha 相乘把壁纸吃光,而且每层各糊一次。
|
||||
* 判据 `cross-client-theme` 的 C 条正是按"玻璃不许套玻璃"判的,它抓到了这处。
|
||||
* 内容仍靠外层那张卡的材质透出玻璃感。
|
||||
*/
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.border({ width: 1, color: Theme.border })
|
||||
.clip(true)
|
||||
@ -658,7 +667,8 @@ export struct SettingsPane {
|
||||
}
|
||||
.width('100%').alignItems(HorizontalAlign.Start)
|
||||
.padding(16).margin({ top: 8 })
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
|
||||
/* 外观(主题 / 壁纸) */
|
||||
@ -729,8 +739,19 @@ export struct SettingsPane {
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%').height('68%')
|
||||
.padding(16).backgroundColor(Theme.surface)
|
||||
.padding(16).backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius({ topLeft: 12, topRight: 12 })
|
||||
/*
|
||||
* ★ 2026-09-19 补(用户:「元素的出现消失动画呢?」)。
|
||||
*
|
||||
* 底部弹层挂在 `if (this.showAddDialog)` 上,原来硬弹。
|
||||
* 与 `MailDetailPage` 的回复框/转发框**同一档、同一挂法**:
|
||||
* `paneRiseIn()` 挂在**弹层本身**(下半部那块表面),遮罩立即出现。
|
||||
* 挂在遮罩上会让整个屏幕(含变暗的底层内容)一起位移 ——
|
||||
* 那看起来是"页面在动",而不是"弹层弹出来"。
|
||||
*/
|
||||
.transition(Theme.paneRiseIn())
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
.position({ x: 0, y: 0 })
|
||||
@ -805,7 +826,8 @@ export struct SettingsPane {
|
||||
}
|
||||
.width('100%').alignItems(HorizontalAlign.Start)
|
||||
.padding(16).margin({ top: 10 })
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
}
|
||||
|
||||
@ -876,7 +898,8 @@ export struct SettingsPane {
|
||||
}
|
||||
.width('100%').alignItems(HorizontalAlign.Start)
|
||||
.padding(16).margin({ top: 8 })
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
}
|
||||
|
||||
@ -913,7 +936,8 @@ export struct SettingsPane {
|
||||
}
|
||||
.width('100%').alignItems(HorizontalAlign.Start)
|
||||
.padding(16).margin({ top: 8 })
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
}
|
||||
|
||||
@ -989,7 +1013,8 @@ export struct SettingsPane {
|
||||
}, (k: UserKey) => k.key_id)
|
||||
}
|
||||
.width('100%').margin({ top: 10 })
|
||||
.backgroundColor(Theme.surface)
|
||||
/* 同上(账号列表那处):这是"KeySection 卡**里面**的一段列表",
|
||||
两层都铺材质 = 嵌套玻璃(判据 C 条会红,且 alpha 相乘会把壁纸吃光)。 */
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.border({ width: 1, color: Theme.border })
|
||||
.clip(true)
|
||||
@ -997,7 +1022,8 @@ export struct SettingsPane {
|
||||
}
|
||||
.width('100%').alignItems(HorizontalAlign.Start)
|
||||
.padding(16).margin({ top: 8 })
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
}
|
||||
|
||||
@ -1134,7 +1160,8 @@ export struct SettingsPane {
|
||||
}
|
||||
.width('100%').alignItems(HorizontalAlign.Start)
|
||||
.padding(16).margin({ top: 8 })
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
}
|
||||
|
||||
@ -1161,7 +1188,8 @@ export struct SettingsPane {
|
||||
}
|
||||
.width('100%').alignItems(HorizontalAlign.Start)
|
||||
.padding(16).margin({ top: 8 })
|
||||
.backgroundColor(Theme.surface)
|
||||
.backgroundColor(this.bgActive ? Color.Transparent : Theme.surface)
|
||||
.backgroundBlurStyle(this.bgActive ? Theme.cardMaterial : BlurStyle.NONE)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.onClick(() => {
|
||||
this.getUIContext().getRouter().pushUrl({ url: 'pages/AdminUsersPage' });
|
||||
|
||||
Reference in New Issue
Block a user