Files
KiriMemo/lib/pages/settings_page.dart
pluv 22715f7247 feat: manage, account and help pages
- 管理页:单元集 / 计划 / 算法三个维度,卡片带元数据
- 账户页:资料、学习数据、竖直分组的四个功能入口
- 新增设置、文档与帮助(底部可上滑助手)、反馈、关于四个页面
- 抽出 StatBlock 与 GroupedListSection 共用组件
- 关闭 Android 预测式返回,转场改为 M3 水平滑入
2026-10-24 15:21:30 +08:00

222 lines
6.0 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import '../app_info.dart';
/// 设置页,按 Android 系统设置的分组列表组织。
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
bool _requeueOnWrong = true;
bool _readAloud = true;
bool _autoSync = true;
bool _wifiOnly = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('设置')),
body: ListView(
padding: const EdgeInsets.only(bottom: 32),
children: <Widget>[
const _SectionHeader('学习'),
_ValueTile(
icon: Icons.school_outlined,
title: '每日新识记上限',
value: '35 条',
onTap: () => _pending('每日新识记上限'),
),
_ValueTile(
icon: Icons.replay_rounded,
title: '每日复习上限',
value: '200 条',
onTap: () => _pending('每日复习上限'),
),
_ValueTile(
icon: Icons.schedule_rounded,
title: '学习时段',
value: '08:00–23:00',
onTap: () => _pending('学习时段'),
),
_SwitchTile(
icon: Icons.restart_alt_rounded,
title: '答错后重新排队',
subtitle: '同一轮里再出现一次',
value: _requeueOnWrong,
onChanged: (bool value) => setState(() => _requeueOnWrong = value),
),
const _SectionHeader('测验'),
_ValueTile(
icon: Icons.quiz_outlined,
title: '默认谜题类型',
value: '完形填空',
onTap: () => _pending('默认谜题类型'),
),
_SwitchTile(
icon: Icons.volume_up_outlined,
title: '自动朗读',
subtitle: '进入谜题时播放语音',
value: _readAloud,
onChanged: (bool value) => setState(() => _readAloud = value),
),
const _SectionHeader('外观'),
_ValueTile(
icon: Icons.dark_mode_outlined,
title: '主题',
value: '跟随系统',
onTap: () => _pending('主题'),
),
_ValueTile(
icon: Icons.text_fields_rounded,
title: '字号',
value: '标准',
onTap: () => _pending('字号'),
),
const _SectionHeader('同步'),
_SwitchTile(
icon: Icons.cloud_sync_outlined,
title: '自动同步',
subtitle: '学完一轮后自动上传',
value: _autoSync,
onChanged: (bool value) => setState(() => _autoSync = value),
),
_SwitchTile(
icon: Icons.wifi_rounded,
title: '仅在 Wi-Fi 下同步',
value: _wifiOnly,
onChanged: (bool value) => setState(() => _wifiOnly = value),
),
_ValueTile(
icon: Icons.sync_rounded,
title: '立即同步',
onTap: () => _pending('立即同步'),
),
const _SectionHeader('关于'),
_ValueTile(
icon: Icons.info_outline_rounded,
title: '版本',
value: appVersion,
),
_ValueTile(
icon: Icons.description_outlined,
title: '开源许可',
onTap: () => _pending('开源许可'),
),
],
),
);
}
void _pending(String feature) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('$feature 尚未接入内核'),
behavior: SnackBarBehavior.floating,
),
);
}
}
class _SectionHeader extends StatelessWidget {
const _SectionHeader(this.title);
final String title;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 6),
child: Text(
title,
style: theme.textTheme.labelLarge?.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w600,
),
),
);
}
}
class _ValueTile extends StatelessWidget {
const _ValueTile({
required this.icon,
required this.title,
this.value,
this.onTap,
});
final IconData icon;
final String title;
final String? value;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
final ColorScheme colors = Theme.of(context).colorScheme;
final String? value = this.value;
return ListTile(
leading: Icon(icon, color: colors.onSurfaceVariant),
title: Text(title),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (value != null)
Text(
value,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colors.onSurfaceVariant,
),
),
if (onTap != null) ...<Widget>[
const SizedBox(width: 4),
Icon(
Icons.chevron_right_rounded,
size: 20,
color: colors.onSurfaceVariant,
),
],
],
),
onTap: onTap,
);
}
}
class _SwitchTile extends StatelessWidget {
const _SwitchTile({
required this.icon,
required this.title,
required this.value,
required this.onChanged,
this.subtitle,
});
final IconData icon;
final String title;
final String? subtitle;
final bool value;
final ValueChanged<bool> onChanged;
@override
Widget build(BuildContext context) {
final ColorScheme colors = Theme.of(context).colorScheme;
return SwitchListTile(
secondary: Icon(icon, color: colors.onSurfaceVariant),
title: Text(title),
subtitle: subtitle == null ? null : Text(subtitle!),
value: value,
onChanged: onChanged,
);
}
}