2026-04-03 16:56:47 +08:00
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
|
|
import 'package:intl/intl.dart';
|
|
|
|
|
|
|
|
|
|
|
|
import '../../../../l10n/app_localizations.dart';
|
|
|
|
|
|
import '../../../../shared/theme/app_color_palette.dart';
|
|
|
|
|
|
import '../../../../shared/theme/design_tokens.dart';
|
|
|
|
|
|
import '../../../../shared/widgets/divination/divination_terms.dart';
|
|
|
|
|
|
import '../../../../shared/widgets/divination/yao_glyph.dart';
|
|
|
|
|
|
import '../../../../shared/widgets/divination/yao_legend.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';
|
|
|
|
|
|
|
|
|
|
|
|
class DivinationResultScreen extends StatefulWidget {
|
|
|
|
|
|
const DivinationResultScreen({super.key, required this.data});
|
|
|
|
|
|
|
|
|
|
|
|
final DivinationResultData data;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
State<DivinationResultScreen> createState() => _DivinationResultScreenState();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _DivinationResultScreenState extends State<DivinationResultScreen> {
|
2026-04-03 19:04:46 +08:00
|
|
|
|
bool _showIntro = true;
|
|
|
|
|
|
bool _introCollapsed = false;
|
2026-04-03 16:56:47 +08:00
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
void initState() {
|
|
|
|
|
|
super.initState();
|
2026-04-03 19:04:46 +08:00
|
|
|
|
_playIntro();
|
2026-04-03 16:56:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 19:04:46 +08:00
|
|
|
|
Future<void> _playIntro() async {
|
|
|
|
|
|
await Future<void>.delayed(const Duration(milliseconds: 120));
|
2026-04-03 16:56:47 +08:00
|
|
|
|
if (!mounted) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
setState(() {
|
2026-04-03 19:04:46 +08:00
|
|
|
|
_introCollapsed = true;
|
2026-04-03 16:56:47 +08:00
|
|
|
|
});
|
2026-04-03 19:04:46 +08:00
|
|
|
|
await Future<void>.delayed(const Duration(milliseconds: 760));
|
2026-04-03 16:56:47 +08:00
|
|
|
|
if (!mounted) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
setState(() {
|
2026-04-03 19:04:46 +08:00
|
|
|
|
_showIntro = false;
|
2026-04-03 16:56:47 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
final colors = Theme.of(context).colorScheme;
|
|
|
|
|
|
final palette = Theme.of(context).extension<AppColorPalette>()!;
|
|
|
|
|
|
final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
|
return Scaffold(
|
|
|
|
|
|
backgroundColor: colors.surface,
|
|
|
|
|
|
appBar: AppBar(
|
|
|
|
|
|
backgroundColor: colors.surface,
|
|
|
|
|
|
surfaceTintColor: colors.surface,
|
|
|
|
|
|
title: Text(l10n.resultScreenTitle),
|
|
|
|
|
|
centerTitle: true,
|
|
|
|
|
|
),
|
|
|
|
|
|
body: Stack(
|
|
|
|
|
|
children: [
|
2026-04-03 19:04:46 +08:00
|
|
|
|
AnimatedOpacity(
|
|
|
|
|
|
opacity: _showIntro ? 0 : 1,
|
|
|
|
|
|
duration: const Duration(milliseconds: 260),
|
|
|
|
|
|
child: SingleChildScrollView(
|
|
|
|
|
|
padding: const EdgeInsets.fromLTRB(
|
|
|
|
|
|
AppSpacing.xl,
|
|
|
|
|
|
AppSpacing.lg,
|
|
|
|
|
|
AppSpacing.xl,
|
|
|
|
|
|
AppSpacing.xl,
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
_ResultHeader(data: widget.data),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
_SignCard(signType: widget.data.signType),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
_KeywordCard(keywords: widget.data.keywords),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
_AnalysisCard(
|
|
|
|
|
|
title: l10n.resultConclusion,
|
|
|
|
|
|
content: widget.data.conclusion,
|
2026-04-03 16:56:47 +08:00
|
|
|
|
),
|
2026-04-03 19:04:46 +08:00
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
_AnalysisCard(
|
|
|
|
|
|
title: l10n.resultAnalysis,
|
|
|
|
|
|
content: widget.data.analysis,
|
2026-04-03 16:56:47 +08:00
|
|
|
|
),
|
2026-04-03 19:04:46 +08:00
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
_AnalysisCard(
|
|
|
|
|
|
title: l10n.resultSuggestion,
|
|
|
|
|
|
content: widget.data.suggestion,
|
2026-04-03 16:56:47 +08:00
|
|
|
|
),
|
2026-04-03 19:04:46 +08:00
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
Container(
|
|
|
|
|
|
width: double.infinity,
|
|
|
|
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: palette.warningContainer,
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
2026-04-03 16:56:47 +08:00
|
|
|
|
),
|
2026-04-03 19:04:46 +08:00
|
|
|
|
child: Row(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Icon(Icons.warning, color: palette.warning, size: 20),
|
|
|
|
|
|
const SizedBox(width: AppSpacing.sm),
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
l10n.resultWarning,
|
|
|
|
|
|
style: Theme.of(context).textTheme.bodyMedium
|
|
|
|
|
|
?.copyWith(
|
|
|
|
|
|
color: palette.warning,
|
|
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
|
|
height: 1.35,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
2026-04-03 16:56:47 +08:00
|
|
|
|
),
|
2026-04-03 19:04:46 +08:00
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.xl),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
l10n.resultBasicInfo,
|
|
|
|
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
_InfoCard(data: widget.data),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.xl),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
l10n.resultHexagramDetail,
|
|
|
|
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
_HexagramDetailCard(data: widget.data),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
if (_showIntro)
|
|
|
|
|
|
Positioned.fill(
|
|
|
|
|
|
child: Material(
|
|
|
|
|
|
color: colors.surface,
|
|
|
|
|
|
child: SafeArea(
|
|
|
|
|
|
child: AnimatedAlign(
|
|
|
|
|
|
duration: const Duration(milliseconds: 760),
|
|
|
|
|
|
curve: Curves.easeInOutCubic,
|
|
|
|
|
|
alignment: _introCollapsed
|
|
|
|
|
|
? const Alignment(0, -0.86)
|
|
|
|
|
|
: Alignment.center,
|
|
|
|
|
|
child: AnimatedContainer(
|
|
|
|
|
|
duration: const Duration(milliseconds: 760),
|
|
|
|
|
|
curve: Curves.easeInOutCubic,
|
|
|
|
|
|
width: _introCollapsed ? 150 : 290,
|
|
|
|
|
|
child: _SignCard(signType: widget.data.signType),
|
2026-04-03 16:56:47 +08:00
|
|
|
|
),
|
2026-04-03 19:04:46 +08:00
|
|
|
|
),
|
2026-04-03 16:56:47 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-04-03 19:04:46 +08:00
|
|
|
|
],
|
2026-04-03 16:56:47 +08:00
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _ResultHeader extends StatelessWidget {
|
|
|
|
|
|
const _ResultHeader({required this.data});
|
|
|
|
|
|
|
|
|
|
|
|
final DivinationResultData data;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
final colors = Theme.of(context).colorScheme;
|
|
|
|
|
|
final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
|
return Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Text(
|
|
|
|
|
|
l10n.resultAIAnalysis,
|
|
|
|
|
|
style: Theme.of(
|
|
|
|
|
|
context,
|
|
|
|
|
|
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
|
|
|
|
|
|
),
|
|
|
|
|
|
const Spacer(),
|
|
|
|
|
|
TextButton(
|
|
|
|
|
|
onPressed: () {
|
|
|
|
|
|
final payload = <String, dynamic>{
|
|
|
|
|
|
'signType': data.signType,
|
|
|
|
|
|
'question': data.params.question,
|
|
|
|
|
|
'keywords': data.keywords,
|
|
|
|
|
|
'conclusion': data.conclusion,
|
|
|
|
|
|
};
|
|
|
|
|
|
Clipboard.setData(
|
|
|
|
|
|
ClipboardData(
|
|
|
|
|
|
text: const JsonEncoder.withIndent(' ').convert(payload),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
Toast.show(
|
|
|
|
|
|
context,
|
|
|
|
|
|
l10n.toastContentCopied,
|
|
|
|
|
|
type: ToastType.success,
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
style: TextButton.styleFrom(foregroundColor: colors.primary),
|
|
|
|
|
|
child: Text(l10n.resultShare),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _SignCard extends StatelessWidget {
|
|
|
|
|
|
const _SignCard({required this.signType});
|
|
|
|
|
|
|
|
|
|
|
|
final String signType;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
final colors = Theme.of(context).colorScheme;
|
|
|
|
|
|
final image = switch (signType) {
|
|
|
|
|
|
'上上签' => 'assets/images/qigua/shangshang.jpg',
|
|
|
|
|
|
'中上签' => 'assets/images/qigua/zhongshang.jpg',
|
|
|
|
|
|
_ => 'assets/images/qigua/zhongxia.jpg',
|
|
|
|
|
|
};
|
|
|
|
|
|
return Card(
|
|
|
|
|
|
margin: EdgeInsets.zero,
|
|
|
|
|
|
color: colors.surface,
|
|
|
|
|
|
elevation: 2,
|
|
|
|
|
|
shape: RoundedRectangleBorder(
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Image.asset(
|
|
|
|
|
|
image,
|
|
|
|
|
|
width: double.infinity,
|
|
|
|
|
|
height: 220,
|
|
|
|
|
|
fit: BoxFit.cover,
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.sm),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
signType,
|
|
|
|
|
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
|
|
|
|
color: colors.primary,
|
|
|
|
|
|
fontWeight: FontWeight.w700,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _KeywordCard extends StatelessWidget {
|
|
|
|
|
|
const _KeywordCard({required this.keywords});
|
|
|
|
|
|
|
|
|
|
|
|
final String keywords;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
final palette = Theme.of(context).extension<AppColorPalette>()!;
|
|
|
|
|
|
return Card(
|
|
|
|
|
|
margin: EdgeInsets.zero,
|
|
|
|
|
|
color: palette.warningContainer,
|
|
|
|
|
|
elevation: 0,
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
padding: const EdgeInsets.all(AppSpacing.lg),
|
|
|
|
|
|
child: Center(
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
keywords,
|
|
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
|
|
|
|
color: Theme.of(context).colorScheme.primary,
|
|
|
|
|
|
fontWeight: FontWeight.w700,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _AnalysisCard extends StatelessWidget {
|
|
|
|
|
|
const _AnalysisCard({required this.title, required this.content});
|
|
|
|
|
|
|
|
|
|
|
|
final String title;
|
|
|
|
|
|
final String content;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
final colors = Theme.of(context).colorScheme;
|
|
|
|
|
|
return Card(
|
|
|
|
|
|
margin: EdgeInsets.zero,
|
|
|
|
|
|
color: colors.surface,
|
|
|
|
|
|
elevation: 2,
|
|
|
|
|
|
shape: RoundedRectangleBorder(
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
padding: const EdgeInsets.all(AppSpacing.lg),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Text(
|
|
|
|
|
|
title,
|
|
|
|
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
|
|
|
|
color: colors.primary,
|
|
|
|
|
|
fontWeight: FontWeight.w700,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
const Spacer(),
|
|
|
|
|
|
TextButton(
|
|
|
|
|
|
onPressed: () {
|
|
|
|
|
|
Clipboard.setData(ClipboardData(text: content));
|
|
|
|
|
|
Toast.show(context, '$title已复制', type: ToastType.success);
|
|
|
|
|
|
},
|
|
|
|
|
|
child: const Text('复制'),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.sm),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
content,
|
|
|
|
|
|
style: Theme.of(
|
|
|
|
|
|
context,
|
|
|
|
|
|
).textTheme.bodyMedium?.copyWith(height: 1.65),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _InfoCard extends StatelessWidget {
|
|
|
|
|
|
const _InfoCard({required this.data});
|
|
|
|
|
|
|
|
|
|
|
|
final DivinationResultData data;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
final colors = Theme.of(context).colorScheme;
|
|
|
|
|
|
return Card(
|
|
|
|
|
|
margin: EdgeInsets.zero,
|
|
|
|
|
|
color: colors.surface,
|
|
|
|
|
|
elevation: 2,
|
|
|
|
|
|
shape: RoundedRectangleBorder(
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
padding: const EdgeInsets.all(AppSpacing.lg),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Text(
|
|
|
|
|
|
'起卦信息',
|
|
|
|
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
|
|
|
|
color: colors.primary,
|
|
|
|
|
|
fontWeight: FontWeight.w700,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
_kv(
|
|
|
|
|
|
context,
|
|
|
|
|
|
'起卦时间',
|
2026-04-03 18:19:52 +08:00
|
|
|
|
DateFormat.yMd(
|
|
|
|
|
|
Localizations.localeOf(context).toString(),
|
|
|
|
|
|
).add_Hm().format(data.params.divinationTime),
|
2026-04-03 16:56:47 +08:00
|
|
|
|
),
|
|
|
|
|
|
_kv(
|
|
|
|
|
|
context,
|
|
|
|
|
|
'起卦方式',
|
|
|
|
|
|
data.params.method == DivinationMethod.auto ? '自动起卦' : '手动起卦',
|
|
|
|
|
|
),
|
|
|
|
|
|
_kv(context, '问题类型', _typeLabel(data.params.questionType)),
|
|
|
|
|
|
_kv(context, '占卜问题', data.params.question),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _kv(BuildContext context, String k, String v) {
|
|
|
|
|
|
return Padding(
|
|
|
|
|
|
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
|
|
|
|
|
|
child: RichText(
|
|
|
|
|
|
text: TextSpan(
|
|
|
|
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
TextSpan(
|
|
|
|
|
|
text: '$k:',
|
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
|
color: Theme.of(
|
|
|
|
|
|
context,
|
|
|
|
|
|
).colorScheme.onSurface.withValues(alpha: 0.75),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
TextSpan(
|
|
|
|
|
|
text: v,
|
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
|
color: Theme.of(context).colorScheme.onSurface,
|
|
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
String _typeLabel(QuestionType type) {
|
|
|
|
|
|
return switch (type) {
|
|
|
|
|
|
QuestionType.career => '事业',
|
|
|
|
|
|
QuestionType.love => '情感',
|
|
|
|
|
|
QuestionType.wealth => '财富',
|
|
|
|
|
|
QuestionType.fortune => '运势',
|
|
|
|
|
|
QuestionType.dream => '解梦',
|
|
|
|
|
|
QuestionType.health => '健康',
|
|
|
|
|
|
QuestionType.study => '学业',
|
|
|
|
|
|
QuestionType.search => '寻物',
|
|
|
|
|
|
QuestionType.other => '其他',
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _HexagramDetailCard extends StatelessWidget {
|
|
|
|
|
|
const _HexagramDetailCard({required this.data});
|
|
|
|
|
|
|
|
|
|
|
|
final DivinationResultData data;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
final colors = Theme.of(context).colorScheme;
|
|
|
|
|
|
return Column(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Card(
|
|
|
|
|
|
margin: EdgeInsets.zero,
|
|
|
|
|
|
color: colors.surface,
|
|
|
|
|
|
elevation: 2,
|
|
|
|
|
|
shape: RoundedRectangleBorder(
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
padding: const EdgeInsets.all(AppSpacing.lg),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Text(
|
|
|
|
|
|
'干支信息',
|
|
|
|
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
|
|
|
|
color: colors.primary,
|
|
|
|
|
|
fontWeight: FontWeight.w700,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: _miniKV(context, '月建', data.ganzhi.yueJian),
|
|
|
|
|
|
),
|
|
|
|
|
|
Expanded(child: _miniKV(context, '日辰', data.ganzhi.riChen)),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.sm),
|
|
|
|
|
|
Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Expanded(child: _miniKV(context, '月破', data.ganzhi.yuePo)),
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: _miniKV(context, '日冲', data.ganzhi.riChong),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
Text('五行旺衰', style: Theme.of(context).textTheme.bodyMedium),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.sm),
|
|
|
|
|
|
_WuXingTable(data: data),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
Text('干支空亡', style: Theme.of(context).textTheme.bodyMedium),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.sm),
|
|
|
|
|
|
_KongWangTable(data: data),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
Card(
|
|
|
|
|
|
margin: EdgeInsets.zero,
|
|
|
|
|
|
color: colors.surfaceContainerLow,
|
|
|
|
|
|
elevation: 2,
|
|
|
|
|
|
shape: RoundedRectangleBorder(
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
padding: const EdgeInsets.all(AppSpacing.lg),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
data.guaName,
|
|
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
|
|
style: Theme.of(context).textTheme.titleMedium
|
|
|
|
|
|
?.copyWith(fontWeight: FontWeight.w700),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
if (data.hasChangingYao)
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
data.targetGuaName,
|
|
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
|
|
style: Theme.of(context).textTheme.titleMedium
|
|
|
|
|
|
?.copyWith(fontWeight: FontWeight.w700),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.md),
|
|
|
|
|
|
for (int idx = 5; idx >= 0; idx--)
|
|
|
|
|
|
_YaoDetailRow(
|
|
|
|
|
|
line: data.yaoLines[idx],
|
|
|
|
|
|
target: data.targetYaoLines[idx],
|
|
|
|
|
|
showTarget: data.hasChangingYao,
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: AppSpacing.sm),
|
|
|
|
|
|
const Align(
|
|
|
|
|
|
alignment: Alignment.centerLeft,
|
|
|
|
|
|
child: YaoLegend(),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _miniKV(BuildContext context, String key, String value) {
|
|
|
|
|
|
return Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Text('$key:'),
|
|
|
|
|
|
Text(value, style: const TextStyle(fontWeight: FontWeight.w600)),
|
|
|
|
|
|
],
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _WuXingTable extends StatelessWidget {
|
|
|
|
|
|
const _WuXingTable({required this.data});
|
|
|
|
|
|
|
|
|
|
|
|
final DivinationResultData data;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
final colors = Theme.of(context).colorScheme;
|
|
|
|
|
|
return Container(
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
border: Border.all(color: colors.outline),
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.sm),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Row(
|
|
|
|
|
|
children: DivinationTerms.wuXing
|
|
|
|
|
|
.map(
|
|
|
|
|
|
(k) => Expanded(
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
|
vertical: AppSpacing.sm,
|
|
|
|
|
|
),
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: colors.surfaceContainerHigh,
|
|
|
|
|
|
border: Border(
|
|
|
|
|
|
right: BorderSide(
|
|
|
|
|
|
color: k == DivinationTerms.wuXing.last
|
|
|
|
|
|
? colors.surfaceContainerHigh
|
|
|
|
|
|
: colors.outline,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Text(k, textAlign: TextAlign.center),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
.toList(),
|
|
|
|
|
|
),
|
|
|
|
|
|
Row(
|
|
|
|
|
|
children: DivinationTerms.wuXing
|
|
|
|
|
|
.map(
|
|
|
|
|
|
(k) => Expanded(
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
|
vertical: AppSpacing.sm,
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
data.wuXingStatus[k] ?? '',
|
|
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
.toList(),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _KongWangTable extends StatelessWidget {
|
|
|
|
|
|
const _KongWangTable({required this.data});
|
|
|
|
|
|
|
|
|
|
|
|
final DivinationResultData data;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
final colors = Theme.of(context).colorScheme;
|
|
|
|
|
|
final rows = [
|
|
|
|
|
|
('年', '${data.ganzhi.yearGanZhi}年', data.ganzhi.yearKongWang),
|
|
|
|
|
|
('月', '${data.ganzhi.monthGanZhi}月', data.ganzhi.monthKongWang),
|
|
|
|
|
|
('日', '${data.ganzhi.dayGanZhi}日', data.ganzhi.dayKongWang),
|
|
|
|
|
|
('时', '${data.ganzhi.timeGanZhi}时', data.ganzhi.timeKongWang),
|
|
|
|
|
|
];
|
|
|
|
|
|
return Container(
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
border: Border.all(color: colors.outline),
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.sm),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
for (final row in rows)
|
|
|
|
|
|
Padding(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
|
horizontal: AppSpacing.md,
|
|
|
|
|
|
vertical: AppSpacing.sm,
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
SizedBox(width: 28, child: Text(row.$1)),
|
|
|
|
|
|
Expanded(child: Text(row.$2, textAlign: TextAlign.center)),
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
|
width: 64,
|
|
|
|
|
|
child: Text(row.$3, textAlign: TextAlign.right),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _YaoDetailRow extends StatelessWidget {
|
|
|
|
|
|
const _YaoDetailRow({
|
|
|
|
|
|
required this.line,
|
|
|
|
|
|
required this.target,
|
|
|
|
|
|
required this.showTarget,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final YaoLineData line;
|
|
|
|
|
|
final YaoLineData target;
|
|
|
|
|
|
final bool showTarget;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
return Padding(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: AppSpacing.sm),
|
|
|
|
|
|
child: Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Expanded(child: _lineCell(context, line, showMark: true)),
|
|
|
|
|
|
if (showTarget)
|
|
|
|
|
|
Expanded(child: _lineCell(context, target, showMark: false)),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _lineCell(
|
|
|
|
|
|
BuildContext context,
|
|
|
|
|
|
YaoLineData data, {
|
|
|
|
|
|
required bool showMark,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
|
width: 20,
|
|
|
|
|
|
child: Text(data.spirit, textAlign: TextAlign.center),
|
|
|
|
|
|
),
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
|
width: 28,
|
|
|
|
|
|
child: Text(data.relation, textAlign: TextAlign.center),
|
|
|
|
|
|
),
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
|
width: 18,
|
|
|
|
|
|
child: Text(data.branch, textAlign: TextAlign.center),
|
|
|
|
|
|
),
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
|
width: 18,
|
|
|
|
|
|
child: Text(data.element, textAlign: TextAlign.center),
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(width: AppSpacing.xs),
|
|
|
|
|
|
Expanded(child: YaoGlyph(type: data.type, height: 6)),
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
|
width: 18,
|
|
|
|
|
|
child: Text(_changeMark(data.type), textAlign: TextAlign.center),
|
|
|
|
|
|
),
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
|
width: 18,
|
|
|
|
|
|
child: Text(showMark ? data.mark : '', textAlign: TextAlign.center),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
String _changeMark(YaoType type) {
|
|
|
|
|
|
return type.changeMark;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|