chore: v6.6.6 版本迭代更新
主要变更: 1. 重构"国学"相关模块为"经典名句",统一命名规范 2. 重命名"阅读报告"为"使用报告",调整相关文案与配置 3. 修复iOS模拟器图片缓存兼容问题,优化图表渲染逻辑 4. 新增设备活跃状态前端兜底判断,修复在线计数异常 5. 完善登录/注册流程,新增忘记密码路由与账户编辑提示 6. 优化文件传输与字体导入逻辑,废弃过时的bytes属性使用 7. 添加Spotlight全局快捷键支持,更新隐私权限与通知配置 8. 补充数据库迁移脚本与部署文档,修复后端接口兼容问题 9. 调整部分UI交互细节,优化内存占用与应用稳定性
This commit is contained in:
@@ -173,7 +173,7 @@ class OverviewDashboard extends ConsumerWidget {
|
||||
(emoji: '📖', label: '稍后读', route: AppRoutes.readLater),
|
||||
(emoji: '🕐', label: '历史', route: AppRoutes.history),
|
||||
(emoji: '✅', label: '签到', route: AppRoutes.signin),
|
||||
(emoji: '📊', label: '阅读报告', route: AppRoutes.readingReport),
|
||||
(emoji: '📊', label: '使用报告', route: AppRoutes.readingReport),
|
||||
(emoji: '🌤️', label: '每日推荐', route: AppRoutes.dailyCard),
|
||||
(emoji: '⚙️', label: '设置', route: AppRoutes.generalSettings),
|
||||
];
|
||||
|
||||
@@ -130,15 +130,19 @@ String? _handleRedirect(BuildContext context, GoRouterState state) {
|
||||
}
|
||||
|
||||
// 引导页已完成但当前在引导页 → 跳转首页
|
||||
// 例外:如果带有 review=true 参数,说明是用户主动查看,不拦截
|
||||
if (onboardingDone && !shouldShow && currentPath == AppRoutes.onboarding) {
|
||||
final savedPage = KvStorage.getString('general_startup_page');
|
||||
final location = switch (savedPage) {
|
||||
'inspiration' => AppRoutes.inspiration,
|
||||
'discover' => AppRoutes.discover,
|
||||
'profile' => AppRoutes.profile,
|
||||
_ => AppRoutes.home,
|
||||
};
|
||||
return location;
|
||||
final isReview = state.uri.queryParameters['review'] == 'true';
|
||||
if (!isReview) {
|
||||
final savedPage = KvStorage.getString('general_startup_page');
|
||||
final location = switch (savedPage) {
|
||||
'inspiration' => AppRoutes.inspiration,
|
||||
'discover' => AppRoutes.discover,
|
||||
'profile' => AppRoutes.profile,
|
||||
_ => AppRoutes.home,
|
||||
};
|
||||
return location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ class AppRoutes {
|
||||
static const String likes = '/likes';
|
||||
static const String quickCard = '/quick-card';
|
||||
static const String login = '/login';
|
||||
static const String forgotPassword = '/forgot-password';
|
||||
static const String signin = '/signin';
|
||||
static const String myDevices = '/my-devices';
|
||||
static const String qrcodeLogin = '/qrcode-login';
|
||||
@@ -127,6 +128,8 @@ class AppRoutes {
|
||||
static const String toolCenterSettings = '/tool-center/settings';
|
||||
static const String rssReader = '/tool/rss_reader';
|
||||
static const String onboarding = '/onboarding';
|
||||
/// 主动查看引导页(从关于页等入口),带此参数跳过redirect拦截
|
||||
static const String onboardingReview = '/onboarding?review=true';
|
||||
static const String experimentalFeatures = '/settings/experimental-features';
|
||||
static const String exchangeRate = '/tool/exchange_rate';
|
||||
static const String nickTool = '/tool/nick';
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:xianyan/core/router/ohos_route_types.dart';
|
||||
import 'package:xianyan/core/router/app_routes.dart';
|
||||
|
||||
import 'package:xianyan/features/auth/presentation/login_page.dart';
|
||||
import 'package:xianyan/features/auth/presentation/forgot_password_page.dart';
|
||||
import 'package:xianyan/features/auth/presentation/qrcode_login_page.dart';
|
||||
import 'package:xianyan/features/mine/signin/presentation/signin_page.dart';
|
||||
import 'package:xianyan/features/mine/user_center/presentation/my_devices_page.dart';
|
||||
@@ -150,6 +151,12 @@ final List<RouteDef> routeRegistry = [
|
||||
module: RouteModule.user,
|
||||
page: () => const LoginPage(),
|
||||
),
|
||||
RouteDef(
|
||||
path: AppRoutes.forgotPassword,
|
||||
name: 'forgot-password',
|
||||
module: RouteModule.user,
|
||||
page: () => const ForgotPasswordPage(),
|
||||
),
|
||||
RouteDef(
|
||||
path: AppRoutes.signin,
|
||||
name: 'signin',
|
||||
|
||||
@@ -143,6 +143,12 @@ enum AppPermission {
|
||||
CupertinoIcons.arrow_counterclockwise,
|
||||
Color(0xFF5856D6),
|
||||
isVirtual: true,
|
||||
),
|
||||
tracking(
|
||||
Permission.notification,
|
||||
CupertinoIcons.eye_fill,
|
||||
Color(0xFF5AC8FA),
|
||||
group: PermissionGroup.optional,
|
||||
);
|
||||
|
||||
const AppPermission(
|
||||
@@ -176,6 +182,7 @@ enum AppPermission {
|
||||
AppPermission.clipboard => t.permClipboardLabel,
|
||||
AppPermission.share => t.permShareLabel,
|
||||
AppPermission.shake => t.permShakeLabel,
|
||||
AppPermission.tracking => t.permTrackingLabel,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -194,6 +201,7 @@ enum AppPermission {
|
||||
AppPermission.clipboard => t.permClipboardDesc,
|
||||
AppPermission.share => t.permShareDesc,
|
||||
AppPermission.shake => t.permShakeDesc,
|
||||
AppPermission.tracking => t.permTrackingDesc,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -212,6 +220,7 @@ enum AppPermission {
|
||||
AppPermission.clipboard => t.permClipboardUsage,
|
||||
AppPermission.share => t.permShareUsage,
|
||||
AppPermission.shake => t.permShakeUsage,
|
||||
AppPermission.tracking => t.permTrackingUsage,
|
||||
};
|
||||
if (raw.isEmpty) return const [];
|
||||
return raw.split('|');
|
||||
@@ -232,16 +241,21 @@ enum AppPermission {
|
||||
AppPermission.clipboard => t.permClipboardDenial,
|
||||
AppPermission.share => t.permShareDenial,
|
||||
AppPermission.shake => t.permShakeDenial,
|
||||
AppPermission.tracking => t.permTrackingDenial,
|
||||
};
|
||||
}
|
||||
|
||||
/// Android 13+ 不需要 storage 权限(由 photos 替代)
|
||||
/// tracking 权限仅 iOS 展示
|
||||
bool get isPlatformRelevant {
|
||||
if (this == AppPermission.storage) {
|
||||
if (!pu.isAndroid) return false;
|
||||
final sdkInt = _androidSdkInt;
|
||||
return sdkInt != null && sdkInt <= 32;
|
||||
}
|
||||
if (this == AppPermission.tracking) {
|
||||
return Platform.isIOS;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -397,6 +411,10 @@ class PermissionService {
|
||||
if (perm.isVirtual) {
|
||||
return _checkVirtualStatus(perm);
|
||||
}
|
||||
// ATT 追踪权限使用专用 API 查询
|
||||
if (perm == AppPermission.tracking) {
|
||||
return _checkTrackingStatus();
|
||||
}
|
||||
try {
|
||||
final status = await perm.permission.status;
|
||||
return AppPermissionStatus.fromPermissionStatus(status);
|
||||
@@ -406,6 +424,24 @@ class PermissionService {
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查 ATT 追踪权限状态
|
||||
static Future<AppPermissionStatus> _checkTrackingStatus() async {
|
||||
if (!Platform.isIOS) return AppPermissionStatus.granted;
|
||||
try {
|
||||
final status = await AppTrackingTransparency.trackingAuthorizationStatus;
|
||||
return switch (status) {
|
||||
TrackingStatus.authorized => AppPermissionStatus.granted,
|
||||
TrackingStatus.denied => AppPermissionStatus.permanentlyDenied,
|
||||
TrackingStatus.notDetermined => AppPermissionStatus.notDetermined,
|
||||
TrackingStatus.restricted => AppPermissionStatus.restricted,
|
||||
_ => AppPermissionStatus.notDetermined,
|
||||
};
|
||||
} catch (e) {
|
||||
_log.e('ATT状态查询异常', e);
|
||||
return AppPermissionStatus.notDetermined;
|
||||
}
|
||||
}
|
||||
|
||||
/// 虚拟权限状态检查
|
||||
static Future<AppPermissionStatus> _checkVirtualStatus(
|
||||
AppPermission perm,
|
||||
@@ -444,6 +480,10 @@ class PermissionService {
|
||||
String? rationale,
|
||||
}) async {
|
||||
if (perm.isVirtual) return true;
|
||||
// ATT 追踪权限使用专用 API 请求
|
||||
if (perm == AppPermission.tracking) {
|
||||
return _requestTrackingPermission(context);
|
||||
}
|
||||
try {
|
||||
final status = await perm.permission.status;
|
||||
|
||||
@@ -573,6 +613,33 @@ class PermissionService {
|
||||
}
|
||||
}
|
||||
|
||||
/// 从权限管理页面请求 ATT 追踪权限(带状态检查和对话框提示)
|
||||
static Future<bool> _requestTrackingPermission(BuildContext context) async {
|
||||
if (!Platform.isIOS) return true;
|
||||
try {
|
||||
// 先检查当前状态
|
||||
final currentStatus = await AppTrackingTransparency.trackingAuthorizationStatus;
|
||||
if (currentStatus == TrackingStatus.authorized) {
|
||||
_log.i('ATT已授权,无需重复请求');
|
||||
return true;
|
||||
}
|
||||
if (currentStatus == TrackingStatus.denied ||
|
||||
currentStatus == TrackingStatus.restricted) {
|
||||
// 已被拒绝或受限,引导去系统设置
|
||||
if (context.mounted) {
|
||||
_showSettingsDialog(context, AppPermission.tracking);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// notDetermined 状态,发起请求
|
||||
final authorized = await requestTrackingPermission();
|
||||
return authorized;
|
||||
} catch (e) {
|
||||
_log.e('ATT权限请求异常', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查当前ATT授权状态(仅iOS)
|
||||
static Future<TrackingStatus> getTrackingStatus() async {
|
||||
if (!Platform.isIOS) return TrackingStatus.authorized;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 图片缓存管理器
|
||||
/// 创建时间: 2026-05-25
|
||||
/// 更新时间: 2026-05-25
|
||||
/// 更新时间: 2026-06-06
|
||||
/// 作用: 统一管理图片内存/磁盘缓存策略
|
||||
/// 上次更新: 初始创建
|
||||
/// 上次更新: 修复iOS模拟器objective_c FFI不兼容导致的崩溃
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
@@ -21,17 +21,28 @@ class CustomCacheManager {
|
||||
|
||||
static CacheManager? _instance;
|
||||
|
||||
/// 获取CacheManager单例
|
||||
/// 在iOS模拟器上,path_provider_foundation的FFI绑定可能不兼容,
|
||||
/// 此时回退到DefaultCacheManager
|
||||
static CacheManager get instance {
|
||||
_instance ??= CacheManager(
|
||||
Config(
|
||||
key,
|
||||
stalePeriod: maxStale,
|
||||
maxNrOfCacheObjects: maxNrOfCacheObjects,
|
||||
repo: JsonCacheInfoRepository(databaseName: key),
|
||||
fileSystem: IOFileSystem(key),
|
||||
fileService: HttpFileService(),
|
||||
),
|
||||
);
|
||||
if (_instance != null) return _instance!;
|
||||
|
||||
try {
|
||||
_instance = CacheManager(
|
||||
Config(
|
||||
key,
|
||||
stalePeriod: maxStale,
|
||||
maxNrOfCacheObjects: maxNrOfCacheObjects,
|
||||
repo: JsonCacheInfoRepository(databaseName: key),
|
||||
fileSystem: IOFileSystem(key),
|
||||
fileService: HttpFileService(),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
// iOS模拟器上objective_c FFI可能不兼容,回退到DefaultCacheManager
|
||||
Log.w('ImageCacheManager: IOFileSystem初始化失败,回退到DefaultCacheManager', e);
|
||||
return DefaultCacheManager();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 最近路由服务(单一数据源)
|
||||
// 创建时间: 2026-05-24
|
||||
// 更新时间: 2026-06-01
|
||||
// 更新时间: 2026-06-07
|
||||
// 作用: 统一管理最近访问路由和自定义路由,消除KvStorage双写问题
|
||||
// 上次更新: 添加动态路由归一化,/chat-flow/123 → /chat-flow,避免重复记录
|
||||
// 上次更新: 添加 removeRecentRoute 方法支持长按删除
|
||||
// ============================================================
|
||||
|
||||
import 'dart:convert';
|
||||
@@ -98,6 +98,18 @@ class RecentRouteService {
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除指定路由(归一化后匹配)
|
||||
static Future<void> removeRecentRoute(String route) async {
|
||||
final normalized = _normalizeRoute(route);
|
||||
try {
|
||||
final updated = List<String>.from(getRecentRoutes())..remove(normalized);
|
||||
await KvStorage.setString(_kRecentRoutes, jsonEncode(updated));
|
||||
Log.i('最近路由: 删除 → $normalized');
|
||||
} catch (e) {
|
||||
Log.w('最近路由: 删除失败 $e');
|
||||
}
|
||||
}
|
||||
|
||||
static Map<String, int> getRouteCounts() {
|
||||
try {
|
||||
final raw = KvStorage.getString(_kRouteCounts);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// ignore_for_file: avoid_dynamic_calls
|
||||
/// ============================================================
|
||||
/// 闲言APP — 通知初始化桥接
|
||||
/// 创建时间: 2026-05-22
|
||||
/// 更新时间: 2026-06-05
|
||||
/// 更新时间: 2026-06-06
|
||||
/// 作用: 构建含OHOS参数的InitializationSettings
|
||||
/// 上次更新: 新增PlatformCapabilities能力查询补充localNotification判断
|
||||
/// 上次更新: 添加avoid_dynamic_calls忽略,鸿蒙端需dynamic反射
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
@@ -95,8 +96,7 @@ Future<bool> requestOhosNotificationPermission(
|
||||
final dynamic ohosPlugin =
|
||||
plugin.resolvePlatformSpecificImplementation();
|
||||
if (ohosPlugin == null) return false;
|
||||
final dynamic result =
|
||||
await ohosPlugin.requestNotificationsPermission();
|
||||
final dynamic result = await ohosPlugin.requestNotificationsPermission();
|
||||
return result == true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
|
||||
|
||||
import '../storage/kv_storage.dart';
|
||||
import '../utils/logger.dart';
|
||||
import '../utils/platform/platform_utils.dart' as pu;
|
||||
@@ -38,10 +40,16 @@ class PostAgreementInitializer {
|
||||
Log.i('PostAgreementInitializer: 开始初始化权限敏感服务...');
|
||||
|
||||
// iOS ATT授权请求(必须在协议同意后、其他服务初始化前请求)
|
||||
// 先检查当前授权状态,仅在未决定时请求,避免Info.plist缺少描述时原生崩溃
|
||||
if (Platform.isIOS) {
|
||||
try {
|
||||
final authorized = await PermissionService.requestTrackingPermission();
|
||||
Log.i('iOS ATT授权结果: ${authorized ? "已授权" : "未授权"}');
|
||||
final status = await AppTrackingTransparency.trackingAuthorizationStatus;
|
||||
if (status == TrackingStatus.notDetermined) {
|
||||
final authorized = await PermissionService.requestTrackingPermission();
|
||||
Log.i('iOS ATT授权结果: ${authorized ? "已授权" : "未授权"}');
|
||||
} else {
|
||||
Log.i('iOS ATT已授权状态: $status,跳过请求');
|
||||
}
|
||||
} catch (e, st) {
|
||||
Log.e('iOS ATT授权请求失败', e, st);
|
||||
}
|
||||
|
||||
@@ -1203,14 +1203,10 @@ class AppDatabase extends _$AppDatabase {
|
||||
|
||||
Future<void> toggleFavorite(String id) async {
|
||||
await _safeDbVoid(() async {
|
||||
final exists = await (select(
|
||||
sentences,
|
||||
)..where((t) => t.id.equals(id))).getSingleOrNull();
|
||||
if (exists == null) return;
|
||||
await (update(sentences)..where((t) => t.id.equals(id))).write(
|
||||
SentencesCompanion(
|
||||
isFavorite: Value(!exists.isFavorite),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
SentencesCompanion.custom(
|
||||
isFavorite: sentences.isFavorite.not(),
|
||||
updatedAt: Constant(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}, 'toggleFavorite');
|
||||
@@ -1218,14 +1214,10 @@ class AppDatabase extends _$AppDatabase {
|
||||
|
||||
Future<void> toggleLike(String id) async {
|
||||
await _safeDbVoid(() async {
|
||||
final exists = await (select(
|
||||
sentences,
|
||||
)..where((t) => t.id.equals(id))).getSingleOrNull();
|
||||
if (exists == null) return;
|
||||
await (update(sentences)..where((t) => t.id.equals(id))).write(
|
||||
SentencesCompanion(
|
||||
isLiked: Value(!exists.isLiked),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
SentencesCompanion.custom(
|
||||
isLiked: sentences.isLiked.not(),
|
||||
updatedAt: Constant(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}, 'toggleLike');
|
||||
@@ -1233,10 +1225,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
|
||||
Future<void> markAsRead(String id) async {
|
||||
await _safeDbVoid(() async {
|
||||
final exists = await (select(
|
||||
sentences,
|
||||
)..where((t) => t.id.equals(id))).getSingleOrNull();
|
||||
if (exists == null) return;
|
||||
await (update(sentences)..where((t) => t.id.equals(id))).write(
|
||||
SentencesCompanion.custom(
|
||||
isRead: const Constant(true),
|
||||
@@ -1626,28 +1614,22 @@ class AppDatabase extends _$AppDatabase {
|
||||
|
||||
Future<void> recordToolUsage(String toolId, String toolName) {
|
||||
return _safeDbVoid(() async {
|
||||
final existing = await (select(
|
||||
toolUsageStats,
|
||||
)..where((t) => t.toolId.equals(toolId))).getSingleOrNull();
|
||||
if (existing != null) {
|
||||
await (update(
|
||||
toolUsageStats,
|
||||
)..where((t) => t.toolId.equals(toolId))).write(
|
||||
ToolUsageStatsCompanion(
|
||||
useCount: Value(existing.useCount + 1),
|
||||
lastUsedAt: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await into(toolUsageStats).insert(
|
||||
ToolUsageStatsCompanion.insert(
|
||||
toolId: toolId,
|
||||
toolName: Value(toolName),
|
||||
lastUsedAt: DateTime.now(),
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
);
|
||||
}
|
||||
await into(toolUsageStats).insert(
|
||||
ToolUsageStatsCompanion.insert(
|
||||
toolId: toolId,
|
||||
toolName: Value(toolName),
|
||||
lastUsedAt: DateTime.now(),
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
mode: InsertMode.insertOrIgnore,
|
||||
);
|
||||
await (update(toolUsageStats)..where((t) => t.toolId.equals(toolId)))
|
||||
.write(
|
||||
ToolUsageStatsCompanion.custom(
|
||||
useCount: toolUsageStats.useCount + const Variable<int>(1),
|
||||
lastUsedAt: Constant(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}, 'recordToolUsage');
|
||||
}
|
||||
|
||||
@@ -1802,14 +1784,10 @@ class AppDatabase extends _$AppDatabase {
|
||||
|
||||
Future<void> incrementRetryCount(int id) {
|
||||
return _safeDbVoid(() async {
|
||||
final existing = await (select(
|
||||
offlineActionQueue,
|
||||
)..where((t) => t.id.equals(id))).getSingleOrNull();
|
||||
if (existing == null) return;
|
||||
await (update(offlineActionQueue)..where((t) => t.id.equals(id))).write(
|
||||
OfflineActionQueueCompanion(
|
||||
retryCount: Value(existing.retryCount + 1),
|
||||
lastAttemptAt: Value(DateTime.now()),
|
||||
OfflineActionQueueCompanion.custom(
|
||||
retryCount: offlineActionQueue.retryCount + const Variable<int>(1),
|
||||
lastAttemptAt: Constant(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}, 'incrementRetryCount');
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 颜色令牌
|
||||
/// 创建时间: 2026-04-20
|
||||
/// 更新时间: 2026-04-20
|
||||
/// 作用: 统一颜色定义,支持日间/夜间模式
|
||||
/// 上次更新: 初始创建
|
||||
/// 更新时间: 2026-06-07
|
||||
/// 作用: 统一颜色定义,支持日间/夜间/AMOLED模式
|
||||
/// 上次更新: 修复深色模式对比度不足问题,提亮textSecondary/textHint/iconDisabled/overlaySubtle
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -52,6 +52,9 @@ class LightColors {
|
||||
}
|
||||
|
||||
/// 颜色令牌 — AMOLED纯黑模式
|
||||
///
|
||||
/// 纯黑背景下文字对比度天然较高,但仍需确保 textHint 等弱色可读,
|
||||
/// overlaySubtle 使用白色半透明避免边框消失。
|
||||
class AmoledColors {
|
||||
AmoledColors._();
|
||||
|
||||
@@ -74,18 +77,18 @@ class AmoledColors {
|
||||
|
||||
// ---- 文字色 ----
|
||||
static const Color textPrimary = Color(0xFFF5F5F5);
|
||||
static const Color textSecondary = Color(0xFF9CA3AF);
|
||||
static const Color textHint = Color(0xFF6B7280);
|
||||
static const Color textDisabled = Color(0xFF4B5563);
|
||||
static const Color textSecondary = Color(0xFFAEAEB2); // iOS systemGray2
|
||||
static const Color textHint = Color(0xFF8E8E93); // iOS systemGray,对比度≈8.5:1
|
||||
static const Color textDisabled = Color(0xFF636366); // 提亮确保最低可读
|
||||
static const Color textInverse = Color(0xFF000000);
|
||||
|
||||
// ---- 图标色 ----
|
||||
static const Color iconPrimary = Color(0xFFFFFFFF);
|
||||
static const Color iconSecondary = Color(0xFF9CA3AF);
|
||||
static const Color iconDisabled = Color(0xFF4B5563);
|
||||
static const Color iconSecondary = Color(0xFFAEAEB2); // 与 textSecondary 一致
|
||||
static const Color iconDisabled = Color(0xFF636366); // 与 textDisabled 一致
|
||||
|
||||
// ---- 遮罩色 ----
|
||||
static const Color overlaySubtle = Color(0x01000000);
|
||||
static const Color overlaySubtle = Color(0x55FFFFFF); // 白色33%透明度,确保边框可见
|
||||
static const Color overlayMedium = Color(0xCB000000);
|
||||
static const Color overlayLight = Color(0xB3000000);
|
||||
static const Color overlayStrong = Color(0x80000000);
|
||||
@@ -95,6 +98,10 @@ class AmoledColors {
|
||||
}
|
||||
|
||||
/// 颜色令牌 — 夜间模式
|
||||
///
|
||||
/// 深色模式颜色遵循 WCAG AA 对比度标准(文字≥4.5:1),
|
||||
/// textHint/iconDisabled 适当提亮确保可读性,
|
||||
/// overlaySubtle 使用白色半透明确保边框/分割线可见。
|
||||
class DarkColors {
|
||||
DarkColors._();
|
||||
|
||||
@@ -117,18 +124,18 @@ class DarkColors {
|
||||
|
||||
// ---- 文字色 ----
|
||||
static const Color textPrimary = Color(0xFFE5E5E5);
|
||||
static const Color textSecondary = Color(0xFF9CA3AF);
|
||||
static const Color textHint = Color(0xFF6B7280);
|
||||
static const Color textDisabled = Color(0xFF4B5563);
|
||||
static const Color textSecondary = Color(0xFFAEAEB2); // iOS systemGray2,对比度≈6:1
|
||||
static const Color textHint = Color(0xFF8E8E93); // iOS systemGray,对比度≈4.5:1
|
||||
static const Color textDisabled = Color(0xFF636366); // 提亮确保最低可读
|
||||
static const Color textInverse = Color(0xFF1A1A2E);
|
||||
|
||||
// ---- 图标色 ----
|
||||
static const Color iconPrimary = Color(0xFFFFFFFF);
|
||||
static const Color iconSecondary = Color(0xFF9CA3AF);
|
||||
static const Color iconDisabled = Color(0xFF4B5563);
|
||||
static const Color iconSecondary = Color(0xFFAEAEB2); // 与 textSecondary 一致
|
||||
static const Color iconDisabled = Color(0xFF636366); // 与 textDisabled 一致
|
||||
|
||||
// ---- 遮罩色 ----
|
||||
static const Color overlaySubtle = Color(0x01333333);
|
||||
static const Color overlaySubtle = Color(0x55FFFFFF); // 白色33%透明度,确保边框可见
|
||||
static const Color overlayMedium = Color(0xCB333333);
|
||||
static const Color overlayLight = Color(0xB3333333);
|
||||
static const Color overlayStrong = Color(0x80000000);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 主题系统
|
||||
/// 创建时间: 2026-04-20
|
||||
/// 更新时间: 2026-05-26
|
||||
/// 更新时间: 2026-06-07
|
||||
/// 作用: 统一 ThemeData 配置,整合设计令牌,支持日/夜切换
|
||||
/// 上次更新: 集成高对比度覆盖和色弱过滤,buildFromSettings新增highContrast/colorWeakTypeId参数
|
||||
/// 上次更新: 修复深色模式对比度不足,DarkColors/AmoledColors提亮textSecondary/textHint/overlaySubtle
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
Reference in New Issue
Block a user