feat: 实现站内通知系统
- 后端: 新增 notifications/user_notifications 表迁移及 ORM 模型
- 后端: 实现 schema/repository/service/router 全套通知 API
- GET /api/v1/notifications (列表+游标分页)
- GET /api/v1/notifications/unread-count
- PATCH /api/v1/notifications/{id}/read (幂等)
- PATCH /api/v1/notifications/mark-all-read (幂等)
- 后端: payload 使用 Pydantic discriminated union (none/open_route/open_url)
- 后端: 19 个单元测试全部通过
- Flutter: 通知 feature 完整实现 (models/apis/repositories/bloc/UI)
- Flutter: Home 页通知按钮接入真实页面,显示未读 badge
- Flutter: 14 个测试全部通过
- 协议文档: notification-inbox-protocol.md 及错误码注册
This commit is contained in:
@@ -8,6 +8,9 @@ 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 '../../../notifications/data/repositories/notification_repository.dart';
|
||||
import '../../../notifications/presentation/bloc/notification_bloc.dart';
|
||||
import '../../../notifications/presentation/screens/notification_center_screen.dart';
|
||||
import '../../../settings/data/models/profile_settings.dart';
|
||||
import '../../../settings/presentation/screens/settings_screen.dart';
|
||||
import '../../../../l10n/app_localizations.dart';
|
||||
@@ -15,8 +18,6 @@ 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';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({
|
||||
@@ -28,6 +29,8 @@ class HomeScreen extends StatefulWidget {
|
||||
required this.historyRecords,
|
||||
required this.coinBalance,
|
||||
required this.divinationApi,
|
||||
required this.notificationBloc,
|
||||
required this.notificationRepository,
|
||||
required this.onLocaleChanged,
|
||||
required this.onProfileSettingsChanged,
|
||||
required this.onSaveProfile,
|
||||
@@ -45,6 +48,8 @@ class HomeScreen extends StatefulWidget {
|
||||
final List<DivinationResultData> historyRecords;
|
||||
final int coinBalance;
|
||||
final DivinationApi divinationApi;
|
||||
final NotificationBloc notificationBloc;
|
||||
final NotificationRepository notificationRepository;
|
||||
final Future<void> Function(String languageTag) onLocaleChanged;
|
||||
final Future<void> Function(ProfileSettingsV1 settings)
|
||||
onProfileSettingsChanged;
|
||||
@@ -108,6 +113,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
onDivinationCompleted: widget.onDivinationCompleted,
|
||||
onDeleteHistorySession: widget.onDeleteHistorySession,
|
||||
allowVibration: widget.profileSettings.notification.allowVibration,
|
||||
notificationBloc: widget.notificationBloc,
|
||||
notificationRepository: widget.notificationRepository,
|
||||
),
|
||||
_ProfileTab(
|
||||
account: widget.account,
|
||||
@@ -155,6 +162,8 @@ class _HomeTab extends StatelessWidget {
|
||||
required this.onDivinationCompleted,
|
||||
required this.onDeleteHistorySession,
|
||||
required this.allowVibration,
|
||||
required this.notificationBloc,
|
||||
required this.notificationRepository,
|
||||
});
|
||||
|
||||
final List<DivinationResultData> historyItems;
|
||||
@@ -165,6 +174,8 @@ class _HomeTab extends StatelessWidget {
|
||||
onDivinationCompleted;
|
||||
final Future<void> Function(String threadId) onDeleteHistorySession;
|
||||
final bool allowVibration;
|
||||
final NotificationBloc notificationBloc;
|
||||
final NotificationRepository notificationRepository;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -194,16 +205,34 @@ class _HomeTab extends StatelessWidget {
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Toast.show(
|
||||
context,
|
||||
l10n.featurePending,
|
||||
type: ToastType.info,
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => NotificationCenterScreen(
|
||||
repository: notificationRepository,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.notifications,
|
||||
color: colors.primary,
|
||||
size: 28,
|
||||
icon: ListenableBuilder(
|
||||
listenable: notificationBloc,
|
||||
builder: (context, _) {
|
||||
final count = notificationBloc.state.unreadCount;
|
||||
if (count > 0) {
|
||||
return Badge(
|
||||
label: Text(count > 99 ? '99+' : '$count'),
|
||||
child: Icon(
|
||||
Icons.notifications,
|
||||
color: colors.primary,
|
||||
size: 28,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Icon(
|
||||
Icons.notifications,
|
||||
color: colors.primary,
|
||||
size: 28,
|
||||
);
|
||||
},
|
||||
),
|
||||
tooltip: l10n.notify,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user