feat(divination): 重构手动起卦教程,支持三硬币交互选择

This commit is contained in:
qzl
2026-04-07 18:41:08 +08:00
parent f904286ba7
commit f394df9362
29 changed files with 873 additions and 326 deletions
@@ -77,7 +77,6 @@ class DivinationApi {
final aggregate = DivinationRunAggregate(
derived: derived,
signLevel: _asString(agentOutputRaw['sign_level']),
summary: _asString(agentOutputRaw['summary']),
conclusion: _asStringList(agentOutputRaw['conclusion']),
focusPoints: _asStringList(agentOutputRaw['focus_points']),
advice: _asStringList(agentOutputRaw['advice']),
@@ -54,7 +54,6 @@ class DivinationRunAggregate {
const DivinationRunAggregate({
required this.derived,
required this.signLevel,
required this.summary,
required this.conclusion,
required this.focusPoints,
required this.advice,
@@ -64,7 +63,6 @@ class DivinationRunAggregate {
final DerivedDivinationData derived;
final String signLevel;
final String summary;
final List<String> conclusion;
final List<String> focusPoints;
final List<String> advice;
@@ -82,8 +80,9 @@ class DivinationRunAggregate {
lowerName: derived.lowerName,
signType: signLevel,
keywords: keywords.join(''),
focusPoints: focusPoints,
conclusion: _asBullet(conclusion),
analysis: summary.isEmpty ? answer : '$summary\n\n$answer',
analysis: answer,
suggestion: _asBullet(advice),
ganzhi: GanzhiData(
yearGanZhi: derived.ganzhi.yearGanZhi,
@@ -22,6 +22,7 @@ class DivinationParams {
required this.divinationTime,
required this.coinBalance,
required this.userId,
this.allowVibration = true,
});
final DivinationMethod method;
@@ -30,6 +31,7 @@ class DivinationParams {
final DateTime divinationTime;
final int coinBalance;
final String userId;
final bool allowVibration;
DivinationParams copyWith({
DivinationMethod? method,
@@ -38,6 +40,7 @@ class DivinationParams {
DateTime? divinationTime,
int? coinBalance,
String? userId,
bool? allowVibration,
}) {
return DivinationParams(
method: method ?? this.method,
@@ -46,6 +49,7 @@ class DivinationParams {
divinationTime: divinationTime ?? this.divinationTime,
coinBalance: coinBalance ?? this.coinBalance,
userId: userId ?? this.userId,
allowVibration: allowVibration ?? this.allowVibration,
);
}
@@ -11,6 +11,7 @@ class DivinationResultData {
required this.lowerName,
required this.signType,
required this.keywords,
required this.focusPoints,
required this.conclusion,
required this.analysis,
required this.suggestion,
@@ -29,6 +30,7 @@ class DivinationResultData {
final String lowerName;
final String signType;
final String keywords;
final List<String> focusPoints;
final String conclusion;
final String analysis;
final String suggestion;
@@ -50,6 +52,7 @@ class DivinationResultData {
'lowerName': lowerName,
'signType': signType,
'keywords': keywords,
'focusPoints': focusPoints,
'conclusion': conclusion,
'analysis': analysis,
'suggestion': suggestion,
@@ -86,6 +89,7 @@ class DivinationResultData {
lowerName: _requiredString(json, 'lowerName'),
signType: _requiredString(json, 'signType'),
keywords: _requiredString(json, 'keywords'),
focusPoints: _requiredStringList(json, 'focusPoints'),
conclusion: _requiredString(json, 'conclusion'),
analysis: _requiredString(json, 'analysis'),
suggestion: _requiredString(json, 'suggestion'),
@@ -113,6 +117,21 @@ class DivinationResultData {
}
}
List<String> _requiredStringList(Map<String, dynamic> json, String key) {
final raw = json[key];
if (raw is! List<dynamic>) {
throw FormatException('Invalid $key payload');
}
return raw
.map((item) {
if (item is! String) {
throw FormatException('Invalid $key item payload');
}
return item;
})
.toList(growable: false);
}
class GanzhiData {
const GanzhiData({
required this.yearGanZhi,
@@ -33,7 +33,6 @@ class DivinationRunService {
DerivedDivinationData? derived;
String signLevel = '';
String summary = '';
List<String> conclusion = const <String>[];
List<String> focusPoints = const <String>[];
List<String> advice = const <String>[];
@@ -64,7 +63,6 @@ class DivinationRunService {
}
if (type == 'TEXT_MESSAGE_END') {
signLevel = _requiredString(event, 'sign_level');
summary = _requiredString(event, 'summary');
conclusion = _requiredStringList(event, 'conclusion');
focusPoints = _requiredStringList(event, 'focus_points');
advice = _requiredStringList(event, 'advice');
@@ -107,7 +105,6 @@ class DivinationRunService {
return DivinationRunAggregate(
derived: derived,
signLevel: signLevel,
summary: summary,
conclusion: conclusion,
focusPoints: focusPoints,
advice: advice,
@@ -300,6 +300,9 @@ class _AutoDivinationScreenState extends State<AutoDivinationScreen>
}
Future<void> _vibrateStrong() async {
if (!widget.params.allowVibration) {
return;
}
final hasVibrator = await Vibration.hasVibrator();
if (hasVibrator == true) {
await Vibration.vibrate(duration: 280, amplitude: 255);
@@ -313,15 +316,17 @@ class _AutoDivinationScreenState extends State<AutoDivinationScreen>
context: context,
builder: (context) {
return DivinationGuideDialog(
title: l10n.autoGuideTitle,
title: l10n.divinationManualGuideTitle,
guideImages: const [
'assets/images/qigua/lc1.jpg',
'assets/images/qigua/lc2.jpg',
'assets/images/qigua/lc3.jpg',
'assets/images/qigua/lc4.jpg',
'assets/images/qigua/lc5.jpg',
['assets/images/tutorial/tutorial_1.png'],
['assets/images/tutorial/tutorial_2.png'],
['assets/images/tutorial/tutorial_3.png'],
],
instructions: [
l10n.divinationManualGuideStep1,
l10n.divinationManualGuideStep2,
l10n.divinationManualGuideStep3,
],
instructionText: l10n.autoGuideInstruction,
);
},
);
@@ -527,7 +532,7 @@ class _CoinColumn extends StatelessWidget {
),
const SizedBox(height: AppSpacing.sm),
Text(
DivinationTerms.yinYang[isYang] ?? '',
DivinationTerms.ziHua[isYang] ?? '',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colors.onSurface,
fontWeight: FontWeight.w700,
@@ -562,8 +567,8 @@ class _CoinFace extends StatelessWidget {
: (isYang ? 0 : 180);
final showingYang = isSpinning ? rotationY < 90 : isYang;
final image = showingYang
? 'assets/images/qigua/yangmian.jpg'
: 'assets/images/qigua/yinmian.jpg';
? 'assets/images/qigua/hua.jpg'
: 'assets/images/qigua/zi.jpg';
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
@@ -244,7 +244,7 @@ class _DivinationProcessingScreenState extends State<DivinationProcessingScreen>
),
),
child: Text(
'I Ching',
l10n.iChingTitle,
style: Theme.of(context)
.textTheme
.labelSmall
@@ -175,6 +175,8 @@ class _DivinationResultScreenState extends State<DivinationResultScreen> {
content: widget.data.conclusion,
),
const SizedBox(height: AppSpacing.md),
_FocusPointsCard(points: widget.data.focusPoints),
const SizedBox(height: AppSpacing.md),
_AnalysisCard(
title: l10n.resultAnalysis,
content: widget.data.analysis,
@@ -426,6 +428,71 @@ class _KeywordCard extends StatelessWidget {
}
}
class _FocusPointsCard extends StatelessWidget {
const _FocusPointsCard({required this.points});
final List<String> points;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final languageCode = Localizations.localeOf(context).languageCode;
final title = languageCode == 'en' ? 'Focus Points' : '断卦要点';
if (points.isEmpty) {
return const SizedBox.shrink();
}
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(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: colors.primary,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: AppSpacing.sm),
...List<Widget>.generate(points.length, (index) {
return Padding(
padding: const EdgeInsets.only(bottom: AppSpacing.xs),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${index + 1}. ',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colors.primary,
fontWeight: FontWeight.w700,
),
),
Expanded(
child: Text(
points[index],
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(height: 1.55),
),
),
],
),
);
}),
],
),
),
);
}
}
class _AnalysisCard extends StatelessWidget {
const _AnalysisCard({required this.title, required this.content});
@@ -715,8 +782,11 @@ class _HexagramDetailCard extends StatelessWidget {
for (int idx = 5; idx >= 0; idx--)
_YaoDetailRow(
line: data.yaoLines[idx],
target: data.targetYaoLines[idx],
showTarget: data.hasChangingYao,
target: idx < data.targetYaoLines.length
? data.targetYaoLines[idx]
: data.yaoLines[idx],
showTarget:
data.hasChangingYao && idx < data.targetYaoLines.length,
),
const SizedBox(height: AppSpacing.sm),
const Align(
@@ -24,12 +24,14 @@ class DivinationScreen extends StatefulWidget {
required this.userId,
required this.onCompleted,
this.runServiceOverride,
this.allowVibration = true,
});
final SessionStore sessionStore;
final String userId;
final Future<void> Function(DivinationResultData result) onCompleted;
final DivinationRunService? runServiceOverride;
final bool allowVibration;
@override
State<DivinationScreen> createState() => _DivinationScreenState();
@@ -51,7 +53,7 @@ class _DivinationScreenState extends State<DivinationScreen> {
widget.runServiceOverride ??
DivinationRunService(api: DivinationApi(apiClient: apiClient));
_params = DivinationParams(
method: DivinationMethod.manual,
method: DivinationMethod.auto,
questionType: QuestionType.career,
question: '',
divinationTime: DateTime.now(),
@@ -169,7 +171,10 @@ class _DivinationScreenState extends State<DivinationScreen> {
return;
}
final nextParams = _params.copyWith(divinationTime: DateTime.now());
final nextParams = _params.copyWith(
divinationTime: DateTime.now(),
allowVibration: widget.allowVibration,
);
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => AutoDivinationScreen(
@@ -406,13 +411,15 @@ Future<void> _showGuide(BuildContext context, AppLocalizations l10n) {
return DivinationGuideDialog(
title: l10n.divinationManualGuideTitle,
guideImages: const [
'assets/images/qigua/lc1.jpg',
'assets/images/qigua/lc2.jpg',
'assets/images/qigua/lc3.jpg',
'assets/images/qigua/lc4.jpg',
'assets/images/qigua/lc5.jpg',
['assets/images/tutorial/tutorial_1.png'],
['assets/images/tutorial/tutorial_2.png'],
['assets/images/tutorial/tutorial_3.png'],
],
instructions: [
l10n.divinationManualGuideStep1,
l10n.divinationManualGuideStep2,
l10n.divinationManualGuideStep3,
],
instructionText: l10n.divinationManualGuideInstruction,
);
},
);
@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
@@ -130,8 +132,16 @@ class _ManualDivinationScreenState extends State<ManualDivinationScreen>
builder: (_) {
return DivinationGuideDialog(
title: l10n.manualSelectYaoTitle,
guideImages: const ['lc2.jpg', 'lc3.jpg', 'lc4.jpg', 'lc5.jpg'],
instructionText: l10n.manualYaoTipContent,
guideImages: const [
['assets/images/tutorial/tutorial_1.png'],
['assets/images/tutorial/tutorial_2.png'],
['assets/images/tutorial/tutorial_3.png'],
],
instructions: [
l10n.divinationManualGuideStep1,
l10n.divinationManualGuideStep2,
l10n.divinationManualGuideStep3,
],
);
},
);
@@ -409,208 +419,199 @@ class _YaoSelectionCard extends StatelessWidget {
int yaoIndex,
void Function(int, YaoType) onSelect,
) {
final l10n = AppLocalizations.of(context)!;
final colors = Theme.of(context).colorScheme;
final options = <(String, YaoType, String)>[
(
'${DivinationTerms.yaoTypeLabels[YaoTypeLabel.youngYang]}${DivinationTerms.youngYangSymbol}',
YaoType.youngYang,
'字字字',
),
(
'${DivinationTerms.yaoTypeLabels[YaoTypeLabel.youngYin]}${DivinationTerms.youngYinSymbol}',
YaoType.youngYin,
'花花花',
),
(
'${DivinationTerms.yaoTypeLabels[YaoTypeLabel.oldYang]}${DivinationTerms.oldYangSymbol}',
YaoType.oldYang,
'字字字',
),
(
'${DivinationTerms.yaoTypeLabels[YaoTypeLabel.oldYin]}${DivinationTerms.oldYinSymbol}',
YaoType.oldYin,
'花花花',
),
];
return showDialog<void>(
context: context,
builder: (context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.lg),
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(AppSpacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
l10n.manualSelectYaoTitle,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: colors.primary,
fontWeight: FontWeight.w700,
),
),
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.close),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
const SizedBox(height: AppSpacing.md),
...options.map((option) {
return Padding(
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
child: _YaoOptionCard(
label: option.$1,
pattern: option.$3,
isSelected: false,
onTap: () {
onSelect(yaoIndex, option.$2);
Navigator.of(context).pop();
},
),
);
}),
const SizedBox(height: AppSpacing.md),
Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: colors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.coinFaceGuideTitle,
style: Theme.of(context).textTheme.titleSmall
?.copyWith(
color: colors.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: AppSpacing.sm),
ClipRRect(
borderRadius: BorderRadius.circular(AppRadius.sm),
child: Image.asset(
'assets/images/qigua/zihua.jpg',
width: double.infinity,
height: 120,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) =>
Container(
height: 120,
color: colors.errorContainer,
child: Center(
child: Text(
l10n.divinationClose,
style: TextStyle(
color: colors.onErrorContainer,
),
),
),
),
),
),
const SizedBox(height: AppSpacing.xs),
Text(
l10n.coinFaceGuideDescription,
style: Theme.of(context).textTheme.bodySmall
?.copyWith(color: colors.onSurfaceVariant),
),
],
),
),
],
),
),
),
return _ThreeCoinSelectorDialog(
onConfirm: (yaoType) {
onSelect(yaoIndex, yaoType);
Navigator.of(context).pop();
},
);
},
);
}
}
class _YaoOptionCard extends StatelessWidget {
const _YaoOptionCard({
required this.label,
required this.pattern,
required this.isSelected,
required this.onTap,
});
class _ThreeCoinSelectorDialog extends StatefulWidget {
const _ThreeCoinSelectorDialog({required this.onConfirm});
final String label;
final String pattern;
final bool isSelected;
final VoidCallback onTap;
final void Function(YaoType) onConfirm;
@override
State<_ThreeCoinSelectorDialog> createState() =>
_ThreeCoinSelectorDialogState();
}
class _ThreeCoinSelectorDialogState extends State<_ThreeCoinSelectorDialog> {
final List<bool> _coinStates = [false, false, false];
YaoType get _currentYaoType {
final huaCount = _coinStates.where((isHua) => isHua).length;
return switch (huaCount) {
0 => YaoType.oldYin,
1 => YaoType.youngYang,
2 => YaoType.youngYin,
3 => YaoType.oldYang,
_ => YaoType.undetermined,
};
}
void _toggleCoin(int index) {
setState(() {
_coinStates[index] = !_coinStates[index];
});
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final colors = Theme.of(context).colorScheme;
return Material(
color: isSelected ? colors.primaryContainer : colors.surface,
borderRadius: BorderRadius.circular(AppRadius.md),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(AppRadius.md),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm + 4,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(
color: isSelected ? colors.primary : colors.outlineVariant,
width: isSelected ? 2 : 1,
),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: isSelected
? colors.onPrimaryContainer
: colors.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 2),
Text(
pattern,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: isSelected
? colors.onPrimaryContainer.withValues(alpha: 0.7)
: colors.onSurfaceVariant,
),
),
],
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.lg),
),
child: Padding(
padding: const EdgeInsets.all(AppSpacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
l10n.manualSelectYaoTitle,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: colors.primary,
fontWeight: FontWeight.w700,
),
),
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.close),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
const SizedBox(height: AppSpacing.lg),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(3, (index) {
return _FlippingCoin(
isHua: _coinStates[index],
onTap: () => _toggleCoin(index),
);
}),
),
const SizedBox(height: AppSpacing.md),
Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: colors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(AppRadius.md),
),
Icon(
Icons.chevron_right,
color: isSelected
? colors.onPrimaryContainer
: colors.onSurfaceVariant,
child: Text(
l10n.manualCoinSelectHint,
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: colors.onSurfaceVariant),
textAlign: TextAlign.center,
),
],
),
),
const SizedBox(height: AppSpacing.lg),
SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: () => widget.onConfirm(_currentYaoType),
child: Text(l10n.confirm),
),
),
],
),
),
);
}
}
class _FlippingCoin extends StatefulWidget {
const _FlippingCoin({required this.isHua, required this.onTap});
final bool isHua;
final VoidCallback onTap;
@override
State<_FlippingCoin> createState() => _FlippingCoinState();
}
class _FlippingCoinState extends State<_FlippingCoin>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
bool _showHua = false;
@override
void initState() {
super.initState();
_showHua = widget.isHua;
_controller = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_animation = Tween<double>(
begin: 0,
end: 1,
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
}
@override
void didUpdateWidget(_FlippingCoin oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.isHua != widget.isHua) {
if (widget.isHua != _showHua) {
_controller.forward().then((_) {
setState(() {
_showHua = widget.isHua;
});
_controller.reverse();
});
}
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onTap,
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
final angle = _animation.value * pi;
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(angle),
child: ClipOval(
child: Image.asset(
_showHua
? 'assets/images/qigua/hua.jpg'
: 'assets/images/qigua/zi.jpg',
width: 80,
height: 80,
fit: BoxFit.cover,
),
),
);
},
),
);
}
}
+41 -5
View File
@@ -80,11 +80,15 @@
"signBad": "Inauspicious",
"language": "Language",
"settingsTitle": "Settings",
"settingsSectionGeneral": "General",
"settingsSectionGeneral": "Preferences",
"settingsSectionQuickAccess": "Primary Menu",
"settingsSectionAccount": "Account",
"settingsSectionPrivacy": "Privacy",
"settingsSectionNotification": "Notifications",
"settingsInterfaceLanguage": "Interface Language",
"settingsAiLanguage": "AI Response Language",
"settingsNotificationAllow": "Allow Notifications",
"settingsNotificationVibration": "Allow Vibration",
"settingsSectionAbout": "About",
"settingsGeneralTitle": "General Settings",
"settingsGeneralSubtitle": "Language: {currentLanguage}. Other fields are reserved to match profiles.settings.",
@@ -166,14 +170,14 @@
}
}
},
"settingsCoinCenterDescription": "Payment is not connected yet. The UI now shows packages and the recharge entry.",
"settingsCoinCenterDescription": "",
"settingsCoinRechargeSection": "Recharge Packages",
"settingsCoinPackBasic": "Starter Pack",
"settingsCoinPackPopular": "Popular Pack",
"settingsCoinPackPremium": "Premium Pack",
"settingsCoinPackPopularBadge": "Popular",
"settingsPurchaseButton": "Pay Now",
"settingsPurchasePending": "Payment is not connected yet",
"settingsPurchasePending": "",
"settingsCoinAmount": "{amount} credits",
"@settingsCoinAmount": {
"placeholders": {
@@ -226,7 +230,9 @@
"divinationMethodTipManual": "Manual: Prepare three identical coins.",
"divinationMethodTipRecommend": "Manual casting provides higher accuracy.",
"divinationManualGuideTitle": "Manual Casting Tutorial",
"divinationManualGuideInstruction": "Prepare three identical coins and cast six times following the guide.",
"divinationManualGuideStep1": "Left: Pattern side. Right: Inscription side. Prepare three identical coins or similar tokens.",
"divinationManualGuideStep2": "Hold the coins in both hands, think about your question, then toss them onto a table. Record how many inscription sides and pattern sides appear.",
"divinationManualGuideStep3": "Record each result by whether the inscription side or pattern side faces up. Repeat 6 times, recording from bottom to top.",
"divinationIAcknowledge": "I Understand",
"divinationClose": "Close",
"divinationModify": "Modify",
@@ -287,6 +293,7 @@
"transitionPreparing": "Deriving...",
"transitionDeriving": "Analyzing...",
"transitionDone": "Complete\nTap to view",
"iChingTitle": "I Ching",
"processingCardQianTitle": "Qian • The Creative",
"processingCardQianQuote": "The movement of Heaven is full of power; thus the noble one makes himself strong and tireless.",
"processingCardDuiTitle": "Dui • The Joyous",
@@ -321,6 +328,7 @@
"manualYaoInstruction": "Tap to view casting method and coin combination guide",
"manualYaoTipTitle": "Tip",
"manualYaoTipContent": "Select from bottom to top, not top to bottom.\n\nCast three coins together, select once each time, six times total.",
"manualCoinSelectHint": "Tap coins to flip between inscription and pattern. Record your casting result.",
"autoScreenTitle": "Auto Casting",
"autoSelectTime": "Select time",
"autoCoinDivination": "Coin Casting",
@@ -365,5 +373,33 @@
"cancel": "Cancel",
"autoSelectTime": "Select Time",
"coinFaceGuideTitle": "Coin Face Guide",
"coinFaceGuideDescription": "字 = side with inscription\n花 = side with pattern"
"coinFaceGuideDescription": "Left: Pattern side. Right: Inscription side.",
"settingsInviteTitle": "My Invitation",
"settingsInviteSubtitle": "Invite friends, earn rewards together",
"settingsInviteMyCode": "My Invite Code",
"settingsInviteCopySuccess": "Invite code copied",
"settingsInviteCopied": "Copied",
"settingsInviteCopy": "Copy",
"settingsInviteStats": "Invited: {count} friend(s)",
"@settingsInviteStats": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"settingsInviteBindCode": "Bind Invite Code",
"settingsInviteBindHint": "Enter your friend's invite code",
"settingsInviteBindPlaceholder": "6-digit code",
"settingsInviteBindButton": "Bind",
"settingsInviteBindSuccess": "Invite code bound successfully",
"settingsInviteBindFailed": "Failed to bind invite code",
"settingsInviteGenerateTitle": "Generate My Invite Code",
"settingsInviteGenerateButton": "Generate My Invite Code",
"settingsInviteGenerateSuccess": "Invite code generated",
"settingsInviteEmptyTitle": "Invite Friends, Earn Rewards",
"settingsInviteEmptyDescription": "Each friend who registers using your invite code gives you bonus credits",
"settingsInviteInputLabel": "Enter invite code (optional)",
"settingsInviteInputHint": "Enter code to bind your inviter",
"settingsInviteInvalidCode": "Please enter a valid 6-character invite code"
}
+198 -30
View File
@@ -497,7 +497,7 @@ abstract class AppLocalizations {
/// No description provided for @settingsSectionGeneral.
///
/// In zh, this message translates to:
/// **'通用设置'**
/// **'偏好设置'**
String get settingsSectionGeneral;
/// No description provided for @settingsSectionQuickAccess.
@@ -524,6 +524,30 @@ abstract class AppLocalizations {
/// **'通知设置'**
String get settingsSectionNotification;
/// No description provided for @settingsInterfaceLanguage.
///
/// In zh, this message translates to:
/// **'界面语言'**
String get settingsInterfaceLanguage;
/// No description provided for @settingsAiLanguage.
///
/// In zh, this message translates to:
/// **'AI 回复语言'**
String get settingsAiLanguage;
/// No description provided for @settingsNotificationAllow.
///
/// In zh, this message translates to:
/// **'允许通知'**
String get settingsNotificationAllow;
/// No description provided for @settingsNotificationVibration.
///
/// In zh, this message translates to:
/// **'允许振动'**
String get settingsNotificationVibration;
/// No description provided for @settingsSectionAbout.
///
/// In zh, this message translates to:
@@ -584,12 +608,6 @@ abstract class AppLocalizations {
/// **'点数可用于后续起卦与相关服务消费'**
String get settingsCoinHeroSubtitle;
/// No description provided for @settingsAiLanguage.
///
/// In zh, this message translates to:
/// **'AI 回复语言'**
String get settingsAiLanguage;
/// No description provided for @settingsAiLanguageHint.
///
/// In zh, this message translates to:
@@ -845,7 +863,7 @@ abstract class AppLocalizations {
/// No description provided for @settingsCoinCenterDescription.
///
/// In zh, this message translates to:
/// **'充值入口暂未接入支付逻辑,先展示套餐与购买流程入口。'**
/// **''**
String get settingsCoinCenterDescription;
/// No description provided for @settingsCoinRechargeSection.
@@ -887,7 +905,7 @@ abstract class AppLocalizations {
/// No description provided for @settingsPurchasePending.
///
/// In zh, this message translates to:
/// **'支付能力暂未接入'**
/// **''**
String get settingsPurchasePending;
/// No description provided for @settingsCoinAmount.
@@ -1118,11 +1136,23 @@ abstract class AppLocalizations {
/// **'手动起卦教程'**
String get divinationManualGuideTitle;
/// No description provided for @divinationManualGuideInstruction.
/// No description provided for @divinationManualGuideStep1.
///
/// In zh, this message translates to:
/// **'准备三枚同样铜钱,按页面引导连续完成六次摇卦。'**
String get divinationManualGuideInstruction;
/// **'左侧为花面,右侧为字面。准备三枚相同的钱币,任何类似款式均可。'**
String get divinationManualGuideStep1;
/// No description provided for @divinationManualGuideStep2.
///
/// In zh, this message translates to:
/// **'双手捧起钱币,闭目心中默念所问之事,然后抛掷钱币于桌面,记录字面和花面出现的次数。'**
String get divinationManualGuideStep2;
/// No description provided for @divinationManualGuideStep3.
///
/// In zh, this message translates to:
/// **'记录每次结果,按照「字面在上还是花面在上」记录。重复六次,从下往上记录。'**
String get divinationManualGuideStep3;
/// No description provided for @divinationIAcknowledge.
///
@@ -1382,100 +1412,106 @@ abstract class AppLocalizations {
/// **'解卦完成\n点击查看'**
String get transitionDone;
/// No description provided for @iChingTitle.
///
/// In zh, this message translates to:
/// **'周易'**
String get iChingTitle;
/// No description provided for @processingCardQianTitle.
///
/// In zh, this message translates to:
/// **'Qian • The Creative'**
/// **'乾 • 元亨利贞'**
String get processingCardQianTitle;
/// No description provided for @processingCardQianQuote.
///
/// In zh, this message translates to:
/// **'The movement of Heaven is full of power; thus the noble one makes himself strong and tireless.'**
/// **'天行健,君子以自强不息。'**
String get processingCardQianQuote;
/// No description provided for @processingCardDuiTitle.
///
/// In zh, this message translates to:
/// **'Dui • The Joyous'**
/// **'兑 • 亨利贞'**
String get processingCardDuiTitle;
/// No description provided for @processingCardDuiQuote.
///
/// In zh, this message translates to:
/// **'Joy grounded in integrity brings openness, harmony, and right expression.'**
/// **'丽泽兑,君子以朋友讲习。'**
String get processingCardDuiQuote;
/// No description provided for @processingCardLiTitle.
///
/// In zh, this message translates to:
/// **'LiThe Clinging Fire'**
/// **'明两作亨利贞'**
String get processingCardLiTitle;
/// No description provided for @processingCardLiQuote.
///
/// In zh, this message translates to:
/// **'With clear brilliance, the great one illumines all directions.'**
/// **'大人以继明照于四方。'**
String get processingCardLiQuote;
/// No description provided for @processingCardZhenTitle.
///
/// In zh, this message translates to:
/// **'Zhen • The Arousing Thunder'**
/// **'震 • 亨震来虩虩,笑言哑哑'**
String get processingCardZhenTitle;
/// No description provided for @processingCardZhenQuote.
///
/// In zh, this message translates to:
/// **'Shock awakens the heart; composure turns fear into growth.'**
/// **'震惊百里,惊远而惧迩也。'**
String get processingCardZhenQuote;
/// No description provided for @processingCardXunTitle.
///
/// In zh, this message translates to:
/// **'Xun • The Gentle Wind'**
/// **'巽 • 小亨利贞'**
String get processingCardXunTitle;
/// No description provided for @processingCardXunQuote.
///
/// In zh, this message translates to:
/// **'Gentle penetration furthers progress and helps one meet the right people.'**
/// **'随风,君子以申命行事。'**
String get processingCardXunQuote;
/// No description provided for @processingCardKanTitle.
///
/// In zh, this message translates to:
/// **'Kan • The Abysmal Water'**
/// **'坎 • 习坎有孚维心亨'**
String get processingCardKanTitle;
/// No description provided for @processingCardKanQuote.
///
/// In zh, this message translates to:
/// **'In danger, sincerity and disciplined action carry one through.'**
/// **'水流而不盈,行险而不失其信。'**
String get processingCardKanQuote;
/// No description provided for @processingCardGenTitle.
///
/// In zh, this message translates to:
/// **'Gen • Keeping Still Mountain'**
/// **'艮 • 艮其背不获其身'**
String get processingCardGenTitle;
/// No description provided for @processingCardGenQuote.
///
/// In zh, this message translates to:
/// **'Stillness at the proper time keeps one centered and steady in place.'**
/// **'时止则止,时行则行,动静不失其时。'**
String get processingCardGenQuote;
/// No description provided for @processingCardKunTitle.
///
/// In zh, this message translates to:
/// **'Kun • The Receptive Earth'**
/// **'坤 • 元亨利牝马之贞'**
String get processingCardKunTitle;
/// No description provided for @processingCardKunQuote.
///
/// In zh, this message translates to:
/// **'The Earth\'s condition is devoted receptivity; the noble one carries all with broad virtue.'**
/// **'地势坤,君子以厚德载物。'**
String get processingCardKunQuote;
/// No description provided for @ganZhiInfo.
@@ -1586,6 +1622,12 @@ abstract class AppLocalizations {
/// **'请从下往上选,不是从上往下选。\n\n三枚铜钱一起摇,摇完一次选一次,一共摇六次。'**
String get manualYaoTipContent;
/// No description provided for @manualCoinSelectHint.
///
/// In zh, this message translates to:
/// **'点击硬币可翻转,调整字面和花面。记录摇卦结果。'**
String get manualCoinSelectHint;
/// No description provided for @autoScreenTitle.
///
/// In zh, this message translates to:
@@ -1715,14 +1757,140 @@ abstract class AppLocalizations {
/// No description provided for @coinFaceGuideTitle.
///
/// In zh, this message translates to:
/// **'字花图片说明'**
/// **'字花对照说明'**
String get coinFaceGuideTitle;
/// No description provided for @coinFaceGuideDescription.
///
/// In zh, this message translates to:
/// **'字=铜钱有字的一面\n花=铜钱有花纹的一面'**
/// **'左侧为花面,右侧为字面。'**
String get coinFaceGuideDescription;
/// No description provided for @settingsInviteTitle.
///
/// In zh, this message translates to:
/// **'我的邀请'**
String get settingsInviteTitle;
/// No description provided for @settingsInviteSubtitle.
///
/// In zh, this message translates to:
/// **'邀请好友,共同获得奖励'**
String get settingsInviteSubtitle;
/// No description provided for @settingsInviteMyCode.
///
/// In zh, this message translates to:
/// **'我的邀请码'**
String get settingsInviteMyCode;
/// No description provided for @settingsInviteCopySuccess.
///
/// In zh, this message translates to:
/// **'邀请码已复制'**
String get settingsInviteCopySuccess;
/// No description provided for @settingsInviteCopied.
///
/// In zh, this message translates to:
/// **'已复制'**
String get settingsInviteCopied;
/// No description provided for @settingsInviteCopy.
///
/// In zh, this message translates to:
/// **'复制'**
String get settingsInviteCopy;
/// No description provided for @settingsInviteStats.
///
/// In zh, this message translates to:
/// **'已邀请:{count} 位好友'**
String settingsInviteStats(int count);
/// No description provided for @settingsInviteBindCode.
///
/// In zh, this message translates to:
/// **'绑定邀请码'**
String get settingsInviteBindCode;
/// No description provided for @settingsInviteBindHint.
///
/// In zh, this message translates to:
/// **'输入好友的邀请码'**
String get settingsInviteBindHint;
/// No description provided for @settingsInviteBindPlaceholder.
///
/// In zh, this message translates to:
/// **'6位邀请码'**
String get settingsInviteBindPlaceholder;
/// No description provided for @settingsInviteBindButton.
///
/// In zh, this message translates to:
/// **'绑定'**
String get settingsInviteBindButton;
/// No description provided for @settingsInviteBindSuccess.
///
/// In zh, this message translates to:
/// **'邀请码绑定成功'**
String get settingsInviteBindSuccess;
/// No description provided for @settingsInviteBindFailed.
///
/// In zh, this message translates to:
/// **'邀请码绑定失败'**
String get settingsInviteBindFailed;
/// No description provided for @settingsInviteGenerateTitle.
///
/// In zh, this message translates to:
/// **'生成我的邀请码'**
String get settingsInviteGenerateTitle;
/// No description provided for @settingsInviteGenerateButton.
///
/// In zh, this message translates to:
/// **'生成我的邀请码'**
String get settingsInviteGenerateButton;
/// No description provided for @settingsInviteGenerateSuccess.
///
/// In zh, this message translates to:
/// **'邀请码生成成功'**
String get settingsInviteGenerateSuccess;
/// No description provided for @settingsInviteEmptyTitle.
///
/// In zh, this message translates to:
/// **'邀请好友,获得奖励'**
String get settingsInviteEmptyTitle;
/// No description provided for @settingsInviteEmptyDescription.
///
/// In zh, this message translates to:
/// **'每成功邀请一位好友注册,您将获得积分奖励'**
String get settingsInviteEmptyDescription;
/// No description provided for @settingsInviteInputLabel.
///
/// In zh, this message translates to:
/// **'输入邀请码(选填)'**
String get settingsInviteInputLabel;
/// No description provided for @settingsInviteInputHint.
///
/// In zh, this message translates to:
/// **'输入邀请码绑定您的邀请人'**
String get settingsInviteInputHint;
/// No description provided for @settingsInviteInvalidCode.
///
/// In zh, this message translates to:
/// **'请输入有效的6位邀请码'**
String get settingsInviteInvalidCode;
}
class _AppLocalizationsDelegate
+100 -10
View File
@@ -217,7 +217,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsTitle => 'Settings';
@override
String get settingsSectionGeneral => 'General';
String get settingsSectionGeneral => 'Preferences';
@override
String get settingsSectionQuickAccess => 'Primary Menu';
@@ -231,6 +231,18 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get settingsSectionNotification => 'Notifications';
@override
String get settingsInterfaceLanguage => 'Interface Language';
@override
String get settingsAiLanguage => 'AI Response Language';
@override
String get settingsNotificationAllow => 'Allow Notifications';
@override
String get settingsNotificationVibration => 'Allow Vibration';
@override
String get settingsSectionAbout => 'About';
@@ -268,9 +280,6 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsCoinHeroSubtitle =>
'Credits will be used for casting and related services later.';
@override
String get settingsAiLanguage => 'AI Response Language';
@override
String get settingsAiLanguageHint =>
'This field will align with profiles.settings.preferences.ai_language once the real preference flow is connected.';
@@ -411,8 +420,7 @@ class AppLocalizationsEn extends AppLocalizations {
}
@override
String get settingsCoinCenterDescription =>
'Payment is not connected yet. The UI now shows packages and the recharge entry.';
String get settingsCoinCenterDescription => '';
@override
String get settingsCoinRechargeSection => 'Recharge Packages';
@@ -433,7 +441,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsPurchaseButton => 'Pay Now';
@override
String get settingsPurchasePending => 'Payment is not connected yet';
String get settingsPurchasePending => '';
@override
String settingsCoinAmount(int amount) {
@@ -566,8 +574,16 @@ class AppLocalizationsEn extends AppLocalizations {
String get divinationManualGuideTitle => 'Manual Casting Tutorial';
@override
String get divinationManualGuideInstruction =>
'Prepare three identical coins and cast six times following the guide.';
String get divinationManualGuideStep1 =>
'Left: Pattern side. Right: Inscription side. Prepare three identical coins or similar tokens.';
@override
String get divinationManualGuideStep2 =>
'Hold the coins in both hands, think about your question, then toss them onto a table. Record how many inscription sides and pattern sides appear.';
@override
String get divinationManualGuideStep3 =>
'Record each result by whether the inscription side or pattern side faces up. Repeat 6 times, recording from bottom to top.';
@override
String get divinationIAcknowledge => 'I Understand';
@@ -703,6 +719,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get transitionDone => 'Complete\nTap to view';
@override
String get iChingTitle => 'I Ching';
@override
String get processingCardQianTitle => 'Qian • The Creative';
@@ -815,6 +834,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get manualYaoTipContent =>
'Select from bottom to top, not top to bottom.\n\nCast three coins together, select once each time, six times total.';
@override
String get manualCoinSelectHint =>
'Tap coins to flip between inscription and pattern. Record your casting result.';
@override
String get autoScreenTitle => 'Auto Casting';
@@ -890,5 +913,72 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get coinFaceGuideDescription =>
'字 = side with inscription\n花 = side with pattern';
'Left: Pattern side. Right: Inscription side.';
@override
String get settingsInviteTitle => 'My Invitation';
@override
String get settingsInviteSubtitle => 'Invite friends, earn rewards together';
@override
String get settingsInviteMyCode => 'My Invite Code';
@override
String get settingsInviteCopySuccess => 'Invite code copied';
@override
String get settingsInviteCopied => 'Copied';
@override
String get settingsInviteCopy => 'Copy';
@override
String settingsInviteStats(int count) {
return 'Invited: $count friend(s)';
}
@override
String get settingsInviteBindCode => 'Bind Invite Code';
@override
String get settingsInviteBindHint => 'Enter your friend\'s invite code';
@override
String get settingsInviteBindPlaceholder => '6-digit code';
@override
String get settingsInviteBindButton => 'Bind';
@override
String get settingsInviteBindSuccess => 'Invite code bound successfully';
@override
String get settingsInviteBindFailed => 'Failed to bind invite code';
@override
String get settingsInviteGenerateTitle => 'Generate My Invite Code';
@override
String get settingsInviteGenerateButton => 'Generate My Invite Code';
@override
String get settingsInviteGenerateSuccess => 'Invite code generated';
@override
String get settingsInviteEmptyTitle => 'Invite Friends, Earn Rewards';
@override
String get settingsInviteEmptyDescription =>
'Each friend who registers using your invite code gives you bonus credits';
@override
String get settingsInviteInputLabel => 'Enter invite code (optional)';
@override
String get settingsInviteInputHint => 'Enter code to bind your inviter';
@override
String get settingsInviteInvalidCode =>
'Please enter a valid 6-character invite code';
}
+113 -33
View File
@@ -215,7 +215,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get settingsTitle => '设置';
@override
String get settingsSectionGeneral => '通用设置';
String get settingsSectionGeneral => '偏好设置';
@override
String get settingsSectionQuickAccess => '一级菜单';
@@ -229,6 +229,18 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get settingsSectionNotification => '通知设置';
@override
String get settingsInterfaceLanguage => '界面语言';
@override
String get settingsAiLanguage => 'AI 回复语言';
@override
String get settingsNotificationAllow => '允许通知';
@override
String get settingsNotificationVibration => '允许振动';
@override
String get settingsSectionAbout => '关于';
@@ -264,9 +276,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get settingsCoinHeroSubtitle => '点数可用于后续起卦与相关服务消费';
@override
String get settingsAiLanguage => 'AI 回复语言';
@override
String get settingsAiLanguageHint =>
'该字段将对齐 profiles.settings.preferences.ai_language,后续接入真实偏好设置。';
@@ -403,7 +412,7 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
String get settingsCoinCenterDescription => '充值入口暂未接入支付逻辑,先展示套餐与购买流程入口。';
String get settingsCoinCenterDescription => '';
@override
String get settingsCoinRechargeSection => '充值套餐';
@@ -424,7 +433,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get settingsPurchaseButton => '立即支付';
@override
String get settingsPurchasePending => '支付能力暂未接入';
String get settingsPurchasePending => '';
@override
String settingsCoinAmount(int amount) {
@@ -549,7 +558,15 @@ class AppLocalizationsZh extends AppLocalizations {
String get divinationManualGuideTitle => '手动起卦教程';
@override
String get divinationManualGuideInstruction => '准备三枚同样铜钱,按页面引导连续完成六次摇卦';
String get divinationManualGuideStep1 => '左侧为花面,右侧为字面。准备三枚相同的钱币,任何类似款式均可';
@override
String get divinationManualGuideStep2 =>
'双手捧起钱币,闭目心中默念所问之事,然后抛掷钱币于桌面,记录字面和花面出现的次数。';
@override
String get divinationManualGuideStep3 =>
'记录每次结果,按照「字面在上还是花面在上」记录。重复六次,从下往上记录。';
@override
String get divinationIAcknowledge => '我知道了';
@@ -686,60 +703,55 @@ class AppLocalizationsZh extends AppLocalizations {
String get transitionDone => '解卦完成\n点击查看';
@override
String get processingCardQianTitle => 'Qian • The Creative';
String get iChingTitle => '周易';
@override
String get processingCardQianQuote =>
'The movement of Heaven is full of power; thus the noble one makes himself strong and tireless.';
String get processingCardQianTitle => '乾 • 元亨利贞';
@override
String get processingCardDuiTitle => 'Dui • The Joyous';
String get processingCardQianQuote => '天行健,君子以自强不息。';
@override
String get processingCardDuiQuote =>
'Joy grounded in integrity brings openness, harmony, and right expression.';
String get processingCardDuiTitle => '兑 • 亨利贞';
@override
String get processingCardLiTitle => 'Li • The Clinging Fire';
String get processingCardDuiQuote => '丽泽兑,君子以朋友讲习。';
@override
String get processingCardLiQuote =>
'With clear brilliance, the great one illumines all directions.';
String get processingCardLiTitle => '离 • 明两作亨利贞';
@override
String get processingCardZhenTitle => 'Zhen • The Arousing Thunder';
String get processingCardLiQuote => '大人以继明照于四方。';
@override
String get processingCardZhenQuote =>
'Shock awakens the heart; composure turns fear into growth.';
String get processingCardZhenTitle => '震 • 亨震来虩虩,笑言哑哑';
@override
String get processingCardXunTitle => 'Xun • The Gentle Wind';
String get processingCardZhenQuote => '震惊百里,惊远而惧迩也。';
@override
String get processingCardXunQuote =>
'Gentle penetration furthers progress and helps one meet the right people.';
String get processingCardXunTitle => '巽 • 小亨利贞';
@override
String get processingCardKanTitle => 'Kan • The Abysmal Water';
String get processingCardXunQuote => '随风,君子以申命行事。';
@override
String get processingCardKanQuote =>
'In danger, sincerity and disciplined action carry one through.';
String get processingCardKanTitle => '坎 • 习坎有孚维心亨';
@override
String get processingCardGenTitle => 'Gen • Keeping Still Mountain';
String get processingCardKanQuote => '水流而不盈,行险而不失其信。';
@override
String get processingCardGenQuote =>
'Stillness at the proper time keeps one centered and steady in place.';
String get processingCardGenTitle => '艮 • 艮其背不获其身';
@override
String get processingCardKunTitle => 'Kun • The Receptive Earth';
String get processingCardGenQuote => '时止则止,时行则行,动静不失其时。';
@override
String get processingCardKunQuote =>
'The Earth\'s condition is devoted receptivity; the noble one carries all with broad virtue.';
String get processingCardKunTitle => '坤 • 元亨利牝马之贞';
@override
String get processingCardKunQuote => '地势坤,君子以厚德载物。';
@override
String get ganZhiInfo => '干支信息';
@@ -795,6 +807,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get manualYaoTipContent => '请从下往上选,不是从上往下选。\n\n三枚铜钱一起摇,摇完一次选一次,一共摇六次。';
@override
String get manualCoinSelectHint => '点击硬币可翻转,调整字面和花面。记录摇卦结果。';
@override
String get autoScreenTitle => '自动起卦';
@@ -865,8 +880,73 @@ class AppLocalizationsZh extends AppLocalizations {
String get cancel => '取消';
@override
String get coinFaceGuideTitle => '字花图片说明';
String get coinFaceGuideTitle => '字花对照说明';
@override
String get coinFaceGuideDescription => '字=铜钱有字的一面\n花=铜钱有花纹的一面';
String get coinFaceGuideDescription => '左侧为花面,右侧为字面。';
@override
String get settingsInviteTitle => '我的邀请';
@override
String get settingsInviteSubtitle => '邀请好友,共同获得奖励';
@override
String get settingsInviteMyCode => '我的邀请码';
@override
String get settingsInviteCopySuccess => '邀请码已复制';
@override
String get settingsInviteCopied => '已复制';
@override
String get settingsInviteCopy => '复制';
@override
String settingsInviteStats(int count) {
return '已邀请:$count 位好友';
}
@override
String get settingsInviteBindCode => '绑定邀请码';
@override
String get settingsInviteBindHint => '输入好友的邀请码';
@override
String get settingsInviteBindPlaceholder => '6位邀请码';
@override
String get settingsInviteBindButton => '绑定';
@override
String get settingsInviteBindSuccess => '邀请码绑定成功';
@override
String get settingsInviteBindFailed => '邀请码绑定失败';
@override
String get settingsInviteGenerateTitle => '生成我的邀请码';
@override
String get settingsInviteGenerateButton => '生成我的邀请码';
@override
String get settingsInviteGenerateSuccess => '邀请码生成成功';
@override
String get settingsInviteEmptyTitle => '邀请好友,获得奖励';
@override
String get settingsInviteEmptyDescription => '每成功邀请一位好友注册,您将获得积分奖励';
@override
String get settingsInviteInputLabel => '输入邀请码(选填)';
@override
String get settingsInviteInputHint => '输入邀请码绑定您的邀请人';
@override
String get settingsInviteInvalidCode => '请输入有效的6位邀请码';
}
+58 -22
View File
@@ -80,11 +80,15 @@
"signBad": "下下签",
"language": "语言",
"settingsTitle": "设置",
"settingsSectionGeneral": "通用设置",
"settingsSectionGeneral": "偏好设置",
"settingsSectionQuickAccess": "一级菜单",
"settingsSectionAccount": "账户操作",
"settingsSectionPrivacy": "隐私设置",
"settingsSectionNotification": "通知设置",
"settingsInterfaceLanguage": "界面语言",
"settingsAiLanguage": "AI回复语言",
"settingsNotificationAllow": "允许通知",
"settingsNotificationVibration": "允许振动",
"settingsSectionAbout": "关于",
"settingsGeneralTitle": "通用设置",
"settingsGeneralSubtitle": "语言:{currentLanguage},其余字段按 profiles.settings 结构预留",
@@ -166,14 +170,14 @@
}
}
},
"settingsCoinCenterDescription": "充值入口暂未接入支付逻辑,先展示套餐与购买流程入口。",
"settingsCoinCenterDescription": "",
"settingsCoinRechargeSection": "充值套餐",
"settingsCoinPackBasic": "入门补充包",
"settingsCoinPackPopular": "常用加量包",
"settingsCoinPackPremium": "高频进阶包",
"settingsCoinPackPopularBadge": "推荐",
"settingsPurchaseButton": "立即支付",
"settingsPurchasePending": "支付能力暂未接入",
"settingsPurchasePending": "",
"settingsCoinAmount": "{amount} 点数",
"@settingsCoinAmount": {
"placeholders": {
@@ -226,7 +230,9 @@
"divinationMethodTipManual": "手动起卦:需要准备三枚同样的铜钱或硬币。",
"divinationMethodTipRecommend": "推荐使用手动起卦,卦象解读准确概率更高。",
"divinationManualGuideTitle": "手动起卦教程",
"divinationManualGuideInstruction": "准备三枚同样铜钱,按页面引导连续完成六次摇卦。",
"divinationManualGuideStep1": "左侧为花面,右侧为字面。准备三枚相同的钱币,任何类似款式均可。",
"divinationManualGuideStep2": "双手捧起钱币,闭目心中默念所问之事,然后抛掷钱币于桌面,记录字面和花面出现的次数。",
"divinationManualGuideStep3": "记录每次结果,按照「字面在上还是花面在上」记录。重复六次,从下往上记录。",
"divinationIAcknowledge": "我知道了",
"divinationClose": "关闭",
"divinationModify": "修改",
@@ -287,22 +293,23 @@
"transitionPreparing": "天机推演中",
"transitionDeriving": "正在解卦",
"transitionDone": "解卦完成\n点击查看",
"processingCardQianTitle": "Qian • The Creative",
"processingCardQianQuote": "The movement of Heaven is full of power; thus the noble one makes himself strong and tireless.",
"processingCardDuiTitle": "Dui • The Joyous",
"processingCardDuiQuote": "Joy grounded in integrity brings openness, harmony, and right expression.",
"processingCardLiTitle": "Li • The Clinging Fire",
"processingCardLiQuote": "With clear brilliance, the great one illumines all directions.",
"processingCardZhenTitle": "Zhen • The Arousing Thunder",
"processingCardZhenQuote": "Shock awakens the heart; composure turns fear into growth.",
"processingCardXunTitle": "Xun • The Gentle Wind",
"processingCardXunQuote": "Gentle penetration furthers progress and helps one meet the right people.",
"processingCardKanTitle": "Kan • The Abysmal Water",
"processingCardKanQuote": "In danger, sincerity and disciplined action carry one through.",
"processingCardGenTitle": "Gen • Keeping Still Mountain",
"processingCardGenQuote": "Stillness at the proper time keeps one centered and steady in place.",
"processingCardKunTitle": "Kun • The Receptive Earth",
"processingCardKunQuote": "The Earth's condition is devoted receptivity; the noble one carries all with broad virtue.",
"iChingTitle": "周易",
"processingCardQianTitle": "乾 • 元亨利贞",
"processingCardQianQuote": "天行健,君子以自强不息。",
"processingCardDuiTitle": "兑 • 亨利贞",
"processingCardDuiQuote": "丽泽兑,君子以朋友讲习。",
"processingCardLiTitle": "离 • 明两作亨利贞",
"processingCardLiQuote": "大人以继明照于四方。",
"processingCardZhenTitle": "震 • 亨震来虩虩,笑言哑哑",
"processingCardZhenQuote": "震惊百里,惊远而惧迩也。",
"processingCardXunTitle": "巽 • 小亨利贞",
"processingCardXunQuote": "随风,君子以申命行事。",
"processingCardKanTitle": "坎 • 习坎有孚维心亨",
"processingCardKanQuote": "水流而不盈,行险而不失其信。",
"processingCardGenTitle": "艮 • 艮其背不获其身",
"processingCardGenQuote": "时止则止,时行则行,动静不失其时。",
"processingCardKunTitle": "坤 • 元亨利牝马之贞",
"processingCardKunQuote": "地势坤,君子以厚德载物。",
"ganZhiInfo": "干支信息",
"wuXingWangShuai": "五行旺衰",
"ganZhiKongWang": "空亡信息",
@@ -321,6 +328,7 @@
"manualYaoInstruction": "点击查看起卦方法与铜钱字花组合说明",
"manualYaoTipTitle": "提示",
"manualYaoTipContent": "请从下往上选,不是从上往下选。\n\n三枚铜钱一起摇,摇完一次选一次,一共摇六次。",
"manualCoinSelectHint": "点击硬币可翻转,调整字面和花面。记录摇卦结果。",
"autoScreenTitle": "自动起卦",
"autoSelectTime": "选择起卦时间",
"autoCoinDivination": "铜钱摇卦",
@@ -364,6 +372,34 @@
"confirm": "确认",
"cancel": "取消",
"autoSelectTime": "选择时间",
"coinFaceGuideTitle": "字花图片说明",
"coinFaceGuideDescription": "字=铜钱有字的一面\n花=铜钱有花纹的一面"
"coinFaceGuideTitle": "字花对照说明",
"coinFaceGuideDescription": "左侧为花面,右侧为字面。",
"settingsInviteTitle": "我的邀请",
"settingsInviteSubtitle": "邀请好友,共同获得奖励",
"settingsInviteMyCode": "我的邀请码",
"settingsInviteCopySuccess": "邀请码已复制",
"settingsInviteCopied": "已复制",
"settingsInviteCopy": "复制",
"settingsInviteStats": "已邀请:{count} 位好友",
"@settingsInviteStats": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"settingsInviteBindCode": "绑定邀请码",
"settingsInviteBindHint": "输入好友的邀请码",
"settingsInviteBindPlaceholder": "6位邀请码",
"settingsInviteBindButton": "绑定",
"settingsInviteBindSuccess": "邀请码绑定成功",
"settingsInviteBindFailed": "邀请码绑定失败",
"settingsInviteGenerateTitle": "生成我的邀请码",
"settingsInviteGenerateButton": "生成我的邀请码",
"settingsInviteGenerateSuccess": "邀请码生成成功",
"settingsInviteEmptyTitle": "邀请好友,获得奖励",
"settingsInviteEmptyDescription": "每成功邀请一位好友注册,您将获得积分奖励",
"settingsInviteInputLabel": "输入邀请码(选填)",
"settingsInviteInputHint": "输入邀请码绑定您的邀请人",
"settingsInviteInvalidCode": "请输入有效的6位邀请码"
}
@@ -5,15 +5,24 @@ import '../../theme/app_color_palette.dart';
import '../../theme/design_tokens.dart';
class DivinationGuideImage extends StatelessWidget {
const DivinationGuideImage({super.key, required this.path});
const DivinationGuideImage({super.key, required this.paths});
final String path;
final List<String> paths;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg),
child: Image.asset(path, fit: BoxFit.contain),
child: paths.length == 1
? Image.asset(paths.first, fit: BoxFit.contain)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(child: Image.asset(paths[0], fit: BoxFit.contain)),
const SizedBox(width: AppSpacing.md),
Expanded(child: Image.asset(paths[1], fit: BoxFit.contain)),
],
),
);
}
}
@@ -67,17 +76,31 @@ class DivinationInstructionCard extends StatelessWidget {
}
}
class DivinationGuideDialog extends StatelessWidget {
class DivinationGuideDialog extends StatefulWidget {
const DivinationGuideDialog({
super.key,
required this.title,
required this.guideImages,
required this.instructionText,
required this.instructions,
});
final String title;
final List<String> guideImages;
final String instructionText;
final List<List<String>> guideImages;
final List<String> instructions;
@override
State<DivinationGuideDialog> createState() => _DivinationGuideDialogState();
}
class _DivinationGuideDialogState extends State<DivinationGuideDialog> {
final PageController _pageController = PageController();
int _currentPage = 0;
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
@@ -90,7 +113,7 @@ class DivinationGuideDialog extends StatelessWidget {
children: [
const SizedBox(height: AppSpacing.lg),
Text(
title,
widget.title,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.w700,
@@ -98,15 +121,25 @@ class DivinationGuideDialog extends StatelessWidget {
),
const SizedBox(height: AppSpacing.md),
Expanded(
child: PageView(
children: guideImages
.map((path) => DivinationGuideImage(path: path))
.toList(),
child: PageView.builder(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_currentPage = index;
});
},
itemCount: widget.guideImages.length,
itemBuilder: (context, index) {
return DivinationGuideImage(paths: widget.guideImages[index]);
},
),
),
Padding(
padding: const EdgeInsets.all(AppSpacing.lg),
child: Text(instructionText),
child: Text(
widget.instructions[_currentPage],
textAlign: TextAlign.center,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(
@@ -12,6 +12,8 @@ abstract final class DivinationTerms {
static const yinYang = {true: '', false: ''};
static const ziHua = {true: '', false: ''};
static const wuXing = ['', '', '', '', ''];
static const yuanZhi = '';