- 管理页:单元集 / 计划 / 算法三个维度,卡片带元数据 - 账户页:资料、学习数据、竖直分组的四个功能入口 - 新增设置、文档与帮助(底部可上滑助手)、反馈、关于四个页面 - 抽出 StatBlock 与 GroupedListSection 共用组件 - 关闭 Android 预测式返回,转场改为 M3 水平滑入
118 lines
3.4 KiB
Dart
118 lines
3.4 KiB
Dart
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,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|