feat: manage, account and help pages

- 管理页:单元集 / 计划 / 算法三个维度,卡片带元数据
- 账户页:资料、学习数据、竖直分组的四个功能入口
- 新增设置、文档与帮助(底部可上滑助手)、反馈、关于四个页面
- 抽出 StatBlock 与 GroupedListSection 共用组件
- 关闭 Android 预测式返回,转场改为 M3 水平滑入
This commit is contained in:
pluv committed 2026-10-24 15:21:30 +08:00
1 parent 2a307b2f48
commit 22715f7247
15 files changed
+1789 -72

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
import 'package:flutter/material.dart';
/// 一组圆角卡片式列表项,行间自动加分隔线。
///
/// 容器用 [Material] 而不是 `Container`:`InkWell` 的水波纹画在最近的
/// `Material` 祖先上,普通容器会让墨水落到外层 Scaffold,圆角裁剪失效,
/// 按住时就变成直角矩形。
class GroupedListSection extends StatelessWidget {
const GroupedListSection({
required this.tiles,
this.dividerIndent = 58,
super.key,
});
final List<Widget> tiles;
/// 分隔线左侧缩进,默认与图标后的文字对齐。
final double dividerIndent;
@override
Widget build(BuildContext context) {
final ColorScheme colors = Theme.of(context).colorScheme;
return Material(
color: colors.surfaceContainerLow,
borderRadius: BorderRadius.circular(20),
clipBehavior: Clip.antiAlias,
child: Column(
children: <Widget>[
for (final (int index, Widget tile) in tiles.indexed) ...<Widget>[
tile,
if (index != tiles.length - 1)
Divider(
height: 1,
thickness: 1,
indent: dividerIndent,
color: colors.outlineVariant.withValues(alpha: 0.6),
),
],
],
),
);
}
}
/// 分组列表里的一行:图标 + 标题(+ 可选副标题)+ 右侧箭头。
class GroupedListTile extends StatelessWidget {
const GroupedListTile({
required this.icon,
required this.title,
this.subtitle,
this.onTap,
this.trailing,
super.key,
});
final IconData icon;
final String title;
final String? subtitle;
final VoidCallback? onTap;
/// 右侧自定义内容;不传且有 [onTap] 时显示箭头。
final Widget? trailing;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ColorScheme colors = theme.colorScheme;
final String? subtitle = this.subtitle;
final Widget? trailing = this.trailing;
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 18),
child: Row(
children: <Widget>[
Icon(icon, size: 22, color: colors.primary),
const SizedBox(width: 18),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
title,
style: theme.textTheme.bodyLarge,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (subtitle != null) ...<Widget>[
const SizedBox(height: 2),
Text(
subtitle,
style: theme.textTheme.bodySmall?.copyWith(
color: colors.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
],
),
),
if (trailing != null)
trailing
else if (onTap != null)
Icon(
Icons.chevron_right_rounded,
size: 22,
color: colors.onSurfaceVariant,
),
],
),
),
);
}
}
+61
View File
@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
/// 「标签 + 大数字 +(可选)单位」的组合,学习页与账户页共用。
class StatBlock extends StatelessWidget {
const StatBlock({
required this.label,
required this.value,
this.unit,
super.key,
});
final String label;
final String value;
final String? unit;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ColorScheme colors = theme.colorScheme;
final String? unit = this.unit;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
label,
style: theme.textTheme.bodyMedium?.copyWith(
color: colors.onSurfaceVariant,
),
),
const SizedBox(height: 2),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: <Widget>[
Flexible(
child: Text(
value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.displaySmall?.copyWith(
fontWeight: FontWeight.w700,
height: 1.1,
),
),
),
if (unit != null) ...<Widget>[
const SizedBox(width: 6),
Text(
unit,
style: theme.textTheme.bodySmall?.copyWith(
color: colors.onSurfaceVariant,
),
),
],
],
),
],
);
}
}