feat: manage, account and help pages
- 管理页:单元集 / 计划 / 算法三个维度,卡片带元数据 - 账户页:资料、学习数据、竖直分组的四个功能入口 - 新增设置、文档与帮助(底部可上滑助手)、反馈、关于四个页面 - 抽出 StatBlock 与 GroupedListSection 共用组件 - 关闭 Android 预测式返回,转场改为 M3 水平滑入
This commit is contained in:
1 parent
2a307b2f48
commit
22715f7247
15 files changed
+1789
-72
No files matched your search
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user