Files
eryao/apps/lib/features/divination/presentation/screens/divination_processing_screen.dart
T

185 lines
5.6 KiB
Dart

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),
),
),
),
),
);
}
}