feat: 接入起卦后端流程并完善积分扣减链路
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/logging/logger.dart';
|
||||
import '../../../../core/network/api_problem.dart';
|
||||
import '../../../../core/network/api_problem_mapper.dart';
|
||||
import '../../../../l10n/app_localizations.dart';
|
||||
import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/toast/toast.dart';
|
||||
import '../../../../shared/widgets/toast/toast_type.dart';
|
||||
import '../../data/models/divination_params.dart';
|
||||
import '../../data/models/divination_result.dart';
|
||||
import '../../data/services/divination_run_service.dart';
|
||||
import 'divination_result_screen.dart';
|
||||
|
||||
enum _ProcessingStep { preparing, deriving, done }
|
||||
|
||||
class DivinationProcessingScreen extends StatefulWidget {
|
||||
const DivinationProcessingScreen({
|
||||
super.key,
|
||||
required this.params,
|
||||
required this.yaoStates,
|
||||
required this.runService,
|
||||
});
|
||||
|
||||
final DivinationParams params;
|
||||
final List<YaoType> yaoStates;
|
||||
final DivinationRunService runService;
|
||||
|
||||
@override
|
||||
State<DivinationProcessingScreen> createState() =>
|
||||
_DivinationProcessingScreenState();
|
||||
}
|
||||
|
||||
class _DivinationProcessingScreenState
|
||||
extends State<DivinationProcessingScreen> {
|
||||
static final Logger _logger = getLogger(
|
||||
'features.divination.processing_screen',
|
||||
);
|
||||
_ProcessingStep _step = _ProcessingStep.preparing;
|
||||
DivinationResultData? _resultData;
|
||||
String? _errorMessage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_startRun();
|
||||
}
|
||||
|
||||
Future<void> _startRun() async {
|
||||
try {
|
||||
final aggregate = await widget.runService.run(
|
||||
params: widget.params,
|
||||
yaoStates: widget.yaoStates,
|
||||
onDerived: () {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_step = _ProcessingStep.deriving;
|
||||
});
|
||||
},
|
||||
onTextMessageEnd: () {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_step = _ProcessingStep.done;
|
||||
});
|
||||
},
|
||||
);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_resultData = aggregate.toViewData(widget.params);
|
||||
_step = _ProcessingStep.done;
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
_logger.error(
|
||||
message: 'Divination processing failed while waiting result events',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
extra: <String, dynamic>{
|
||||
'step': _step.name,
|
||||
'method': widget.params.method.name,
|
||||
'questionType': widget.params.questionType.name,
|
||||
},
|
||||
);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final message = error is ApiProblem
|
||||
? mapApiProblemToMessage(error, l10n)
|
||||
: l10n.errorRequestGeneric;
|
||||
setState(() {
|
||||
_errorMessage = message;
|
||||
});
|
||||
Toast.show(context, message, type: ToastType.error);
|
||||
}
|
||||
}
|
||||
|
||||
void _openResult() {
|
||||
final data = _resultData;
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => DivinationResultScreen(data: data),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
final text = switch (_step) {
|
||||
_ProcessingStep.preparing => l10n.transitionPreparing,
|
||||
_ProcessingStep.deriving => l10n.transitionDeriving,
|
||||
_ProcessingStep.done => l10n.transitionDone,
|
||||
};
|
||||
|
||||
final canContinue = _step == _ProcessingStep.done && _resultData != null;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surface,
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppSpacing.xl),
|
||||
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: 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(
|
||||
_errorMessage!,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium?.copyWith(color: colors.error),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user