feat: 添加起卦教程首次访问追踪和Agent时间上下文

- 后端 ProfileSettingsV1 添加 DivinationTutorialSettings 字段
- 前端三个起卦页面添加首次访问检测,自动弹出教程
- 教程展示后更新 settings 标记,避免重复弹出
- 使用本地状态管理避免并发更新覆盖问题
- Agent 系统提示添加时间上下文信息
This commit is contained in:
qzl
2026-04-15 18:56:41 +08:00
parent 55eeab43df
commit 69b34bd723
13 changed files with 624 additions and 0 deletions
@@ -18,6 +18,7 @@ import '../../../../shared/widgets/divination/yao_line_row.dart';
import '../../../../shared/widgets/date_time_picker/date_time_picker_bottom_sheet.dart';
import '../../../../shared/widgets/toast/toast.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
import '../../../settings/data/models/profile_settings.dart';
import '../../data/models/divination_backend_models.dart';
import '../../data/apis/divination_api.dart';
import '../../data/models/divination_params.dart';
@@ -33,12 +34,17 @@ class AutoDivinationScreen extends StatefulWidget {
required this.runService,
this.divinationApi,
required this.onCompleted,
required this.profileSettings,
required this.onProfileSettingsChanged,
});
final DivinationParams params;
final DivinationRunService runService;
final DivinationApi? divinationApi;
final Future<void> Function(DivinationResultData result) onCompleted;
final ProfileSettingsV1 profileSettings;
final Future<void> Function(ProfileSettingsV1 settings)
onProfileSettingsChanged;
@override
State<AutoDivinationScreen> createState() => _AutoDivinationScreenState();
@@ -64,6 +70,8 @@ class _AutoDivinationScreenState extends State<AutoDivinationScreen>
DateTime _lastShake = DateTime.fromMillisecondsSinceEpoch(0);
bool _spinLocked = false;
bool _submitting = false;
bool _tutorialChecked = false;
late DivinationTutorialSettings _localTutorialSettings;
final GlobalKey<OnboardingState> _onboardingKey =
GlobalKey<OnboardingState>();
@@ -89,6 +97,37 @@ class _AutoDivinationScreenState extends State<AutoDivinationScreen>
duration: const Duration(milliseconds: 500),
)..repeat(reverse: true);
_listenShake();
_localTutorialSettings = widget.profileSettings.divinationTutorial;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_tutorialChecked) {
_tutorialChecked = true;
_checkFirstVisit();
}
}
void _checkFirstVisit() {
if (!_localTutorialSettings.autoDivinationShown) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
_showGuide();
});
}
}
Future<void> _markTutorialShown() async {
setState(() {
_localTutorialSettings = _localTutorialSettings.copyWith(
autoDivinationShown: true,
);
});
final updated = widget.profileSettings.copyWith(
divinationTutorial: _localTutorialSettings,
);
await widget.onProfileSettingsChanged(updated);
}
@override
@@ -176,6 +215,7 @@ class _AutoDivinationScreenState extends State<AutoDivinationScreen>
key: _onboardingKey,
steps: steps,
onChanged: _onGuideStepChanged,
onEnd: (_) => _markTutorialShown(),
child: SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(AppSpacing.xl),