feat: 实现用户画像、占卜历史与后端用户管理模块
This commit is contained in:
@@ -27,22 +27,73 @@ class DivinationResultScreen extends StatefulWidget {
|
||||
class _DivinationResultScreenState extends State<DivinationResultScreen> {
|
||||
bool _showIntro = true;
|
||||
bool _introCollapsed = false;
|
||||
Rect? _introTargetRect;
|
||||
final GlobalKey _stackKey = GlobalKey();
|
||||
final GlobalKey _finalSignCardKey = GlobalKey();
|
||||
|
||||
void _backToHome() {
|
||||
final navigator = Navigator.of(context);
|
||||
navigator.popUntil((route) => route.isFirst);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_prepareIntro();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _prepareIntro() async {
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
if (_measureIntroTargetRect()) {
|
||||
break;
|
||||
}
|
||||
await Future<void>.delayed(const Duration(milliseconds: 16));
|
||||
}
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
_playIntro();
|
||||
}
|
||||
|
||||
bool _measureIntroTargetRect() {
|
||||
final stackContext = _stackKey.currentContext;
|
||||
final targetContext = _finalSignCardKey.currentContext;
|
||||
if (stackContext == null || targetContext == null) {
|
||||
return false;
|
||||
}
|
||||
final stackRender = stackContext.findRenderObject();
|
||||
final targetRender = targetContext.findRenderObject();
|
||||
if (stackRender is! RenderBox || targetRender is! RenderBox) {
|
||||
return false;
|
||||
}
|
||||
final offset = targetRender.localToGlobal(
|
||||
Offset.zero,
|
||||
ancestor: stackRender,
|
||||
);
|
||||
final targetRect = offset & targetRender.size;
|
||||
if (_introTargetRect == targetRect) {
|
||||
return true;
|
||||
}
|
||||
setState(() {
|
||||
_introTargetRect = targetRect;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _playIntro() async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 120));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 180));
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_introCollapsed = true;
|
||||
});
|
||||
await Future<void>.delayed(const Duration(milliseconds: 760));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 1450));
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
@@ -51,121 +102,179 @@ class _DivinationResultScreenState extends State<DivinationResultScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
Rect _introStartRect(Size size) {
|
||||
const startWidth = 332.0;
|
||||
const startHeight = 234.0;
|
||||
return Rect.fromLTWH(
|
||||
(size.width - startWidth) / 2,
|
||||
(size.height - startHeight) / 2,
|
||||
startWidth,
|
||||
startHeight,
|
||||
);
|
||||
}
|
||||
|
||||
@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(
|
||||
return PopScope<void>(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (didPop) {
|
||||
return;
|
||||
}
|
||||
_backToHome();
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: colors.surface,
|
||||
surfaceTintColor: colors.surface,
|
||||
title: Text(l10n.resultScreenTitle),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_AnalysisCard(
|
||||
title: l10n.resultAnalysis,
|
||||
content: widget.data.analysis,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_AnalysisCard(
|
||||
title: l10n.resultSuggestion,
|
||||
content: widget.data.suggestion,
|
||||
),
|
||||
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),
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
onPressed: _backToHome,
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||
),
|
||||
backgroundColor: colors.surface,
|
||||
surfaceTintColor: colors.surface,
|
||||
title: Text(l10n.resultScreenTitle),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final stackSize = Size(constraints.maxWidth, constraints.maxHeight);
|
||||
final startRect = _introStartRect(stackSize);
|
||||
final targetRect = _introTargetRect ?? startRect;
|
||||
final currentRect = _introCollapsed ? targetRect : startRect;
|
||||
|
||||
return Stack(
|
||||
key: _stackKey,
|
||||
children: [
|
||||
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: Row(
|
||||
child: Column(
|
||||
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,
|
||||
_ResultHeader(data: widget.data),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_SignCard(
|
||||
key: _finalSignCardKey,
|
||||
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,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_AnalysisCard(
|
||||
title: l10n.resultAnalysis,
|
||||
content: widget.data.analysis,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_AnalysisCard(
|
||||
title: l10n.resultSuggestion,
|
||||
content: widget.data.suggestion,
|
||||
),
|
||||
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),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
if (_showIntro)
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: ColoredBox(color: colors.surface),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (_showIntro)
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 1450),
|
||||
curve: Curves.easeInOutCubicEmphasized,
|
||||
left: currentRect.left,
|
||||
top: currentRect.top,
|
||||
width: currentRect.width,
|
||||
height: currentRect.height,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
_signImageAssetForType(
|
||||
context,
|
||||
widget.data.signType,
|
||||
),
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colors.shadow.withValues(alpha: 0.24),
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -217,18 +326,16 @@ class _ResultHeader extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _SignCard extends StatelessWidget {
|
||||
const _SignCard({required this.signType});
|
||||
const _SignCard({super.key, 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',
|
||||
};
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final image = _signImageAssetForType(context, signType);
|
||||
final localizedSignType = _localizedSignTypeLabel(l10n, signType);
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
color: colors.surface,
|
||||
@@ -248,7 +355,7 @@ class _SignCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
Text(
|
||||
signType,
|
||||
localizedSignType,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: colors.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -261,6 +368,35 @@ class _SignCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
String _localizedSignTypeLabel(AppLocalizations l10n, String signType) {
|
||||
final normalized = signType.trim();
|
||||
if (normalized.contains('上上')) {
|
||||
return l10n.signTypeShangShang;
|
||||
}
|
||||
if (normalized.contains('中上')) {
|
||||
return l10n.signTypeZhongShang;
|
||||
}
|
||||
if (normalized.contains('下下')) {
|
||||
return l10n.signTypeXiaXia;
|
||||
}
|
||||
return l10n.signTypeZhongXia;
|
||||
}
|
||||
|
||||
String _signImageAssetForType(BuildContext context, String signType) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final normalized = _localizedSignTypeLabel(l10n, signType);
|
||||
if (normalized == l10n.signTypeShangShang) {
|
||||
return 'assets/images/qigua/shangshang.jpg';
|
||||
}
|
||||
if (normalized == l10n.signTypeZhongShang) {
|
||||
return 'assets/images/qigua/zhongshang.jpg';
|
||||
}
|
||||
if (normalized == l10n.signTypeXiaXia) {
|
||||
return 'assets/images/qigua/xiaxia.jpg';
|
||||
}
|
||||
return 'assets/images/qigua/zhongxia.jpg';
|
||||
}
|
||||
|
||||
class _KeywordCard extends StatelessWidget {
|
||||
const _KeywordCard({required this.keywords});
|
||||
|
||||
@@ -299,6 +435,7 @@ class _AnalysisCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
color: colors.surface,
|
||||
@@ -323,9 +460,13 @@ class _AnalysisCard extends StatelessWidget {
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: content));
|
||||
Toast.show(context, '$title已复制', type: ToastType.success);
|
||||
Toast.show(
|
||||
context,
|
||||
l10n.toastContentCopiedWithTitle(title),
|
||||
type: ToastType.success,
|
||||
);
|
||||
},
|
||||
child: const Text('复制'),
|
||||
child: Text(l10n.resultCopy),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -351,6 +492,7 @@ class _InfoCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
color: colors.surface,
|
||||
@@ -360,32 +502,41 @@ class _InfoCard extends StatelessWidget {
|
||||
),
|
||||
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,
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.resultDivinationInfo,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: colors.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_kv(
|
||||
context,
|
||||
'起卦时间',
|
||||
DateFormat.yMd(
|
||||
Localizations.localeOf(context).toString(),
|
||||
).add_Hm().format(data.params.divinationTime),
|
||||
),
|
||||
_kv(
|
||||
context,
|
||||
'起卦方式',
|
||||
data.params.method == DivinationMethod.auto ? '自动起卦' : '手动起卦',
|
||||
),
|
||||
_kv(context, '问题类型', _typeLabel(data.params.questionType)),
|
||||
_kv(context, '占卜问题', data.params.question),
|
||||
],
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_kv(
|
||||
context,
|
||||
l10n.resultDivinationTime,
|
||||
DateFormat.yMd(
|
||||
Localizations.localeOf(context).toString(),
|
||||
).add_Hm().format(data.params.divinationTime),
|
||||
),
|
||||
_kv(
|
||||
context,
|
||||
l10n.resultDivinationMethod,
|
||||
data.params.method == DivinationMethod.auto
|
||||
? l10n.resultAutoMethod
|
||||
: l10n.resultManualMethod,
|
||||
),
|
||||
_kv(
|
||||
context,
|
||||
l10n.resultQuestionType,
|
||||
_typeLabel(context, data.params.questionType),
|
||||
),
|
||||
_kv(context, l10n.resultQuestion, data.params.question),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -419,17 +570,18 @@ class _InfoCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
String _typeLabel(QuestionType type) {
|
||||
String _typeLabel(BuildContext context, QuestionType type) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return switch (type) {
|
||||
QuestionType.career => '事业',
|
||||
QuestionType.love => '情感',
|
||||
QuestionType.wealth => '财富',
|
||||
QuestionType.fortune => '运势',
|
||||
QuestionType.dream => '解梦',
|
||||
QuestionType.health => '健康',
|
||||
QuestionType.study => '学业',
|
||||
QuestionType.search => '寻物',
|
||||
QuestionType.other => '其他',
|
||||
QuestionType.career => l10n.questionTypeCareer,
|
||||
QuestionType.love => l10n.questionTypeLove,
|
||||
QuestionType.wealth => l10n.questionTypeWealth,
|
||||
QuestionType.fortune => l10n.questionTypeFortune,
|
||||
QuestionType.dream => l10n.questionTypeDream,
|
||||
QuestionType.health => l10n.questionTypeHealth,
|
||||
QuestionType.study => l10n.questionTypeStudy,
|
||||
QuestionType.search => l10n.questionTypeSearch,
|
||||
QuestionType.other => l10n.questionTypeOther,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -442,6 +594,7 @@ class _HexagramDetailCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return Column(
|
||||
children: [
|
||||
Card(
|
||||
@@ -457,7 +610,7 @@ class _HexagramDetailCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'干支信息',
|
||||
l10n.ganZhiInfo,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: colors.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -467,26 +620,58 @@ class _HexagramDetailCard extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _miniKV(context, '月建', data.ganzhi.yueJian),
|
||||
child: _miniKV(
|
||||
context,
|
||||
DivinationTerms.yueJian,
|
||||
data.ganzhi.yueJian,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _miniKV(
|
||||
context,
|
||||
DivinationTerms.riChen,
|
||||
data.ganzhi.riChen,
|
||||
),
|
||||
),
|
||||
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),
|
||||
child: _miniKV(
|
||||
context,
|
||||
DivinationTerms.yuePo,
|
||||
data.ganzhi.yuePo,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _miniKV(
|
||||
context,
|
||||
DivinationTerms.riChong,
|
||||
data.ganzhi.riChong,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text('五行旺衰', style: Theme.of(context).textTheme.bodyMedium),
|
||||
Text(
|
||||
l10n.wuXingWangShuai,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: colors.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
_WuXingTable(data: data),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text('干支空亡', style: Theme.of(context).textTheme.bodyMedium),
|
||||
Text(
|
||||
l10n.ganZhiKongWang,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: colors.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
_KongWangTable(data: data),
|
||||
],
|
||||
@@ -626,12 +811,65 @@ class _KongWangTable extends StatelessWidget {
|
||||
@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),
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final header = <String>[
|
||||
l10n.resultPillarColumn,
|
||||
l10n.resultYearPillar,
|
||||
l10n.resultMonthPillar,
|
||||
l10n.resultDayPillar,
|
||||
l10n.resultTimePillar,
|
||||
];
|
||||
final rows = <List<String>>[
|
||||
<String>[
|
||||
l10n.resultGanZhiLabel,
|
||||
data.ganzhi.yearGanZhi,
|
||||
data.ganzhi.monthGanZhi,
|
||||
data.ganzhi.dayGanZhi,
|
||||
data.ganzhi.timeGanZhi,
|
||||
],
|
||||
<String>[
|
||||
l10n.resultKongWangLabel,
|
||||
data.ganzhi.yearKongWang,
|
||||
data.ganzhi.monthKongWang,
|
||||
data.ganzhi.dayKongWang,
|
||||
data.ganzhi.timeKongWang,
|
||||
],
|
||||
];
|
||||
|
||||
Widget buildCell(
|
||||
String text, {
|
||||
bool isHeader = false,
|
||||
bool isLast = false,
|
||||
bool isFirst = false,
|
||||
}) {
|
||||
return Expanded(
|
||||
flex: isFirst ? 2 : 3,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.xs,
|
||||
vertical: AppSpacing.sm,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isHeader ? colors.surfaceContainerHigh : colors.surface,
|
||||
border: Border(
|
||||
right: isLast
|
||||
? BorderSide.none
|
||||
: BorderSide(color: colors.outline),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: isHeader || isFirst
|
||||
? FontWeight.w700
|
||||
: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: colors.outline),
|
||||
@@ -639,20 +877,30 @@ class _KongWangTable extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
for (final row in rows)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.sm,
|
||||
Row(
|
||||
children: [
|
||||
for (int i = 0; i < header.length; i++)
|
||||
buildCell(
|
||||
header[i],
|
||||
isHeader: true,
|
||||
isFirst: i == 0,
|
||||
isLast: i == header.length - 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
for (int r = 0; r < rows.length; r++)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(top: BorderSide(color: colors.outline)),
|
||||
),
|
||||
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),
|
||||
),
|
||||
for (int c = 0; c < rows[r].length; c++)
|
||||
buildCell(
|
||||
rows[r][c],
|
||||
isFirst: c == 0,
|
||||
isLast: c == rows[r].length - 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user