Files
English/kouyu_english/lib/features/onboarding/onboarding_pages.dart
T

228 lines
6.2 KiB
Dart

import 'package:flutter/material.dart';
import '../../core/app_state.dart';
import '../../core/app_theme.dart';
import '../../core/models.dart';
import '../../widgets/app_widgets.dart';
class WelcomePage extends StatefulWidget {
const WelcomePage({super.key, required this.state, required this.onContinue});
final AppState state;
final VoidCallback onContinue;
@override
State<WelcomePage> createState() => _WelcomePageState();
}
class _WelcomePageState extends State<WelcomePage> {
late LearningGoal selectedGoal;
late int selectedMinutes;
@override
void initState() {
super.initState();
selectedGoal = widget.state.goal;
selectedMinutes = widget.state.dailyMinutes;
}
@override
Widget build(BuildContext context) {
return AppPage(
child: SpacedColumn(
spacing: 20,
children: [
const Eyebrow('欢迎'),
Text(
'每天 20 分钟,\n说出能用的英语。',
style: Theme.of(context).textTheme.headlineMedium,
),
const Text('从真实生活场景开始,先做到听得懂、说得清。'),
_ChoiceGroup<LearningGoal>(
title: '你的主要目标',
value: selectedGoal,
options: const {
LearningGoal.dailyLife: '日常生活',
LearningGoal.travel: '旅行',
LearningGoal.workStarter: '工作起步',
},
onChanged: (value) => setState(() => selectedGoal = value),
),
_ChoiceGroup<int>(
title: '每天学习多久?',
value: selectedMinutes,
options: const {10: '10 分钟', 20: '20 分钟', 30: '30 分钟'},
onChanged: (value) => setState(() => selectedMinutes = value),
),
PrimaryButton(
label: '继续',
onPressed: () {
widget.state
..setGoal(selectedGoal)
..setDailyMinutes(selectedMinutes);
widget.onContinue();
},
),
],
),
);
}
}
class PlacementPage extends StatefulWidget {
const PlacementPage({
super.key,
required this.state,
required this.onStart,
this.onBack,
});
final AppState state;
final VoidCallback onStart;
final VoidCallback? onBack;
@override
State<PlacementPage> createState() => _PlacementPageState();
}
class _PlacementPageState extends State<PlacementPage> {
late PlacementLevel selected;
@override
void initState() {
super.initState();
selected = widget.state.placement;
}
@override
Widget build(BuildContext context) {
const labels = {
PlacementLevel.beginner: ('完全零基础', '从你好、自我介绍开始'),
PlacementLevel.someBasics: ('能说一点', '认识常见单词或短句'),
PlacementLevel.simpleConversation: ('能简单对话', '想说得更自然、更有信心'),
};
return AppPage(
appBar: widget.onBack != null
? AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
tooltip: "返回",
onPressed: widget.onBack,
),
title: const Text("基础定位"),
)
: null,
child: SpacedColumn(
spacing: 14,
children: [
const Eyebrow('第一步 · 约 3 分钟'),
Text('从哪里开始?', style: Theme.of(context).textTheme.headlineMedium),
const Text('选择最接近的状态,之后随时能调整。'),
for (final option in PlacementLevel.values)
_PlacementChoice(
option: option,
labels: labels[option]!,
isSelected: selected == option,
onTap: () => setState(() => selected = option),
),
PrimaryButton(
label: '开始 3 分钟定位',
onPressed: () {
widget.state.setPlacement(selected);
widget.onStart();
},
),
Center(
child: TextButton(
onPressed: widget.onStart,
child: const Text('直接从第一课开始'),
),
),
],
),
);
}
}
class _PlacementChoice extends StatelessWidget {
const _PlacementChoice({
required this.option,
required this.labels,
required this.isSelected,
required this.onTap,
});
final PlacementLevel option;
final (String, String) labels;
final bool isSelected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) => SectionCard(
tint: isSelected ? AppColors.softGreen : null,
onTap: onTap,
child: Row(
children: [
Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_off,
color: isSelected ? AppColors.green : AppColors.muted,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
labels.$1,
style: const TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 3),
Text(labels.$2, style: Theme.of(context).textTheme.bodyMedium),
],
),
),
],
),
);
}
class _ChoiceGroup<T> extends StatelessWidget {
const _ChoiceGroup({
required this.title,
required this.value,
required this.options,
required this.onChanged,
});
final String title;
final T value;
final Map<T, String> options;
final ValueChanged<T> onChanged;
@override
Widget build(BuildContext context) {
return SectionCard(
child: SpacedColumn(
spacing: 10,
children: [
Text(title, style: const TextStyle(fontWeight: FontWeight.w600)),
Wrap(
spacing: 8,
runSpacing: 8,
children: options.entries
.map(
(entry) => ChoiceChip(
label: Text(entry.value),
selected: value == entry.key,
selectedColor: AppColors.softGreen,
onSelected: (_) => onChanged(entry.key),
),
)
.toList(),
),
],
),
);
}
}