docs: 更新协议文档,删除废弃计划文档

- 更新 http-error-codes, user-points-chat-data-protocol
- 更新 divination-run-protocol, profile-protocol
- 删除废弃的后端和前端设计计划文档
This commit is contained in:
qzl
2026-04-08 17:23:02 +08:00
parent 49fc9a116f
commit e80a82bef4
57 changed files with 4117 additions and 2269 deletions
@@ -1,8 +1,11 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../../../../core/auth/session_store.dart';
import '../../../divination/presentation/screens/divination_screen.dart';
import '../../../divination/presentation/screens/divination_result_screen.dart';
import '../../../divination/data/apis/divination_api.dart';
import '../../../divination/data/models/divination_params.dart';
import '../../../divination/data/models/divination_result.dart';
import '../../../settings/data/models/profile_settings.dart';
@@ -11,6 +14,7 @@ import '../../../../l10n/app_localizations.dart';
import '../../../../shared/theme/app_color_palette.dart';
import '../../../../shared/theme/design_tokens.dart';
import '../../../../shared/widgets/bottom_nav_bar.dart';
import '../../../../shared/widgets/divination/divination_summary_card.dart';
import '../../../../shared/widgets/toast/toast.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
@@ -23,11 +27,13 @@ class HomeScreen extends StatefulWidget {
required this.profileSettings,
required this.historyRecords,
required this.coinBalance,
required this.divinationApi,
required this.onLocaleChanged,
required this.onProfileSettingsChanged,
required this.onSaveProfile,
required this.onUploadAvatar,
required this.onDivinationCompleted,
required this.onDeleteHistorySession,
required this.onLogout,
});
@@ -37,6 +43,7 @@ class HomeScreen extends StatefulWidget {
final ProfileSettingsV1 profileSettings;
final List<DivinationResultData> historyRecords;
final int coinBalance;
final DivinationApi divinationApi;
final Future<void> Function(String languageTag) onLocaleChanged;
final Future<void> Function(ProfileSettingsV1 settings)
onProfileSettingsChanged;
@@ -45,6 +52,7 @@ class HomeScreen extends StatefulWidget {
final Future<ProfileSettingsV1> Function(String filePath) onUploadAvatar;
final Future<void> Function(DivinationResultData result)
onDivinationCompleted;
final Future<void> Function(String threadId) onDeleteHistorySession;
final Future<void> Function() onLogout;
@override
@@ -94,7 +102,9 @@ class _HomeScreenState extends State<HomeScreen> {
historyItems: historyItems,
sessionStore: widget.sessionStore,
userId: widget.account,
divinationApi: widget.divinationApi,
onDivinationCompleted: widget.onDivinationCompleted,
onDeleteHistorySession: widget.onDeleteHistorySession,
allowVibration: widget.profileSettings.notification.allowVibration,
),
_ProfileTab(
@@ -138,15 +148,19 @@ class _HomeTab extends StatelessWidget {
required this.historyItems,
required this.sessionStore,
required this.userId,
required this.divinationApi,
required this.onDivinationCompleted,
required this.onDeleteHistorySession,
required this.allowVibration,
});
final List<DivinationResultData> historyItems;
final SessionStore sessionStore;
final String userId;
final DivinationApi divinationApi;
final Future<void> Function(DivinationResultData result)
onDivinationCompleted;
final Future<void> Function(String threadId) onDeleteHistorySession;
final bool allowVibration;
@override
@@ -253,9 +267,29 @@ class _HomeTab extends StatelessWidget {
const SizedBox(height: AppSpacing.xl),
Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg),
child: Text(
l10n.historyTitle,
style: Theme.of(context).textTheme.titleMedium,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
l10n.historyTitle,
style: Theme.of(context).textTheme.titleMedium,
),
if (historyItems.length > 4)
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => DivinationHistoryScreen(
initialItems: historyItems,
divinationApi: divinationApi,
onDeleteHistorySession: onDeleteHistorySession,
),
),
);
},
child: Text(l10n.more),
),
],
),
),
const SizedBox(height: AppSpacing.md),
@@ -279,22 +313,66 @@ class _HomeTab extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: historyItems.take(4).map((item) {
final threadId = item.threadId;
return Padding(
padding: const EdgeInsets.only(
left: AppSpacing.md,
right: AppSpacing.md,
bottom: AppSpacing.md,
),
child: _HistoryCard(
item: item,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => DivinationResultScreen(data: item),
child: threadId == null
? _HistoryCard(
item: item,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => DivinationResultScreen(
data: item,
divinationApi: null,
enableIntroTransition: false,
),
),
);
},
)
: Dismissible(
key: ValueKey<String>('home-history-$threadId'),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
),
decoration: BoxDecoration(
color: colors.errorContainer,
borderRadius: BorderRadius.circular(
AppRadius.md,
),
),
child: Icon(
Icons.delete_outline,
color: colors.onErrorContainer,
),
),
confirmDismiss: (_) async => true,
onDismissed: (_) {
unawaited(onDeleteHistorySession(threadId));
},
child: _HistoryCard(
item: item,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => DivinationResultScreen(
data: item,
divinationApi: divinationApi,
enableIntroTransition: false,
),
),
);
},
),
),
);
},
),
);
}).toList(),
),
@@ -305,6 +383,118 @@ class _HomeTab extends StatelessWidget {
}
}
class DivinationHistoryScreen extends StatefulWidget {
const DivinationHistoryScreen({
super.key,
required this.initialItems,
required this.divinationApi,
required this.onDeleteHistorySession,
});
final List<DivinationResultData> initialItems;
final DivinationApi divinationApi;
final Future<void> Function(String threadId) onDeleteHistorySession;
@override
State<DivinationHistoryScreen> createState() =>
_DivinationHistoryScreenState();
}
class _DivinationHistoryScreenState extends State<DivinationHistoryScreen> {
late List<DivinationResultData> _items;
@override
void initState() {
super.initState();
_items = List<DivinationResultData>.from(
widget.initialItems,
growable: true,
);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final colors = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(title: Text(l10n.historyTitle)),
backgroundColor: colors.surfaceContainerLow,
body: _items.isEmpty
? Center(child: Text(l10n.noRecords))
: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: AppSpacing.md),
itemCount: _items.length,
itemBuilder: (context, index) {
final item = _items[index];
final threadId = item.threadId;
return Padding(
padding: const EdgeInsets.only(
left: AppSpacing.md,
right: AppSpacing.md,
bottom: AppSpacing.md,
),
child: threadId == null
? _HistoryCard(
item: item,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => DivinationResultScreen(
data: item,
divinationApi: null,
enableIntroTransition: false,
),
),
);
},
)
: Dismissible(
key: ValueKey<String>('history-$threadId'),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
),
decoration: BoxDecoration(
color: colors.errorContainer,
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Icon(
Icons.delete_outline,
color: colors.onErrorContainer,
),
),
confirmDismiss: (_) async => true,
onDismissed: (_) {
setState(() {
_items.removeAt(index);
});
unawaited(widget.onDeleteHistorySession(threadId));
},
child: _HistoryCard(
item: item,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => DivinationResultScreen(
data: item,
divinationApi: widget.divinationApi,
enableIntroTransition: false,
),
),
);
},
),
),
);
},
),
);
}
}
class _ProfileTab extends StatelessWidget {
const _ProfileTab({
required this.account,
@@ -355,9 +545,15 @@ class _HistoryCard extends StatelessWidget {
final palette = Theme.of(context).extension<AppColorPalette>()!;
final categoryLabel = switch (item.params.questionType) {
QuestionType.career || QuestionType.study => l10n.categoryCareer,
QuestionType.love => l10n.categoryLove,
_ => l10n.categoryMoney,
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,
};
final categoryStyle = switch (item.params.questionType) {
@@ -390,177 +586,39 @@ class _HistoryCard extends StatelessWidget {
? (colors.errorContainer, colors.onErrorContainer)
: (palette.historyGrayBg, palette.historyGrayText);
return Material(
color: colors.surface.withValues(alpha: 0),
child: InkWell(
borderRadius: BorderRadius.circular(AppRadius.md),
return SizedBox(
width: double.infinity,
child: DivinationSummaryCard(
question: item.params.question,
onTap: onTap,
child: 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(
item.params.question,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: AppSpacing.sm),
Wrap(
spacing: AppSpacing.sm,
runSpacing: AppSpacing.sm,
children: [
_Tag(
label: categoryLabel,
background: categoryStyle.$1,
foreground: categoryStyle.$2,
),
_Tag(
label: item.guaName,
background: palette.historyBlueBg,
foreground: palette.historyBlueText,
),
_Tag(
label: signLabel,
background: signStyle.$1,
foreground: signStyle.$2,
),
],
),
],
),
),
leading: Icon(
Icons.auto_awesome,
color: palette.historyBlueText,
size: 22,
),
leadingBackgroundColor: palette.historyBlueBg,
tags: [
DivinationSummaryTagData(
label: categoryLabel,
background: categoryStyle.$1,
foreground: categoryStyle.$2,
),
DivinationSummaryTagData(
label: item.guaName,
background: palette.historyBlueBg,
foreground: palette.historyBlueText,
),
DivinationSummaryTagData(
label: signLabel,
background: signStyle.$1,
foreground: signStyle.$2,
),
],
),
);
}
}
class _Tag extends StatelessWidget {
const _Tag({
required this.label,
required this.background,
required this.foreground,
});
final String label;
final Color background;
final Color foreground;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.sm,
vertical: AppSpacing.xs,
),
decoration: BoxDecoration(
color: background,
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
label,
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: foreground),
),
);
}
}
class _HistoryRecordsScreen extends StatelessWidget {
const _HistoryRecordsScreen({
required this.records,
required this.onOpenResult,
});
final List<DivinationResultData> records;
final ValueChanged<DivinationResultData> onOpenResult;
String _itemKey(DivinationResultData item) {
return '${item.guaName}_${item.binaryCode}_${item.params.question}';
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final colors = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: colors.surfaceContainerLow,
appBar: AppBar(
title: Text(l10n.historyTitle),
centerTitle: true,
backgroundColor: colors.surfaceContainerLow,
surfaceTintColor: colors.surfaceContainerLow,
),
body: records.isEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
l10n.noRecords,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: AppSpacing.sm),
Text(l10n.noRecordsSubtitle),
],
),
)
: ListView.separated(
padding: EdgeInsets.only(
top: AppSpacing.md,
bottom: AppSpacing.md,
),
itemBuilder: (context, index) {
final item = records[index];
return Dismissible(
key: ValueKey(_itemKey(item)),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: AppSpacing.lg),
decoration: BoxDecoration(
color: colors.error,
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Icon(
Icons.delete_outline_rounded,
color: colors.onError,
),
),
confirmDismiss: (direction) async {
return true;
},
onDismissed: (direction) {
// TODO: implement delete
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
),
child: _HistoryCard(
item: item,
onTap: () => onOpenResult(item),
),
),
);
},
separatorBuilder: (_, _) => const SizedBox(height: AppSpacing.md),
itemCount: records.length,
),
);
}
}
class _WelcomeDialog extends StatefulWidget {
const _WelcomeDialog({required this.onDone});