本次提交包含多项更新: 1. 更新file_picker依赖到11.0.0-ohos.1版本 2. 清理SecureStorage、Catcher2配置冗余代码 3. 优化鸿蒙系统下HomeWidget调用方式 4. 重构编辑器导航栏图标与页面路由引用 5. 修复边框样式、简化空值判断逻辑 6. 移除冗余系统UI样式配置 7. 新增共享组件导出与自适应返回按钮 8. 批量替换路由引用为app_routes 9. 标记过时通知服务并补充注释 10. 新增引导页扫一扫功能卡片 11. 完善沉浸式状态栏配置逻辑 12. 为大量页面添加统一自适应返回按钮
677 lines
18 KiB
Dart
677 lines
18 KiB
Dart
/// ============================================================
|
|
/// 闲言APP — 页面注册表
|
|
/// 创建时间: 2026-04-20
|
|
/// 更新时间: 2026-04-20
|
|
/// 作用: 所有页面统一注册 + 合法性检测 + 主题令牌 + 描述
|
|
/// 上次更新: 初始创建 — 全页面注册+设计令牌映射+合法性校验
|
|
/// ============================================================
|
|
|
|
import '../theme/app_spacing.dart';
|
|
import '../theme/app_radius.dart';
|
|
import '../router/app_routes.dart';
|
|
|
|
// ============================================================
|
|
// 页面注册条目
|
|
// ============================================================
|
|
|
|
/// 页面注册条目模型
|
|
class PageRegistryEntry {
|
|
const PageRegistryEntry({
|
|
required this.route,
|
|
required this.name,
|
|
required this.description,
|
|
required this.category,
|
|
this.isTab = false,
|
|
this.isFullscreen = false,
|
|
this.designTokens = const [],
|
|
this.status = PageStatus.active,
|
|
});
|
|
|
|
/// 路由路径
|
|
final String route;
|
|
|
|
/// 页面名称
|
|
final String name;
|
|
|
|
/// 页面描述
|
|
final String description;
|
|
|
|
/// 页面分类
|
|
final PageCategory category;
|
|
|
|
/// 是否为 Tab 页面
|
|
final bool isTab;
|
|
|
|
/// 是否全屏页面
|
|
final bool isFullscreen;
|
|
|
|
/// 使用的设计令牌列表
|
|
final List<DesignTokenRef> designTokens;
|
|
|
|
/// 页面状态
|
|
final PageStatus status;
|
|
}
|
|
|
|
/// 页面分类
|
|
enum PageCategory {
|
|
core('核心', '🏠'),
|
|
editor('编辑器', '🎨'),
|
|
discovery('发现', '🔍'),
|
|
settings('设置', '⚙️'),
|
|
membership('会员', '👑');
|
|
|
|
const PageCategory(this.label, this.emoji);
|
|
|
|
final String label;
|
|
final String emoji;
|
|
}
|
|
|
|
/// 页面状态
|
|
enum PageStatus {
|
|
active('已上线'),
|
|
wip('开发中'),
|
|
planned('规划中'),
|
|
deprecated('已废弃');
|
|
|
|
const PageStatus(this.label);
|
|
|
|
final String label;
|
|
}
|
|
|
|
/// 设计令牌引用
|
|
class DesignTokenRef {
|
|
const DesignTokenRef({
|
|
required this.name,
|
|
required this.type,
|
|
required this.value,
|
|
this.description,
|
|
});
|
|
|
|
final String name;
|
|
final DesignTokenType type;
|
|
final String value;
|
|
final String? description;
|
|
}
|
|
|
|
/// 设计令牌类型
|
|
enum DesignTokenType { color, spacing, radius, typography, shadow, glass }
|
|
|
|
// ============================================================
|
|
// 设计令牌参考表
|
|
// ============================================================
|
|
|
|
/// 全局设计令牌参考
|
|
class DesignTokenRegistry {
|
|
DesignTokenRegistry._();
|
|
|
|
// ---- 颜色令牌 ----
|
|
static const colorTokens = [
|
|
DesignTokenRef(
|
|
name: 'primary',
|
|
type: DesignTokenType.color,
|
|
value: '#6C63FF',
|
|
description: '主色-薰衣草紫',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'secondary',
|
|
type: DesignTokenType.color,
|
|
value: '#FF6B6B',
|
|
description: '辅助色-珊瑚红',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'accent',
|
|
type: DesignTokenType.color,
|
|
value: '#4ECDC4',
|
|
description: '强调色-薄荷绿',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'success',
|
|
type: DesignTokenType.color,
|
|
value: '#10B981',
|
|
description: '成功色',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'warning',
|
|
type: DesignTokenType.color,
|
|
value: '#F59E0B',
|
|
description: '警告色',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'error',
|
|
type: DesignTokenType.color,
|
|
value: '#EF4444',
|
|
description: '错误色',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'bgPrimary-light',
|
|
type: DesignTokenType.color,
|
|
value: '#FAFAFA',
|
|
description: '日间主背景',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'bgPrimary-dark',
|
|
type: DesignTokenType.color,
|
|
value: '#1A1A2E',
|
|
description: '夜间主背景',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'textPrimary-light',
|
|
type: DesignTokenType.color,
|
|
value: '#1A1A2E',
|
|
description: '日间主文字',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'textPrimary-dark',
|
|
type: DesignTokenType.color,
|
|
value: '#E5E5E5',
|
|
description: '夜间主文字',
|
|
),
|
|
];
|
|
|
|
// ---- 间距令牌 ----
|
|
static const spacingTokens = [
|
|
DesignTokenRef(
|
|
name: 'xs',
|
|
type: DesignTokenType.spacing,
|
|
value: '${AppSpacing.xs}px',
|
|
description: '紧凑间距',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'sm',
|
|
type: DesignTokenType.spacing,
|
|
value: '${AppSpacing.sm}px',
|
|
description: '元素内间距',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'md',
|
|
type: DesignTokenType.spacing,
|
|
value: '${AppSpacing.md}px',
|
|
description: '标准间距',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'lg',
|
|
type: DesignTokenType.spacing,
|
|
value: '${AppSpacing.lg}px',
|
|
description: '区块间距',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'xl',
|
|
type: DesignTokenType.spacing,
|
|
value: '${AppSpacing.xl}px',
|
|
description: '大区块间距',
|
|
),
|
|
];
|
|
|
|
// ---- 圆角令牌 ----
|
|
static const radiusTokens = [
|
|
DesignTokenRef(
|
|
name: 'sm',
|
|
type: DesignTokenType.radius,
|
|
value: '${AppRadius.sm}px',
|
|
description: '小按钮/标签',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'md',
|
|
type: DesignTokenType.radius,
|
|
value: '${AppRadius.md}px',
|
|
description: '卡片/弹窗',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'lg',
|
|
type: DesignTokenType.radius,
|
|
value: '${AppRadius.lg}px',
|
|
description: '大卡片/面板',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'xl',
|
|
type: DesignTokenType.radius,
|
|
value: '${AppRadius.xl}px',
|
|
description: '底部弹窗',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'full',
|
|
type: DesignTokenType.radius,
|
|
value: '999px',
|
|
description: '胶囊/圆形',
|
|
),
|
|
];
|
|
|
|
// ---- 字体令牌 ----
|
|
static const typographyTokens = [
|
|
DesignTokenRef(
|
|
name: 'display',
|
|
type: DesignTokenType.typography,
|
|
value: '34px/Bold',
|
|
description: '超大标题',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'title1',
|
|
type: DesignTokenType.typography,
|
|
value: '28px/Bold',
|
|
description: '大标题',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'title2',
|
|
type: DesignTokenType.typography,
|
|
value: '22px/Bold',
|
|
description: '中标题',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'title3',
|
|
type: DesignTokenType.typography,
|
|
value: '20px/SemiBold',
|
|
description: '小标题',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'headline',
|
|
type: DesignTokenType.typography,
|
|
value: '17px/SemiBold',
|
|
description: '头条',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'body',
|
|
type: DesignTokenType.typography,
|
|
value: '17px/Regular',
|
|
description: '正文',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'callout',
|
|
type: DesignTokenType.typography,
|
|
value: '16px/Medium',
|
|
description: '标注',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'subhead',
|
|
type: DesignTokenType.typography,
|
|
value: '15px/Regular',
|
|
description: '副标题',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'footnote',
|
|
type: DesignTokenType.typography,
|
|
value: '13px/Regular',
|
|
description: '脚注',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'caption1',
|
|
type: DesignTokenType.typography,
|
|
value: '12px/Regular',
|
|
description: '说明文字',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'caption2',
|
|
type: DesignTokenType.typography,
|
|
value: '11px/Regular',
|
|
description: '小说明文字',
|
|
),
|
|
];
|
|
|
|
// ---- 毛玻璃令牌 ----
|
|
static const glassTokens = [
|
|
DesignTokenRef(
|
|
name: 'glassColorLight',
|
|
type: DesignTokenType.glass,
|
|
value: '#FFFFFF',
|
|
description: '日间毛玻璃底色',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'glassColorDark',
|
|
type: DesignTokenType.glass,
|
|
value: '#2D2D44',
|
|
description: '夜间毛玻璃底色',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'borderOpacity',
|
|
type: DesignTokenType.glass,
|
|
value: '0.3',
|
|
description: '毛玻璃边框透明度',
|
|
),
|
|
];
|
|
|
|
/// 获取所有令牌
|
|
static List<DesignTokenRef> get allTokens => [
|
|
...colorTokens,
|
|
...spacingTokens,
|
|
...radiusTokens,
|
|
...typographyTokens,
|
|
...glassTokens,
|
|
];
|
|
|
|
/// 按类型筛选令牌
|
|
static List<DesignTokenRef> tokensByType(DesignTokenType type) {
|
|
return allTokens.where((t) => t.type == type).toList();
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 页面注册表
|
|
// ============================================================
|
|
|
|
/// 页面注册表 — 所有页面必须在此注册
|
|
class PageRegistry {
|
|
PageRegistry._();
|
|
|
|
/// 全部注册页面
|
|
static const List<PageRegistryEntry> pages = [
|
|
// ---- 核心 Tab 页面 ----
|
|
PageRegistryEntry(
|
|
route: AppRoutes.home,
|
|
name: '首页',
|
|
description: '句子阅读主页面,展示每日推荐 + 句子流 + Hitokoto API',
|
|
category: PageCategory.core,
|
|
isTab: true,
|
|
designTokens: [
|
|
DesignTokenRef(
|
|
name: 'primary',
|
|
type: DesignTokenType.color,
|
|
value: '#6C63FF',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'md',
|
|
type: DesignTokenType.spacing,
|
|
value: '16px',
|
|
),
|
|
DesignTokenRef(name: 'lg', type: DesignTokenType.radius, value: '12px'),
|
|
DesignTokenRef(
|
|
name: 'title1',
|
|
type: DesignTokenType.typography,
|
|
value: '28px/Bold',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'body',
|
|
type: DesignTokenType.typography,
|
|
value: '17px/Regular',
|
|
),
|
|
],
|
|
),
|
|
PageRegistryEntry(
|
|
route: AppRoutes.inspiration,
|
|
name: '发现',
|
|
description: '发现 + 分类浏览 + 热门标签 + 句子瀑布流',
|
|
category: PageCategory.discovery,
|
|
isTab: true,
|
|
designTokens: [
|
|
DesignTokenRef(
|
|
name: 'accent',
|
|
type: DesignTokenType.color,
|
|
value: '#4ECDC4',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'md',
|
|
type: DesignTokenType.spacing,
|
|
value: '16px',
|
|
),
|
|
DesignTokenRef(name: 'md', type: DesignTokenType.radius, value: '8px'),
|
|
DesignTokenRef(
|
|
name: 'title3',
|
|
type: DesignTokenType.typography,
|
|
value: '20px/SemiBold',
|
|
),
|
|
],
|
|
),
|
|
PageRegistryEntry(
|
|
route: AppRoutes.editor,
|
|
name: '编辑器',
|
|
description: '卡片/壁纸编辑器,包含画布预览+工具栏+底部面板',
|
|
category: PageCategory.editor,
|
|
isTab: true,
|
|
designTokens: [
|
|
DesignTokenRef(
|
|
name: 'primary',
|
|
type: DesignTokenType.color,
|
|
value: '#6C63FF',
|
|
),
|
|
DesignTokenRef(name: 'sm', type: DesignTokenType.spacing, value: '8px'),
|
|
DesignTokenRef(name: 'md', type: DesignTokenType.radius, value: '8px'),
|
|
DesignTokenRef(
|
|
name: 'glassColorLight',
|
|
type: DesignTokenType.glass,
|
|
value: '#FFFFFF',
|
|
),
|
|
],
|
|
),
|
|
PageRegistryEntry(
|
|
route: AppRoutes.profile,
|
|
name: '个人中心',
|
|
description: '用户个人设置 + 主题切换 + 收藏/历史 + 关于',
|
|
category: PageCategory.settings,
|
|
isTab: true,
|
|
designTokens: [
|
|
DesignTokenRef(
|
|
name: 'primary',
|
|
type: DesignTokenType.color,
|
|
value: '#6C63FF',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'md',
|
|
type: DesignTokenType.spacing,
|
|
value: '16px',
|
|
),
|
|
DesignTokenRef(name: 'lg', type: DesignTokenType.radius, value: '12px'),
|
|
],
|
|
),
|
|
|
|
// ---- 全屏页面 ----
|
|
PageRegistryEntry(
|
|
route: AppRoutes.search,
|
|
name: '搜索',
|
|
description: '句子搜索 + 热搜词 + 搜索历史 + 结果展示',
|
|
category: PageCategory.discovery,
|
|
isFullscreen: true,
|
|
designTokens: [
|
|
DesignTokenRef(
|
|
name: 'primary',
|
|
type: DesignTokenType.color,
|
|
value: '#6C63FF',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'md',
|
|
type: DesignTokenType.spacing,
|
|
value: '16px',
|
|
),
|
|
DesignTokenRef(name: 'md', type: DesignTokenType.radius, value: '8px'),
|
|
],
|
|
),
|
|
PageRegistryEntry(
|
|
route: AppRoutes.source,
|
|
name: '句子来源',
|
|
description: '句子来源详情 — 书籍/影视/人物/动漫/歌词分类浏览',
|
|
category: PageCategory.discovery,
|
|
isFullscreen: true,
|
|
designTokens: [
|
|
DesignTokenRef(
|
|
name: 'accent',
|
|
type: DesignTokenType.color,
|
|
value: '#4ECDC4',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'md',
|
|
type: DesignTokenType.spacing,
|
|
value: '16px',
|
|
),
|
|
DesignTokenRef(name: 'lg', type: DesignTokenType.radius, value: '12px'),
|
|
],
|
|
),
|
|
PageRegistryEntry(
|
|
route: AppRoutes.member,
|
|
name: '会员中心',
|
|
description: '会员权益展示 + 订阅管理 + FAQ',
|
|
category: PageCategory.membership,
|
|
isFullscreen: true,
|
|
designTokens: [
|
|
DesignTokenRef(
|
|
name: 'warning',
|
|
type: DesignTokenType.color,
|
|
value: '#F59E0B',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'md',
|
|
type: DesignTokenType.spacing,
|
|
value: '16px',
|
|
),
|
|
DesignTokenRef(name: 'xl', type: DesignTokenType.radius, value: '16px'),
|
|
],
|
|
),
|
|
PageRegistryEntry(
|
|
route: '/settings/theme',
|
|
name: '主题个性化',
|
|
description: '主题模式+强调色+字体大小+毛玻璃强度管理',
|
|
category: PageCategory.settings,
|
|
isFullscreen: true,
|
|
designTokens: [
|
|
DesignTokenRef(
|
|
name: 'primary',
|
|
type: DesignTokenType.color,
|
|
value: '#6C63FF',
|
|
),
|
|
DesignTokenRef(
|
|
name: 'md',
|
|
type: DesignTokenType.spacing,
|
|
value: '16px',
|
|
),
|
|
DesignTokenRef(name: 'md', type: DesignTokenType.radius, value: '8px'),
|
|
DesignTokenRef(
|
|
name: 'glassColorLight',
|
|
type: DesignTokenType.glass,
|
|
value: '#FFFFFF',
|
|
),
|
|
],
|
|
),
|
|
PageRegistryEntry(
|
|
route: AppRoutes.editor,
|
|
name: '编辑器',
|
|
description: '从首页/其他页面进入的全屏编辑器',
|
|
category: PageCategory.editor,
|
|
isFullscreen: true,
|
|
designTokens: [
|
|
DesignTokenRef(
|
|
name: 'primary',
|
|
type: DesignTokenType.color,
|
|
value: '#6C63FF',
|
|
),
|
|
DesignTokenRef(name: 'sm', type: DesignTokenType.spacing, value: '8px'),
|
|
DesignTokenRef(name: 'md', type: DesignTokenType.radius, value: '8px'),
|
|
],
|
|
),
|
|
];
|
|
|
|
/// 按路由查找页面
|
|
static PageRegistryEntry? findByRoute(String route) {
|
|
for (final entry in pages) {
|
|
if (entry.route == route) return entry;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// 按分类筛选
|
|
static List<PageRegistryEntry> byCategory(PageCategory category) {
|
|
return pages.where((p) => p.category == category).toList();
|
|
}
|
|
|
|
/// 获取 Tab 页面
|
|
static List<PageRegistryEntry> get tabPages =>
|
|
pages.where((p) => p.isTab).toList();
|
|
|
|
/// 获取全屏页面
|
|
static List<PageRegistryEntry> get fullscreenPages =>
|
|
pages.where((p) => p.isFullscreen).toList();
|
|
|
|
/// 检测路由是否合法
|
|
static bool isRouteRegistered(String route) {
|
|
return pages.any((p) => p.route == route);
|
|
}
|
|
|
|
/// 检测所有页面是否都使用了设计令牌
|
|
static List<String> validateDesignTokens() {
|
|
final warnings = <String>[];
|
|
for (final page in pages) {
|
|
if (page.designTokens.isEmpty) {
|
|
warnings.add('${page.name}(${page.route}) 未声明使用的设计令牌');
|
|
}
|
|
}
|
|
return warnings;
|
|
}
|
|
|
|
/// 强制验证 — 路由表与注册表一致性
|
|
///
|
|
/// 检查所有 AppRoutes 中定义的路由是否都在注册表中注册。
|
|
static List<String> validateRouteConsistency() {
|
|
final errors = <String>[];
|
|
final registeredRoutes = pages.map((p) => p.route).toSet();
|
|
final appRoutes = [
|
|
AppRoutes.home,
|
|
AppRoutes.inspiration,
|
|
AppRoutes.editor,
|
|
AppRoutes.profile,
|
|
AppRoutes.search,
|
|
AppRoutes.source,
|
|
AppRoutes.member,
|
|
AppRoutes.themeSettings,
|
|
];
|
|
|
|
for (final route in appRoutes) {
|
|
if (!registeredRoutes.contains(route)) {
|
|
errors.add('路由 $route 在 AppRoutes 中定义但未在 PageRegistry 中注册');
|
|
}
|
|
}
|
|
|
|
for (final route in registeredRoutes) {
|
|
if (!appRoutes.contains(route)) {
|
|
errors.add('路由 $route 在 PageRegistry 中注册但未在 AppRoutes 中定义');
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
/// 强制验证 — 页面设计规范
|
|
///
|
|
/// 检查每个页面是否使用了正确的设计令牌类型。
|
|
static List<String> validateDesignCompliance() {
|
|
final errors = <String>[];
|
|
for (final page in pages) {
|
|
final hasColor = page.designTokens.any(
|
|
(t) => t.type == DesignTokenType.color,
|
|
);
|
|
final hasSpacing = page.designTokens.any(
|
|
(t) => t.type == DesignTokenType.spacing,
|
|
);
|
|
final hasRadius = page.designTokens.any(
|
|
(t) => t.type == DesignTokenType.radius,
|
|
);
|
|
|
|
if (!hasColor) {
|
|
errors.add('${page.name}(${page.route}) 缺少颜色令牌声明');
|
|
}
|
|
if (!hasSpacing) {
|
|
errors.add('${page.name}(${page.route}) 缺少间距令牌声明');
|
|
}
|
|
if (!hasRadius) {
|
|
errors.add('${page.name}(${page.route}) 缺少圆角令牌声明');
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
/// 执行全部验证
|
|
static List<String> validateAll() {
|
|
return [
|
|
...validateDesignTokens(),
|
|
...validateRouteConsistency(),
|
|
...validateDesignCompliance(),
|
|
];
|
|
}
|
|
|
|
/// 获取注册页面总数
|
|
static int get pageCount => pages.length;
|
|
|
|
/// 获取各分类页面数量
|
|
static Map<PageCategory, int> get categoryCounts {
|
|
final counts = <PageCategory, int>{};
|
|
for (final page in pages) {
|
|
counts[page.category] = (counts[page.category] ?? 0) + 1;
|
|
}
|
|
return counts;
|
|
}
|
|
}
|