docs: 更新协议文档,删除废弃计划文档
- 更新 http-error-codes, user-points-chat-data-protocol - 更新 divination-run-protocol, profile-protocol - 删除废弃的后端和前端设计计划文档
This commit is contained in:
@@ -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});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user