feat: 实现用户画像、占卜历史与后端用户管理模块
This commit is contained in:
+184
-35
@@ -20,32 +20,57 @@ class DivinationProcessingScreen extends StatefulWidget {
|
||||
required this.params,
|
||||
required this.yaoStates,
|
||||
required this.runService,
|
||||
required this.onCompleted,
|
||||
});
|
||||
|
||||
final DivinationParams params;
|
||||
final List<YaoType> yaoStates;
|
||||
final DivinationRunService runService;
|
||||
final Future<void> Function(DivinationResultData result) onCompleted;
|
||||
|
||||
@override
|
||||
State<DivinationProcessingScreen> createState() =>
|
||||
_DivinationProcessingScreenState();
|
||||
}
|
||||
|
||||
class _DivinationProcessingScreenState
|
||||
extends State<DivinationProcessingScreen> {
|
||||
class _DivinationProcessingScreenState extends State<DivinationProcessingScreen>
|
||||
with TickerProviderStateMixin {
|
||||
static final Logger _logger = getLogger(
|
||||
'features.divination.processing_screen',
|
||||
);
|
||||
static const int _iChingCardCount = 8;
|
||||
_ProcessingStep _step = _ProcessingStep.preparing;
|
||||
DivinationResultData? _resultData;
|
||||
String? _errorMessage;
|
||||
late final AnimationController _cardRotationController;
|
||||
int _currentCardIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_cardRotationController =
|
||||
AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 2000),
|
||||
)..addStatusListener((status) {
|
||||
if (status != AnimationStatus.completed || !mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_currentCardIndex = (_currentCardIndex + 1) % _iChingCardCount;
|
||||
});
|
||||
_cardRotationController.forward(from: 0);
|
||||
});
|
||||
_cardRotationController.forward();
|
||||
_startRun();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_cardRotationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _startRun() async {
|
||||
try {
|
||||
final aggregate = await widget.runService.run(
|
||||
@@ -75,6 +100,22 @@ class _DivinationProcessingScreenState
|
||||
_resultData = aggregate.toViewData(widget.params);
|
||||
_step = _ProcessingStep.done;
|
||||
});
|
||||
_cardRotationController.stop();
|
||||
final data = _resultData;
|
||||
if (data != null) {
|
||||
try {
|
||||
await widget.onCompleted(data);
|
||||
} catch (error, stackTrace) {
|
||||
_logger.warning(
|
||||
message: 'Failed to persist post-run side effects',
|
||||
extra: <String, dynamic>{'error': error.toString()},
|
||||
);
|
||||
_logger.debug(
|
||||
message: 'Post-run side effect stack trace',
|
||||
extra: <String, dynamic>{'stackTrace': stackTrace.toString()},
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error, stackTrace) {
|
||||
_logger.error(
|
||||
message: 'Divination processing failed while waiting result events',
|
||||
@@ -117,11 +158,12 @@ class _DivinationProcessingScreenState
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
final text = switch (_step) {
|
||||
final statusText = switch (_step) {
|
||||
_ProcessingStep.preparing => l10n.transitionPreparing,
|
||||
_ProcessingStep.deriving => l10n.transitionDeriving,
|
||||
_ProcessingStep.done => l10n.transitionDone,
|
||||
};
|
||||
final cardDataList = _iChingCardData(l10n);
|
||||
|
||||
final canContinue = _step == _ProcessingStep.done && _resultData != null;
|
||||
|
||||
@@ -134,39 +176,123 @@ class _DivinationProcessingScreenState
|
||||
child: _errorMessage == null
|
||||
? GestureDetector(
|
||||
onTap: canContinue ? _openResult : null,
|
||||
child: Container(
|
||||
width: 220,
|
||||
height: 320,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: Border.all(
|
||||
color: colors.primary.withValues(alpha: 0.2),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colors.shadow.withValues(alpha: 0.25),
|
||||
blurRadius: 22,
|
||||
offset: const Offset(0, 12),
|
||||
child: AnimatedBuilder(
|
||||
animation: _cardRotationController,
|
||||
builder: (context, _) {
|
||||
final angle = canContinue
|
||||
? 0.0
|
||||
: _rotationForProgress(
|
||||
_cardRotationController.value,
|
||||
);
|
||||
final card = cardDataList[_currentCardIndex];
|
||||
return Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.identity()
|
||||
..setEntry(3, 2, 0.0011)
|
||||
..rotateY(angle),
|
||||
child: Container(
|
||||
width: 220,
|
||||
height: 320,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
colors.primaryContainer.withValues(
|
||||
alpha: 0.55,
|
||||
),
|
||||
colors.secondaryContainer.withValues(
|
||||
alpha: 0.38,
|
||||
),
|
||||
colors.surface,
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: Border.all(
|
||||
color: colors.primary.withValues(alpha: 0.3),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colors.shadow.withValues(alpha: 0.18),
|
||||
blurRadius: 26,
|
||||
offset: const Offset(0, 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (canContinue)
|
||||
Icon(
|
||||
Icons.visibility,
|
||||
color: colors.primary,
|
||||
size: 34,
|
||||
)
|
||||
else ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.sm,
|
||||
vertical: AppSpacing.xs,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface.withValues(
|
||||
alpha: 0.75,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppRadius.full,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'I Ching',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall
|
||||
?.copyWith(
|
||||
color: colors.primary,
|
||||
letterSpacing: 0.3,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
card.$1,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall
|
||||
?.copyWith(
|
||||
color: colors.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
card.$2,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(
|
||||
height: 1.5,
|
||||
color: colors.onSurface.withValues(
|
||||
alpha: 0.86,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
Text(
|
||||
statusText,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
canContinue ? Icons.visibility : Icons.auto_awesome,
|
||||
color: colors.primary,
|
||||
size: 34,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
@@ -181,4 +307,27 @@ class _DivinationProcessingScreenState
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double _rotationForProgress(double progress) {
|
||||
if (progress < 0.25) {
|
||||
return (1 - progress / 0.25) * (3.1415926 / 2);
|
||||
}
|
||||
if (progress < 0.75) {
|
||||
return 0;
|
||||
}
|
||||
return ((progress - 0.75) / 0.25) * (3.1415926 / 2);
|
||||
}
|
||||
|
||||
List<(String, String)> _iChingCardData(AppLocalizations l10n) {
|
||||
return <(String, String)>[
|
||||
(l10n.processingCardQianTitle, l10n.processingCardQianQuote),
|
||||
(l10n.processingCardDuiTitle, l10n.processingCardDuiQuote),
|
||||
(l10n.processingCardLiTitle, l10n.processingCardLiQuote),
|
||||
(l10n.processingCardZhenTitle, l10n.processingCardZhenQuote),
|
||||
(l10n.processingCardXunTitle, l10n.processingCardXunQuote),
|
||||
(l10n.processingCardKanTitle, l10n.processingCardKanQuote),
|
||||
(l10n.processingCardGenTitle, l10n.processingCardGenQuote),
|
||||
(l10n.processingCardKunTitle, l10n.processingCardKunQuote),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user