feat(app): 初始化 Flutter 前端项目 (Phase F0)
- Flutter 3.44.0 + Dart 3.12.0 - 设计系统: 7色Token×浅/深模式, NotoSansSC/Caveat字体, 圆角10/16/22/28/pill, 三级阴影 - ResponsiveScaffold: 手机底部TabBar / 平板侧边Rail / 桌面三栏 - go_router 路由表: 13个页面 (5个Tab + 8个全屏页面) - 13个功能模块占位页面 (home/calendar/mood/search/profile/editor/auth/class/teacher/parent/achievement/stickers/templates) - 依赖: flutter_bloc, go_router, freezed, isar, dio, perfect_freehand, fl_chart - 中国镜像: PUB_HOSTED_URL + FLUTTER_STORAGE_BASE_URL - flutter analyze: No issues found
This commit is contained in:
19
app/lib/app.dart
Normal file
19
app/lib/app.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'core/theme/app_theme.dart';
|
||||
import 'core/routing/app_router.dart';
|
||||
|
||||
class NuanjiApp extends StatelessWidget {
|
||||
const NuanjiApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
title: '暖记',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: AppTheme.light(),
|
||||
darkTheme: AppTheme.dark(),
|
||||
themeMode: ThemeMode.system,
|
||||
routerConfig: appRouter,
|
||||
);
|
||||
}
|
||||
}
|
||||
24
app/lib/core/constants/breakpoints.dart
Normal file
24
app/lib/core/constants/breakpoints.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
// 暖记响应式断点
|
||||
// 手机 < 600 | 平板 600-1024 | 桌面 > 1024
|
||||
|
||||
class Breakpoints {
|
||||
Breakpoints._();
|
||||
|
||||
/// 手机最大宽度
|
||||
static const double mobile = 600;
|
||||
|
||||
/// 平板最大宽度
|
||||
static const double tablet = 1024;
|
||||
|
||||
/// 触摸目标最小尺寸 (WCAG + Apple HIG)
|
||||
static const double touchTarget = 44;
|
||||
|
||||
/// 判断设备类型
|
||||
static DeviceType getDeviceType(double width) {
|
||||
if (width < mobile) return DeviceType.mobile;
|
||||
if (width < tablet) return DeviceType.tablet;
|
||||
return DeviceType.desktop;
|
||||
}
|
||||
}
|
||||
|
||||
enum DeviceType { mobile, tablet, desktop }
|
||||
41
app/lib/core/constants/design_tokens.dart
Normal file
41
app/lib/core/constants/design_tokens.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
// 暖记设计常量 — 间距 / 动画 / 触摸目标
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
|
||||
class DesignTokens {
|
||||
DesignTokens._();
|
||||
|
||||
// ===== 间距 =====
|
||||
static const double spacing4 = 4;
|
||||
static const double spacing8 = 8;
|
||||
static const double spacing12 = 12;
|
||||
static const double spacing16 = 16;
|
||||
static const double spacing20 = 20;
|
||||
static const double spacing24 = 24;
|
||||
static const double spacing32 = 32;
|
||||
static const double spacing48 = 48;
|
||||
|
||||
// ===== 动画时长 =====
|
||||
static const Duration animFast = Duration(milliseconds: 150);
|
||||
static const Duration animNormal = Duration(milliseconds: 300);
|
||||
static const Duration animSlow = Duration(milliseconds: 500);
|
||||
|
||||
// ===== 弹性曲线 cubic-bezier(0.34, 1.56, 0.64, 1) =====
|
||||
static const Curve warmCurve = Curves.easeOutBack;
|
||||
|
||||
// ===== 列表/网格 =====
|
||||
static const int journalGridCrossAxisCountMobile = 2;
|
||||
static const int journalGridCrossAxisCountTablet = 3;
|
||||
static const int journalGridCrossAxisCountDesktop = 4;
|
||||
|
||||
// ===== 日记限制 =====
|
||||
static const int maxTagsPerJournal = 10;
|
||||
static const int maxTitleLength = 100;
|
||||
static const int maxStrokesPerElement = 5000;
|
||||
static const int maxUndoSteps = 50;
|
||||
|
||||
// ===== 班级码 =====
|
||||
static const int classCodeLength = 6;
|
||||
static const int classCodeMaxAttempts = 5;
|
||||
static const int classCodeLockoutMinutes = 30;
|
||||
}
|
||||
181
app/lib/core/routing/app_router.dart
Normal file
181
app/lib/core/routing/app_router.dart
Normal file
@@ -0,0 +1,181 @@
|
||||
// 暖记路由表 — go_router 20 页面
|
||||
|
||||
export '../../widgets/responsive_scaffold.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../widgets/responsive_scaffold.dart';
|
||||
|
||||
import '../../features/home/views/home_page.dart';
|
||||
import '../../features/calendar/views/calendar_page.dart';
|
||||
import '../../features/mood/views/mood_page.dart';
|
||||
import '../../features/search/views/search_page.dart';
|
||||
import '../../features/profile/views/profile_page.dart';
|
||||
import '../../features/editor/views/editor_page.dart';
|
||||
import '../../features/auth/views/login_page.dart';
|
||||
import '../../features/class_/views/class_page.dart';
|
||||
import '../../features/teacher/views/teacher_page.dart';
|
||||
import '../../features/parent/views/parent_page.dart';
|
||||
import '../../features/achievement/views/achievement_page.dart';
|
||||
import '../../features/stickers/views/sticker_library_page.dart';
|
||||
import '../../features/templates/views/template_gallery_page.dart';
|
||||
|
||||
// Shell 分支键
|
||||
final _rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final _shellNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
/// 暖记路由配置
|
||||
final appRouter = GoRouter(
|
||||
navigatorKey: _rootNavigatorKey,
|
||||
initialLocation: '/home',
|
||||
debugLogDiagnostics: true,
|
||||
routes: [
|
||||
// 认证路由(无 Shell)
|
||||
GoRoute(
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
builder: (context, state) => const LoginPage(),
|
||||
),
|
||||
|
||||
// 主 Shell 路由(底部导航 + 侧边导航)
|
||||
ShellRoute(
|
||||
navigatorKey: _shellNavigatorKey,
|
||||
builder: (context, state, child) {
|
||||
// 根据当前路径计算选中的 tab index
|
||||
final index = _selectedIndexFromLocation(state.uri.path);
|
||||
return _AppShell(
|
||||
selectedIndex: index,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
// Tab 0: 首页日记流
|
||||
GoRoute(
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
builder: (context, state) => const HomePage(),
|
||||
),
|
||||
// Tab 1: 日历
|
||||
GoRoute(
|
||||
path: '/calendar',
|
||||
name: 'calendar',
|
||||
builder: (context, state) => const CalendarPage(),
|
||||
),
|
||||
// Tab 2: 心情
|
||||
GoRoute(
|
||||
path: '/mood',
|
||||
name: 'mood',
|
||||
builder: (context, state) => const MoodPage(),
|
||||
),
|
||||
// Tab 3: 搜索
|
||||
GoRoute(
|
||||
path: '/search',
|
||||
name: 'search',
|
||||
builder: (context, state) => const SearchPage(),
|
||||
),
|
||||
// Tab 4: 个人中心
|
||||
GoRoute(
|
||||
path: '/profile',
|
||||
name: 'profile',
|
||||
builder: (context, state) => const ProfilePage(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// 全屏页面(无底部导航)
|
||||
GoRoute(
|
||||
path: '/editor',
|
||||
name: 'editor',
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) {
|
||||
final journalId = state.uri.queryParameters['id'];
|
||||
return EditorPage(journalId: journalId);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/class',
|
||||
name: 'class',
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) => const ClassPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/teacher',
|
||||
name: 'teacher',
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) => const TeacherPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/parent',
|
||||
name: 'parent',
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) => const ParentPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/achievements',
|
||||
name: 'achievements',
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) => const AchievementPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/stickers',
|
||||
name: 'stickers',
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) => const StickerLibraryPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/templates',
|
||||
name: 'templates',
|
||||
parentNavigatorKey: _rootNavigatorKey,
|
||||
builder: (context, state) => const TemplateGalleryPage(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
/// 路径 → Tab index 映射
|
||||
int _selectedIndexFromLocation(String location) {
|
||||
if (location.startsWith('/calendar')) return 1;
|
||||
if (location.startsWith('/mood')) return 2;
|
||||
if (location.startsWith('/search')) return 3;
|
||||
if (location.startsWith('/profile')) return 4;
|
||||
return 0; // 默认首页
|
||||
}
|
||||
|
||||
/// App Shell — 包裹 ResponsiveScaffold
|
||||
class _AppShell extends StatelessWidget {
|
||||
const _AppShell({
|
||||
required this.selectedIndex,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final int selectedIndex;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveScaffold(
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: (index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
context.go('/home');
|
||||
case 1:
|
||||
context.go('/calendar');
|
||||
case 2:
|
||||
context.go('/mood');
|
||||
case 3:
|
||||
context.go('/search');
|
||||
case 4:
|
||||
context.go('/profile');
|
||||
}
|
||||
},
|
||||
body: child,
|
||||
floatingActionButton: selectedIndex == 0
|
||||
? FloatingActionButton(
|
||||
onPressed: () => context.go('/editor'),
|
||||
child: const Icon(Icons.edit_rounded),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
128
app/lib/core/theme/app_colors.dart
Normal file
128
app/lib/core/theme/app_colors.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
// 暖记色彩系统 — 7 色 × 浅色/深色模式
|
||||
// 设计规格 v1.2
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 暖记色彩 Token
|
||||
class AppColors {
|
||||
AppColors._();
|
||||
|
||||
// ===== 浅色模式 =====
|
||||
|
||||
/// 奶油白背景 #FFF8F0
|
||||
static const Color bgLight = Color(0xFFFFF8F0);
|
||||
|
||||
/// 珊瑚色主色 #E07A5F
|
||||
static const Color accent = Color(0xFFE07A5F);
|
||||
|
||||
/// 鼠尾草绿 #81B29A
|
||||
static const Color secondary = Color(0xFF81B29A);
|
||||
|
||||
/// 暖金 #F2CC8F
|
||||
static const Color tertiary = Color(0xFFF2CC8F);
|
||||
|
||||
/// 主文字 #2D2420
|
||||
static const Color fgLight = Color(0xFF2D2420);
|
||||
|
||||
/// 卡片背景 #FFFFFF
|
||||
static const Color surfaceLight = Color(0xFFFFFFFF);
|
||||
|
||||
/// 玫瑰粉 #D4A5A5
|
||||
static const Color rose = Color(0xFFD4A5A5);
|
||||
|
||||
// ===== 深色模式 =====
|
||||
|
||||
/// 深色背景 #1A1614
|
||||
static const Color bgDark = Color(0xFF1A1614);
|
||||
|
||||
/// 深色珊瑚 #E8907A
|
||||
static const Color accentDark = Color(0xFFE8907A);
|
||||
|
||||
/// 深色鼠尾草 #8FBF9E
|
||||
static const Color secondaryDark = Color(0xFF8FBF9E);
|
||||
|
||||
/// 深色暖金 #D4B878
|
||||
static const Color tertiaryDark = Color(0xFFD4B878);
|
||||
|
||||
/// 深色主文字 #F0E8DF
|
||||
static const Color fgDark = Color(0xFFF0E8DF);
|
||||
|
||||
/// 深色卡片 #2A2520
|
||||
static const Color surfaceDark = Color(0xFF2A2520);
|
||||
|
||||
/// 深色玫瑰 #C4A0A0
|
||||
static const Color roseDark = Color(0xFFC4A0A0);
|
||||
|
||||
// ===== 功能色 =====
|
||||
|
||||
/// 错误红
|
||||
static const Color error = Color(0xFFD32F2F);
|
||||
|
||||
/// 成功绿
|
||||
static const Color success = Color(0xFF4CAF50);
|
||||
|
||||
/// 警告黄
|
||||
static const Color warning = Color(0xFFFFA726);
|
||||
|
||||
/// 信息蓝
|
||||
static const Color info = Color(0xFF42A5F5);
|
||||
|
||||
// ===== 心情颜色映射 =====
|
||||
|
||||
/// 心情 → 颜色
|
||||
static const Map<String, Color> moodColors = {
|
||||
'happy': Color(0xFFFFD93D), // 😊 开心 — 暖黄
|
||||
'calm': Color(0xFF81B29A), // 😌 平静 — 鼠尾草绿
|
||||
'sad': Color(0xFF7B9CC4), // 😢 难过 — 灰蓝
|
||||
'angry': Color(0xFFE07A5F), // 😠 生气 — 珊瑚
|
||||
'thinking': Color(0xFFB8A9C9),// 🤔 思考 — 淡紫
|
||||
};
|
||||
|
||||
// ===== 浅色主题色彩方案 =====
|
||||
|
||||
static const _light = ColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primary: accent,
|
||||
onPrimary: Colors.white,
|
||||
primaryContainer: Color(0xFFFFE0D6),
|
||||
onPrimaryContainer: fgLight,
|
||||
secondary: secondary,
|
||||
onSecondary: Colors.white,
|
||||
secondaryContainer: Color(0xFFD4E8DC),
|
||||
onSecondaryContainer: fgLight,
|
||||
tertiary: tertiary,
|
||||
onTertiary: fgLight,
|
||||
error: error,
|
||||
onError: Colors.white,
|
||||
surface: surfaceLight,
|
||||
onSurface: fgLight,
|
||||
surfaceContainerHighest: bgLight,
|
||||
);
|
||||
|
||||
// ===== 深色主题色彩方案 =====
|
||||
|
||||
static const _dark = ColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primary: accentDark,
|
||||
onPrimary: fgDark,
|
||||
primaryContainer: Color(0xFF5A2E22),
|
||||
onPrimaryContainer: Color(0xFFFFD6CC),
|
||||
secondary: secondaryDark,
|
||||
onSecondary: fgDark,
|
||||
secondaryContainer: Color(0xFF2A4A38),
|
||||
onSecondaryContainer: Color(0xFFD4E8DC),
|
||||
tertiary: tertiaryDark,
|
||||
onTertiary: fgDark,
|
||||
error: Color(0xFFEF5350),
|
||||
onError: fgDark,
|
||||
surface: surfaceDark,
|
||||
onSurface: fgDark,
|
||||
surfaceContainerHighest: bgDark,
|
||||
);
|
||||
|
||||
/// 获取浅色色彩方案
|
||||
static ColorScheme lightScheme() => _light;
|
||||
|
||||
/// 获取深色色彩方案
|
||||
static ColorScheme darkScheme() => _dark;
|
||||
}
|
||||
28
app/lib/core/theme/app_radius.dart
Normal file
28
app/lib/core/theme/app_radius.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
// 暖记圆角系统
|
||||
// 设计规格: 10 / 16 / 22 / 28 / pill
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppRadius {
|
||||
AppRadius._();
|
||||
|
||||
/// 小圆角 10px — 按钮、输入框
|
||||
static const double sm = 10;
|
||||
static BorderRadius get smBorder => BorderRadius.circular(sm);
|
||||
|
||||
/// 中圆角 16px — 卡片、弹窗
|
||||
static const double md = 16;
|
||||
static BorderRadius get mdBorder => BorderRadius.circular(md);
|
||||
|
||||
/// 大圆角 22px — 大卡片、底部面板
|
||||
static const double lg = 22;
|
||||
static BorderRadius get lgBorder => BorderRadius.circular(lg);
|
||||
|
||||
/// 超大圆角 28px — 模态框、全屏面板
|
||||
static const double xl = 28;
|
||||
static BorderRadius get xlBorder => BorderRadius.circular(xl);
|
||||
|
||||
/// 胶囊型 — 标签、Chip
|
||||
static const double pill = 100;
|
||||
static BorderRadius get pillBorder => BorderRadius.circular(pill);
|
||||
}
|
||||
49
app/lib/core/theme/app_shadows.dart
Normal file
49
app/lib/core/theme/app_shadows.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
// 暖记阴影系统 — soft / medium / float
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppShadows {
|
||||
AppShadows._();
|
||||
|
||||
/// 柔和阴影 — 卡片默认
|
||||
static List<BoxShadow> soft(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return [
|
||||
BoxShadow(
|
||||
color: isDark
|
||||
? Colors.black.withValues(alpha: 0.3)
|
||||
: const Color(0xFF2D2420).withValues(alpha: 0.08),
|
||||
offset: const Offset(0, 2),
|
||||
blurRadius: 8,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/// 中等阴影 — 浮动元素、FAB
|
||||
static List<BoxShadow> medium(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return [
|
||||
BoxShadow(
|
||||
color: isDark
|
||||
? Colors.black.withValues(alpha: 0.4)
|
||||
: const Color(0xFF2D2420).withValues(alpha: 0.12),
|
||||
offset: const Offset(0, 4),
|
||||
blurRadius: 16,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/// 浮动阴影 — 弹窗、底部面板
|
||||
static List<BoxShadow> floating(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return [
|
||||
BoxShadow(
|
||||
color: isDark
|
||||
? Colors.black.withValues(alpha: 0.5)
|
||||
: const Color(0xFF2D2420).withValues(alpha: 0.16),
|
||||
offset: const Offset(0, 8),
|
||||
blurRadius: 24,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
159
app/lib/core/theme/app_theme.dart
Normal file
159
app/lib/core/theme/app_theme.dart
Normal file
@@ -0,0 +1,159 @@
|
||||
// 暖记主题入口 — 浅色/深色 ThemeData
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import 'app_colors.dart';
|
||||
import 'app_typography.dart';
|
||||
import 'app_radius.dart';
|
||||
|
||||
class AppTheme {
|
||||
AppTheme._();
|
||||
|
||||
/// 浅色主题
|
||||
static ThemeData light() => _buildTheme(AppColors.lightScheme());
|
||||
|
||||
/// 深色主题
|
||||
static ThemeData dark() => _buildTheme(AppColors.darkScheme());
|
||||
|
||||
static ThemeData _buildTheme(ColorScheme colorScheme) {
|
||||
final isLight = colorScheme.brightness == Brightness.light;
|
||||
final textTheme = isLight
|
||||
? AppTypography.lightTextTheme()
|
||||
: AppTypography.darkTextTheme();
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: colorScheme,
|
||||
textTheme: textTheme,
|
||||
scaffoldBackgroundColor: isLight ? AppColors.bgLight : AppColors.bgDark,
|
||||
|
||||
// AppBar
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 1,
|
||||
centerTitle: true,
|
||||
backgroundColor: isLight ? AppColors.bgLight : AppColors.bgDark,
|
||||
foregroundColor: colorScheme.onSurface,
|
||||
titleTextStyle: textTheme.titleLarge,
|
||||
),
|
||||
|
||||
// Card
|
||||
cardTheme: CardThemeData(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
side: BorderSide(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
color: colorScheme.surface,
|
||||
),
|
||||
|
||||
// ElevatedButton
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: AppRadius.smBorder,
|
||||
),
|
||||
backgroundColor: colorScheme.primary,
|
||||
foregroundColor: colorScheme.onPrimary,
|
||||
textStyle: textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
|
||||
// OutlinedButton
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: AppRadius.smBorder,
|
||||
),
|
||||
side: BorderSide(color: colorScheme.primary),
|
||||
textStyle: textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
|
||||
// InputDecoration
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: AppRadius.smBorder,
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: AppRadius.smBorder,
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: AppRadius.smBorder,
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
),
|
||||
|
||||
// BottomNavigationBar
|
||||
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedItemColor: colorScheme.primary,
|
||||
unselectedItemColor: colorScheme.onSurface.withValues(alpha: 0.5),
|
||||
backgroundColor: colorScheme.surface,
|
||||
elevation: 8,
|
||||
),
|
||||
|
||||
// Chip
|
||||
chipTheme: ChipThemeData(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: AppRadius.pillBorder,
|
||||
),
|
||||
side: BorderSide.none,
|
||||
),
|
||||
|
||||
// FloatingActionButton
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
),
|
||||
elevation: 4,
|
||||
),
|
||||
|
||||
// Page transitions — 弹性曲线
|
||||
pageTransitionsTheme: PageTransitionsTheme(
|
||||
builders: {
|
||||
TargetPlatform.android: _WarmCurveBuilder(),
|
||||
TargetPlatform.iOS: const CupertinoPageTransitionsBuilder(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 暖记弹性页面转场: cubic-bezier(0.34, 1.56, 0.64, 1)
|
||||
class _WarmCurveBuilder extends PageTransitionsBuilder {
|
||||
const _WarmCurveBuilder();
|
||||
|
||||
static const Curve _warmCurve = Curves.easeOutBack;
|
||||
|
||||
@override
|
||||
Widget buildTransitions<T>(
|
||||
PageRoute<T> route,
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
return SlideTransition(
|
||||
position: animation.drive(
|
||||
Tween(begin: const Offset(1.0, 0.0), end: Offset.zero)
|
||||
.chain(CurveTween(curve: _warmCurve)),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
114
app/lib/core/theme/app_typography.dart
Normal file
114
app/lib/core/theme/app_typography.dart
Normal file
@@ -0,0 +1,114 @@
|
||||
// 暖记字体系统 — Noto Sans SC + Caveat
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppTypography {
|
||||
AppTypography._();
|
||||
|
||||
/// 字体族
|
||||
static const String displayFont = 'Caveat'; // 手写风格(标题装饰)
|
||||
static const String bodyFont = 'NotoSansSC'; // 正文(中文优先)
|
||||
static const String monoFont = 'JetBrains Mono'; // 等宽(暂用系统回退)
|
||||
|
||||
/// 浅色主题文字主题
|
||||
static TextTheme lightTextTheme() => TextTheme(
|
||||
// 大标题 — 手写风格
|
||||
displayLarge: TextStyle(
|
||||
fontFamily: displayFont,
|
||||
fontSize: 57,
|
||||
height: 1.12,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
fontFamily: displayFont,
|
||||
fontSize: 45,
|
||||
height: 1.16,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
displaySmall: TextStyle(
|
||||
fontFamily: displayFont,
|
||||
fontSize: 36,
|
||||
height: 1.22,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
// 标题 — 正文衬线
|
||||
headlineLarge: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 32,
|
||||
height: 1.25,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
headlineMedium: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 28,
|
||||
height: 1.29,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 24,
|
||||
height: 1.33,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
// 副标题
|
||||
titleLarge: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 22,
|
||||
height: 1.27,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 16,
|
||||
height: 1.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
titleSmall: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 14,
|
||||
height: 1.43,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
// 正文
|
||||
bodyLarge: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 16,
|
||||
height: 1.5,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 14,
|
||||
height: 1.43,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
bodySmall: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 12,
|
||||
height: 1.33,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
// 标签
|
||||
labelLarge: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 14,
|
||||
height: 1.43,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
labelMedium: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 12,
|
||||
height: 1.33,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
labelSmall: TextStyle(
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 11,
|
||||
height: 1.45,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
|
||||
/// 深色主题文字主题
|
||||
static TextTheme darkTextTheme() => lightTextTheme();
|
||||
}
|
||||
14
app/lib/features/achievement/views/achievement_page.dart
Normal file
14
app/lib/features/achievement/views/achievement_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AchievementPage extends StatelessWidget {
|
||||
const AchievementPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('成就 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
app/lib/features/auth/views/login_page.dart
Normal file
14
app/lib/features/auth/views/login_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoginPage extends StatelessWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('登录 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
app/lib/features/calendar/views/calendar_page.dart
Normal file
14
app/lib/features/calendar/views/calendar_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CalendarPage extends StatelessWidget {
|
||||
const CalendarPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('日历 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
app/lib/features/class_/views/class_page.dart
Normal file
14
app/lib/features/class_/views/class_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ClassPage extends StatelessWidget {
|
||||
const ClassPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('班级 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
20
app/lib/features/editor/views/editor_page.dart
Normal file
20
app/lib/features/editor/views/editor_page.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EditorPage extends StatelessWidget {
|
||||
final String? journalId;
|
||||
|
||||
const EditorPage({super.key, this.journalId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Text(
|
||||
journalId != null
|
||||
? '编辑日记 ($journalId) - 占位页面'
|
||||
: '新建日记 - 占位页面',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
app/lib/features/home/views/home_page.dart
Normal file
14
app/lib/features/home/views/home_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('首页 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
app/lib/features/mood/views/mood_page.dart
Normal file
14
app/lib/features/mood/views/mood_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MoodPage extends StatelessWidget {
|
||||
const MoodPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('心情 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
app/lib/features/parent/views/parent_page.dart
Normal file
14
app/lib/features/parent/views/parent_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ParentPage extends StatelessWidget {
|
||||
const ParentPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('家长 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
app/lib/features/profile/views/profile_page.dart
Normal file
14
app/lib/features/profile/views/profile_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ProfilePage extends StatelessWidget {
|
||||
const ProfilePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('我的 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
app/lib/features/search/views/search_page.dart
Normal file
14
app/lib/features/search/views/search_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SearchPage extends StatelessWidget {
|
||||
const SearchPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('搜索 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
app/lib/features/stickers/views/sticker_library_page.dart
Normal file
14
app/lib/features/stickers/views/sticker_library_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StickerLibraryPage extends StatelessWidget {
|
||||
const StickerLibraryPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('贴纸库 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
app/lib/features/teacher/views/teacher_page.dart
Normal file
14
app/lib/features/teacher/views/teacher_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TeacherPage extends StatelessWidget {
|
||||
const TeacherPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('教师 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
app/lib/features/templates/views/template_gallery_page.dart
Normal file
14
app/lib/features/templates/views/template_gallery_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TemplateGalleryPage extends StatelessWidget {
|
||||
const TemplateGalleryPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('模板画廊 - 占位页面'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
7
app/lib/main.dart
Normal file
7
app/lib/main.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'app.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(const NuanjiApp());
|
||||
}
|
||||
270
app/lib/widgets/responsive_scaffold.dart
Normal file
270
app/lib/widgets/responsive_scaffold.dart
Normal file
@@ -0,0 +1,270 @@
|
||||
// 暖记响应式骨架 — 三级自适应布局
|
||||
// 手机: 底部 TabBar | 平板: 侧边导航 | 桌面: 三栏
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../core/constants/breakpoints.dart';
|
||||
|
||||
/// 暖记自适应 Scaffold
|
||||
class ResponsiveScaffold extends StatefulWidget {
|
||||
const ResponsiveScaffold({
|
||||
super.key,
|
||||
required this.selectedIndex,
|
||||
required this.onDestinationSelected,
|
||||
required this.body,
|
||||
this.floatingActionButton,
|
||||
this.appBarTitle,
|
||||
this.secondaryBody,
|
||||
});
|
||||
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> onDestinationSelected;
|
||||
final Widget body;
|
||||
final Widget? floatingActionButton;
|
||||
final String? appBarTitle;
|
||||
final Widget? secondaryBody;
|
||||
|
||||
@override
|
||||
State<ResponsiveScaffold> createState() => _ResponsiveScaffoldState();
|
||||
}
|
||||
|
||||
class _ResponsiveScaffoldState extends State<ResponsiveScaffold> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.sizeOf(context).width;
|
||||
final deviceType = Breakpoints.getDeviceType(width);
|
||||
|
||||
switch (deviceType) {
|
||||
case DeviceType.mobile:
|
||||
return _MobileLayout(
|
||||
selectedIndex: widget.selectedIndex,
|
||||
onDestinationSelected: widget.onDestinationSelected,
|
||||
body: widget.body,
|
||||
floatingActionButton: widget.floatingActionButton,
|
||||
appBarTitle: widget.appBarTitle,
|
||||
);
|
||||
case DeviceType.tablet:
|
||||
return _TabletLayout(
|
||||
selectedIndex: widget.selectedIndex,
|
||||
onDestinationSelected: widget.onDestinationSelected,
|
||||
body: widget.body,
|
||||
appBarTitle: widget.appBarTitle,
|
||||
);
|
||||
case DeviceType.desktop:
|
||||
return _DesktopLayout(
|
||||
selectedIndex: widget.selectedIndex,
|
||||
onDestinationSelected: widget.onDestinationSelected,
|
||||
body: widget.body,
|
||||
secondaryBody: widget.secondaryBody,
|
||||
appBarTitle: widget.appBarTitle,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 导航项定义 =====
|
||||
|
||||
final _navItems = [
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.home_outlined),
|
||||
selectedIcon: const Icon(Icons.home),
|
||||
label: '首页',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.calendar_month_outlined),
|
||||
selectedIcon: const Icon(Icons.calendar_month),
|
||||
label: '日历',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.auto_awesome_outlined),
|
||||
selectedIcon: const Icon(Icons.auto_awesome),
|
||||
label: '心情',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
selectedIcon: const Icon(Icons.search),
|
||||
label: '搜索',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.person_outline),
|
||||
selectedIcon: const Icon(Icons.person),
|
||||
label: '我的',
|
||||
),
|
||||
];
|
||||
|
||||
const _railItems = [
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.home_outlined),
|
||||
selectedIcon: Icon(Icons.home),
|
||||
label: Text('首页'),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.calendar_month_outlined),
|
||||
selectedIcon: Icon(Icons.calendar_month),
|
||||
label: Text('日历'),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.auto_awesome_outlined),
|
||||
selectedIcon: Icon(Icons.auto_awesome),
|
||||
label: Text('心情'),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.search_outlined),
|
||||
selectedIcon: Icon(Icons.search),
|
||||
label: Text('搜索'),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.person_outline),
|
||||
selectedIcon: Icon(Icons.person),
|
||||
label: Text('我的'),
|
||||
),
|
||||
];
|
||||
|
||||
// ===== 手机布局 — 底部 TabBar =====
|
||||
|
||||
class _MobileLayout extends StatelessWidget {
|
||||
const _MobileLayout({
|
||||
required this.selectedIndex,
|
||||
required this.onDestinationSelected,
|
||||
required this.body,
|
||||
this.floatingActionButton,
|
||||
this.appBarTitle,
|
||||
});
|
||||
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> onDestinationSelected;
|
||||
final Widget body;
|
||||
final Widget? floatingActionButton;
|
||||
final String? appBarTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: appBarTitle != null
|
||||
? AppBar(title: Text(appBarTitle!))
|
||||
: null,
|
||||
body: body,
|
||||
floatingActionButton: floatingActionButton,
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: onDestinationSelected,
|
||||
destinations: _navItems,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 平板布局 — 侧边 NavigationRail =====
|
||||
|
||||
class _TabletLayout extends StatelessWidget {
|
||||
const _TabletLayout({
|
||||
required this.selectedIndex,
|
||||
required this.onDestinationSelected,
|
||||
required this.body,
|
||||
this.appBarTitle,
|
||||
});
|
||||
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> onDestinationSelected;
|
||||
final Widget body;
|
||||
final String? appBarTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: appBarTitle != null
|
||||
? AppBar(title: Text(appBarTitle!))
|
||||
: null,
|
||||
body: Row(
|
||||
children: [
|
||||
NavigationRail(
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: onDestinationSelected,
|
||||
destinations: _railItems,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Icon(
|
||||
Icons.edit_note_rounded,
|
||||
size: 32,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
Expanded(child: body),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 桌面布局 — 三栏 =====
|
||||
|
||||
class _DesktopLayout extends StatelessWidget {
|
||||
const _DesktopLayout({
|
||||
required this.selectedIndex,
|
||||
required this.onDestinationSelected,
|
||||
required this.body,
|
||||
this.secondaryBody,
|
||||
this.appBarTitle,
|
||||
});
|
||||
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int> onDestinationSelected;
|
||||
final Widget body;
|
||||
final Widget? secondaryBody;
|
||||
final String? appBarTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: appBarTitle != null
|
||||
? AppBar(title: Text(appBarTitle!))
|
||||
: null,
|
||||
body: Row(
|
||||
children: [
|
||||
NavigationRail(
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: onDestinationSelected,
|
||||
destinations: _railItems,
|
||||
extended: true,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.edit_note_rounded,
|
||||
size: 32,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'暖记',
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontFamily: 'Caveat',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
// 主内容区
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: body,
|
||||
),
|
||||
// 第二面板(日记详情/预览)
|
||||
if (secondaryBody != null) ...[
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: secondaryBody!,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user