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

+114 -4
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mobile/main.dart';
import 'package:mobile/widgets/grouped_list_section.dart';
/// 在导航栏内按标签定位,避免和页面标题的相同文字混淆。
Finder navLabel(String label) {
@@ -11,9 +12,22 @@ Finder navLabel(String label) {
);
}
/// 手机尺寸(400 × 800 dp),避免默认 800×600 掩盖溢出问题。
void usePhoneSurface(WidgetTester tester) {
tester.view.physicalSize = const Size(400, 800);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.reset);
}
Future<void> pumpApp(WidgetTester tester) async {
usePhoneSurface(tester);
await tester.pumpWidget(const KiriMemoApp());
await tester.pumpAndSettle();
}
void main() {
testWidgets('底部导航提供学习 / 管理 / 统计 / 账户四个入口', (WidgetTester tester) async {
await tester.pumpWidget(const KiriMemoApp());
await pumpApp(tester);
expect(find.byType(NavigationBar), findsOneWidget);
expect(find.byType(NavigationDestination), findsNWidgets(4));
@@ -23,8 +37,19 @@ void main() {
}
});
testWidgets('Android 端使用水平滑入转场,而非预测式返回', (WidgetTester tester) async {
await pumpApp(tester);
final MaterialApp app = tester.widget<MaterialApp>(find.byType(MaterialApp));
final PageTransitionsTheme transitions = app.theme!.pageTransitionsTheme;
expect(
transitions.builders[TargetPlatform.android],
isA<FadeForwardsPageTransitionsBuilder>(),
);
});
testWidgets('默认停留在学习页,点击其它入口会切换', (WidgetTester tester) async {
await tester.pumpWidget(const KiriMemoApp());
await pumpApp(tester);
NavigationBar bar = tester.widget<NavigationBar>(find.byType(NavigationBar));
expect(bar.selectedIndex, 0);
@@ -43,7 +68,7 @@ void main() {
});
testWidgets('学习首页展示今日任务与唯一主行动', (WidgetTester tester) async {
await tester.pumpWidget(const KiriMemoApp());
await pumpApp(tester);
expect(find.text('今日计划'), findsOneWidget);
expect(find.text('待复习'), findsOneWidget);
@@ -62,12 +87,97 @@ void main() {
for (final String label in <String>['收藏夹', '自我测试', '附带资料', '计划设置']) {
expect(find.text(label), findsOneWidget);
}
});
testWidgets('未接入内核的入口点击后给出反馈', (WidgetTester tester) async {
await pumpApp(tester);
// 未接入内核的入口点击后要有反馈,不能静默无响应。
await tester.tap(find.text('开始学习'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
expect(find.text('学习会话 尚未接入内核'), findsOneWidget);
});
testWidgets('管理页有三个维度,各自列出卡片', (WidgetTester tester) async {
await pumpApp(tester);
await tester.tap(navLabel('管理'));
await tester.pumpAndSettle();
for (final String label in <String>['单元集', '计划', '算法']) {
expect(find.text(label), findsOneWidget);
}
expect(find.text('高考必背古诗文'), findsOneWidget);
expect(find.text('标识 cngk-t'), findsOneWidget);
await tester.tap(find.text('计划'));
await tester.pumpAndSettle();
expect(find.text('每日计划'), findsOneWidget);
expect(find.text('周末冲刺'), findsOneWidget);
expect(find.text('新识记'), findsNWidgets(2));
await tester.tap(find.text('算法'));
await tester.pumpAndSettle();
expect(find.text('NSP-0'), findsOneWidget);
expect(find.text('默认'), findsOneWidget);
});
testWidgets('账户页展示资料、学习数据与四个功能入口', (WidgetTester tester) async {
await pumpApp(tester);
await tester.tap(navLabel('账户'));
await tester.pumpAndSettle();
expect(find.text('zhiyu'), findsOneWidget);
expect(find.text('ID KM-1024 · 大一'), findsOneWidget);
expect(find.text('累计学习'), findsOneWidget);
expect(find.text('坚持'), findsOneWidget);
// 四个入口是同一张分组卡片里的四行。
expect(find.byType(GroupedListSection), findsOneWidget);
expect(find.byType(GroupedListTile), findsNWidgets(4));
for (final String label in <String>[
'设置',
'文档与帮助',
'反馈问题',
'关于 KiriMemo',
]) {
expect(find.text(label), findsOneWidget);
}
});
testWidgets('账户页可以进入设置页,设置项按分组排列', (WidgetTester tester) async {
await pumpApp(tester);
await tester.tap(navLabel('账户'));
await tester.pumpAndSettle();
await tester.tap(find.text('设置'));
await tester.pumpAndSettle();
expect(find.text('每日新识记上限'), findsOneWidget);
expect(find.text('答错后重新排队'), findsOneWidget);
// 分组在下方,需要滚动到可见。
await tester.scrollUntilVisible(find.text('版本'), 200);
expect(find.text('版本'), findsOneWidget);
expect(find.text('1.0.0'), findsOneWidget);
});
testWidgets('文档与帮助页底部有可上滑的助手', (WidgetTester tester) async {
await pumpApp(tester);
await tester.tap(navLabel('账户'));
await tester.pumpAndSettle();
await tester.tap(find.text('文档与帮助'));
await tester.pumpAndSettle();
expect(find.text('文档内容将在这里显示'), findsOneWidget);
expect(find.text('问问 KiriMemo'), findsOneWidget);
// 收起时是横栏,点开后出现输入框。
expect(find.byType(TextField), findsNothing);
await tester.tap(find.text('问问 KiriMemo'));
await tester.pumpAndSettle();
expect(find.byType(TextField), findsOneWidget);
});
}