Files
patbond-flutter/lib/core/theme/app_theme.dart
T
lixi 8d890c0424 feat: 落地登录纵切——网络层、认证会话与登录/注册/Splash 三页(ADR-003/ADR-004)
- 新增 lib/core/network/:ApiClient(dio 封装,错误信封→类型化异常,base URL 经 --dart-define=PATBOND_API_BASE_URL 注入)、AuthInterceptor(Bearer + 设备标识)、TokenRefresher(单飞刷新,401/40101 重放一次,仅 40102/再 401 清会话)
- 新增 lib/features/auth/:SessionManager(token 只进 flutter_secure_storage)、AuthRepository(login/register/refresh/logout/me,注册带 Idempotency-Key)、Splash(500ms 最短停留/5s 超时/错误态重试+改用账号登录)、登录页与注册页(照 12 号组装稿,错误三层映射,预留区不渲染)
- App 根组件改为认证状态机驱动(Splash↔登录↔主壳 300ms fade);个人中心退出登录接入真实 logout
- 顺带修复:FIX-1 促销卡渐变改 [primaryStrong, primary]、FIX-2 补 helperStyle: muted、README dart format 命令补 --output=none
- 新增依赖:dio ^5.11.1、flutter_secure_storage ^11.0.0、uuid ^4.6.0
- 门禁:dart format(0 changed)/ flutter analyze(No issues)/ flutter test(30 passed,其中新增 23:TokenRefresher 5 + AuthRepository 10 + 登录页 4 + 注册页 4)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 11:52:51 +08:00

189 lines
6.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
/// 语义色 tokenADR-005:以 AI宠物_iOS_UI设计稿.html 珊瑚橙暖色系为品牌正典,
/// 详见 iteration-1-reports/04-ui-login-design-spec.md §1.1)。
abstract final class AppColors {
/// 品牌珊瑚橙:图标、装饰、渐变起点;不用于承载文字的实心填充(对比度不足)。
static const primary = Color(0xFFFF6F4C);
/// 加深珊瑚:实心按钮填充、可点击文字链接(白字约 4.5:1,WCAG AA)。
static const primaryStrong = Color(0xFFD6431A);
/// 浅色底上的强调文字、按钮反白替代。
static const primaryDark = Color(0xFF7A2E12);
/// amber:渐变终点、徽章、会员/促销 CTA。
static const accent = Color(0xFFFFB648);
/// amber 底上的文字。
static const accentDark = Color(0xFF7A4B0A);
/// 页面背景(奶油底)。
static const canvas = Color(0xFFFFF7ED);
/// 卡片、输入框背景。
static const surface = Color(0xFFFFFFFF);
/// peach:图标底、占位块、选中指示。
static const surfaceTint = Color(0xFFFFE8D6);
/// 主文字。
static const ink = Color(0xFF3E2A1F);
/// 次级文字、占位符。
static const muted = Color(0xFF9C8977);
/// 描边、分隔线。
static const border = Color(0xFFF0DCC8);
/// sage:成功提示的图标/描边。
static const success = Color(0xFF7FA88A);
/// 成功提示底上的文字。
static const successInk = Color(0xFF3F5744);
/// 成功提示底色。
static const successSurface = Color(0xFFE8F0E8);
static const warning = Color(0xFFF59E0B);
/// 校验错误文字、错误描边、失败提示(白底上约 5.4:1,AA 达标)。
static const error = Color(0xFFD0342C);
/// 品牌渐变:hero 区、头像环、装饰性大面积(不承载正文文字)。
static const brandGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [primary, accent],
);
}
/// 圆角 token(设计规范 §1.3)。
abstract final class AppRadius {
/// 小按钮、内嵌 CTA、错误横幅。
static const sm = 12.0;
/// 主按钮、SnackBar、菜单组。
static const md = 16.0;
/// 输入框、feed 卡。
static const lg = 18.0;
/// 大卡片。
static const xl = 24.0;
/// 芯片、徽章。
static const pill = 999.0;
}
ThemeData buildAppTheme() {
final scheme = ColorScheme.fromSeed(
seedColor: AppColors.primary,
brightness: Brightness.light,
surface: AppColors.surface,
).copyWith(error: AppColors.error, onError: Colors.white);
return ThemeData(
useMaterial3: true,
colorScheme: scheme,
scaffoldBackgroundColor: AppColors.canvas,
fontFamilyFallback: const [
'Noto Sans CJK SC',
'Noto Sans SC',
'PingFang SC',
'Microsoft YaHei',
'sans-serif',
],
textTheme: const TextTheme(
headlineSmall: TextStyle(
color: AppColors.ink,
fontSize: 22,
fontWeight: FontWeight.w800,
),
titleLarge: TextStyle(
color: AppColors.ink,
fontSize: 18,
fontWeight: FontWeight.w800,
),
titleMedium: TextStyle(
color: AppColors.ink,
fontSize: 15,
fontWeight: FontWeight.w700,
),
bodyMedium: TextStyle(color: AppColors.ink, fontSize: 14, height: 1.5),
bodySmall: TextStyle(color: AppColors.muted, fontSize: 12, height: 1.4),
),
cardTheme: const CardThemeData(
color: AppColors.surface,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.xl)),
side: BorderSide(color: AppColors.border),
),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: AppColors.primaryStrong,
foregroundColor: Colors.white,
disabledBackgroundColor: AppColors.ink.withAlpha(31),
disabledForegroundColor: AppColors.ink.withAlpha(97),
minimumSize: const Size(64, 52),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.md)),
),
textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700),
),
),
inputDecorationTheme: const InputDecorationTheme(
filled: true,
fillColor: AppColors.surface,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
borderSide: BorderSide(color: AppColors.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
borderSide: BorderSide(color: AppColors.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
borderSide: BorderSide(color: AppColors.primary, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
borderSide: BorderSide(color: AppColors.error, width: 1.5),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
borderSide: BorderSide(color: AppColors.error, width: 1.5),
),
errorStyle: TextStyle(color: AppColors.error, fontSize: 12),
helperStyle: TextStyle(color: AppColors.muted, fontSize: 12),
),
snackBarTheme: const SnackBarThemeData(
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.md)),
),
),
navigationBarTheme: NavigationBarThemeData(
backgroundColor: AppColors.surface,
indicatorColor: AppColors.surfaceTint,
height: 72,
labelTextStyle: WidgetStateProperty.resolveWith((states) {
return TextStyle(
fontSize: 11,
fontWeight: states.contains(WidgetState.selected)
? FontWeight.w700
: FontWeight.w500,
color: states.contains(WidgetState.selected)
? AppColors.primaryStrong
: AppColors.muted,
);
}),
),
);
}