docs: 更新协议文档,删除废弃计划文档

- 更新 http-error-codes, user-points-chat-data-protocol
- 更新 divination-run-protocol, profile-protocol
- 删除废弃的后端和前端设计计划文档
This commit is contained in:
qzl
2026-04-08 17:23:02 +08:00
parent 49fc9a116f
commit e80a82bef4
57 changed files with 4117 additions and 2269 deletions
@@ -4,6 +4,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:onboarding_overlay/onboarding_overlay.dart';
import 'package:sensors_plus/sensors_plus.dart';
import 'package:vibration/vibration.dart';
@@ -19,6 +20,7 @@ import '../../../../shared/widgets/date_time_picker/date_time_picker_bottom_shee
import '../../../../shared/widgets/toast/toast.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
import '../../data/models/divination_backend_models.dart';
import '../../data/apis/divination_api.dart';
import '../../data/models/divination_params.dart';
import '../../data/models/divination_result.dart';
import '../../data/services/divination_run_service.dart';
@@ -29,11 +31,13 @@ class AutoDivinationScreen extends StatefulWidget {
super.key,
required this.params,
required this.runService,
this.divinationApi,
required this.onCompleted,
});
final DivinationParams params;
final DivinationRunService runService;
final DivinationApi? divinationApi;
final Future<void> Function(DivinationResultData result) onCompleted;
@override
@@ -48,6 +52,7 @@ class _AutoDivinationScreenState extends State<AutoDivinationScreen>
YaoType.undetermined,
);
late final AnimationController _spinController;
late final AnimationController _blinkController;
StreamSubscription<AccelerometerEvent>? _accSubscription;
DateTime _selectedTime = DateTime.now();
bool _isSpinning = false;
@@ -60,6 +65,17 @@ class _AutoDivinationScreenState extends State<AutoDivinationScreen>
bool _spinLocked = false;
bool _submitting = false;
final GlobalKey<OnboardingState> _onboardingKey =
GlobalKey<OnboardingState>();
final ScrollController _scrollController = ScrollController();
final GlobalKey _step2TargetKey = GlobalKey();
final GlobalKey _step3TargetKey = GlobalKey();
final GlobalKey _step4TargetKey = GlobalKey();
final FocusNode _step1Focus = FocusNode();
final FocusNode _step2Focus = FocusNode();
final FocusNode _step3Focus = FocusNode();
final FocusNode _step4Focus = FocusNode();
@override
void initState() {
super.initState();
@@ -68,13 +84,23 @@ class _AutoDivinationScreenState extends State<AutoDivinationScreen>
vsync: this,
duration: const Duration(milliseconds: 500),
);
_blinkController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
)..repeat(reverse: true);
_listenShake();
}
@override
void dispose() {
_accSubscription?.cancel();
_scrollController.dispose();
_spinController.dispose();
_blinkController.dispose();
_step1Focus.dispose();
_step2Focus.dispose();
_step3Focus.dispose();
_step4Focus.dispose();
super.dispose();
}
@@ -96,35 +122,113 @@ class _AutoDivinationScreenState extends State<AutoDivinationScreen>
}
Widget _buildBody(BuildContext context, AppLocalizations l10n) {
return SingleChildScrollView(
padding: const EdgeInsets.all(AppSpacing.xl),
child: Column(
children: [
_InstructionCard(onTap: () => _showGuide(context, l10n)),
const SizedBox(height: AppSpacing.lg),
_TimeSelectorCard(selectedTime: _selectedTime, onPickTime: _pickTime),
const SizedBox(height: AppSpacing.lg),
_YaoPickerCard(
isSpinning: _isSpinning,
coin1Yang: _coin1Yang,
coin2Yang: _coin2Yang,
coin3Yang: _coin3Yang,
spinController: _spinController,
countdown: _countdown,
shakeCount: _shakeCount,
canShake: _canShake,
onStartShake: _startSpin,
buttonText: _buttonText(l10n),
statusText: _statusText(l10n),
),
const SizedBox(height: AppSpacing.lg),
_HexagramCard(yaoStates: _yaoStates),
const SizedBox(height: AppSpacing.lg),
_ResolveButton(
enabled: _shakeCount >= 6 && !_submitting,
onPressed: _submitRun,
),
],
final steps = [
OnboardingStep(
focusNode: _step1Focus,
titleText: l10n.autoGuideStep1Title,
bodyText: l10n.autoGuideStep1Body,
titleTextColor: Colors.white,
bodyTextColor: Colors.white,
hasArrow: false,
hasLabelBox: true,
fullscreen: true,
overlayColor: Colors.black.withValues(alpha: 0.7),
),
OnboardingStep(
focusNode: _step2Focus,
titleText: l10n.autoGuideStep2Title,
bodyText: l10n.autoGuideStep2Body,
titleTextColor: Colors.white,
bodyTextColor: Colors.white,
hasArrow: true,
hasLabelBox: true,
arrowPosition: ArrowPosition.top,
overlayColor: Colors.black.withValues(alpha: 0.7),
delay: const Duration(milliseconds: 320),
),
OnboardingStep(
focusNode: _step3Focus,
titleText: l10n.autoGuideStep3Title,
bodyText: l10n.autoGuideStep3Body,
titleTextColor: Colors.white,
bodyTextColor: Colors.white,
hasArrow: true,
hasLabelBox: true,
arrowPosition: ArrowPosition.top,
overlayColor: Colors.black.withValues(alpha: 0.7),
delay: const Duration(milliseconds: 320),
),
OnboardingStep(
focusNode: _step4Focus,
titleText: l10n.autoGuideStep4Title,
bodyText: l10n.autoGuideStep4Body,
titleTextColor: Colors.white,
bodyTextColor: Colors.white,
hasArrow: true,
hasLabelBox: true,
arrowPosition: ArrowPosition.top,
overlayColor: Colors.black.withValues(alpha: 0.7),
delay: const Duration(milliseconds: 320),
),
];
return Onboarding(
key: _onboardingKey,
steps: steps,
onChanged: _onGuideStepChanged,
child: SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(AppSpacing.xl),
child: Column(
children: [
_InstructionCard(onTap: _showGuide),
const SizedBox(height: AppSpacing.lg),
_TimeSelectorCard(
selectedTime: _selectedTime,
onPickTime: _pickTime,
),
const SizedBox(height: AppSpacing.lg),
Container(
key: _step2TargetKey,
child: Focus(
focusNode: _step2Focus,
child: _YaoPickerCard(
isSpinning: _isSpinning,
coin1Yang: _coin1Yang,
coin2Yang: _coin2Yang,
coin3Yang: _coin3Yang,
spinController: _spinController,
countdown: _countdown,
shakeCount: _shakeCount,
canShake: _canShake,
onStartShake: _startSpin,
buttonText: _buttonText(l10n),
statusText: _statusText(l10n),
),
),
),
const SizedBox(height: AppSpacing.lg),
Container(
key: _step3TargetKey,
child: Focus(
focusNode: _step3Focus,
child: _HexagramCard(yaoStates: _yaoStates),
),
),
const SizedBox(height: AppSpacing.lg),
Container(
key: _step4TargetKey,
child: Focus(
focusNode: _step4Focus,
child: _ResolveButton(
enabled: _shakeCount >= 6 && !_submitting,
onPressed: _submitRun,
blinkAnimation: _blinkController,
),
),
),
],
),
),
);
}
@@ -286,6 +390,7 @@ class _AutoDivinationScreenState extends State<AutoDivinationScreen>
params: widget.params.copyWith(divinationTime: _selectedTime),
yaoStates: _yaoStates,
runService: widget.runService,
divinationApi: widget.divinationApi,
onCompleted: widget.onCompleted,
),
),
@@ -311,24 +416,35 @@ class _AutoDivinationScreenState extends State<AutoDivinationScreen>
await HapticFeedback.heavyImpact();
}
Future<void> _showGuide(BuildContext context, AppLocalizations l10n) async {
await showDialog<void>(
context: context,
builder: (context) {
return DivinationGuideDialog(
title: l10n.divinationManualGuideTitle,
guideImages: const [
['assets/images/tutorial/tutorial_1.png'],
['assets/images/tutorial/tutorial_2.png'],
['assets/images/tutorial/tutorial_3.png'],
],
instructions: [
l10n.divinationManualGuideStep1,
l10n.divinationManualGuideStep2,
l10n.divinationManualGuideStep3,
],
);
},
void _showGuide() {
_scrollToGuideStep(0);
Future<void>.delayed(const Duration(milliseconds: 120), () {
if (!mounted) {
return;
}
_onboardingKey.currentState?.show();
});
}
void _onGuideStepChanged(int currentIndex) {
_scrollToGuideStep(currentIndex + 1);
}
void _scrollToGuideStep(int stepIndex) {
final GlobalKey? targetKey = switch (stepIndex) {
2 => _step3TargetKey,
3 => _step4TargetKey,
_ => null,
};
final targetContext = targetKey?.currentContext;
if (targetContext == null) {
return;
}
Scrollable.ensureVisible(
targetContext,
duration: const Duration(milliseconds: 260),
curve: Curves.easeOut,
alignment: 0.12,
);
}
}
@@ -532,7 +648,7 @@ class _CoinColumn extends StatelessWidget {
),
const SizedBox(height: AppSpacing.sm),
Text(
DivinationTerms.ziHua[isYang] ?? '',
_coinFaceLabel(context, isYang),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colors.onSurface,
fontWeight: FontWeight.w700,
@@ -541,6 +657,11 @@ class _CoinColumn extends StatelessWidget {
],
);
}
String _coinFaceLabel(BuildContext context, bool isYang) {
final l10n = AppLocalizations.of(context)!;
return isYang ? l10n.autoCoinFaceZi : l10n.autoCoinFaceHua;
}
}
class _CoinFace extends StatelessWidget {
@@ -567,8 +688,8 @@ class _CoinFace extends StatelessWidget {
: (isYang ? 0 : 180);
final showingYang = isSpinning ? rotationY < 90 : isYang;
final image = showingYang
? 'assets/images/qigua/hua.jpg'
: 'assets/images/qigua/zi.jpg';
? 'assets/images/qigua/zi.jpg'
: 'assets/images/qigua/hua.jpg';
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
@@ -632,10 +753,11 @@ class _YaoRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Padding(
padding: const EdgeInsets.symmetric(vertical: AppSpacing.sm),
child: YaoLineRow(
name: DivinationTerms.yaoNames[index],
name: DivinationTerms.yaoName(l10n, index),
type: type,
showChangeMark: true,
lineHeight: 8,
@@ -645,21 +767,38 @@ class _YaoRow extends StatelessWidget {
}
class _ResolveButton extends StatelessWidget {
const _ResolveButton({required this.enabled, required this.onPressed});
const _ResolveButton({
required this.enabled,
required this.onPressed,
required this.blinkAnimation,
});
final bool enabled;
final VoidCallback onPressed;
final Animation<double> blinkAnimation;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return SizedBox(
width: double.infinity,
height: 50,
child: FilledButton(
onPressed: enabled ? onPressed : null,
child: Text(l10n.autoStartResolve),
),
final colors = Theme.of(context).colorScheme;
return AnimatedBuilder(
animation: blinkAnimation,
builder: (context, _) {
final base = colors.primary;
return SizedBox(
width: double.infinity,
height: 50,
child: FilledButton(
onPressed: enabled ? onPressed : null,
style: FilledButton.styleFrom(
backgroundColor: enabled
? base.withValues(alpha: 0.6 + blinkAnimation.value * 0.4)
: base,
),
child: Text(l10n.autoStartResolve),
),
);
},
);
}
}