docs: 更新协议文档,删除废弃计划文档
- 更新 http-error-codes, user-points-chat-data-protocol - 更新 divination-run-protocol, profile-protocol - 删除废弃的后端和前端设计计划文档
This commit is contained in:
@@ -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),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user