feat: 新增站内消息系统,支持通知与订单跳转
This commit is contained in:
1 parent
411ec8be23
commit
c799b62f6b
14 files changed
+464
-9
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/network/api_response.dart';
|
||||
import '../../../core/notifications/local_notifications.dart';
|
||||
import '../../../core/providers.dart';
|
||||
import '../../auth/application/session_controller.dart';
|
||||
import 'notice_routes.dart';
|
||||
|
||||
/// 轮询未读站内消息,并在 App 不处于前台时弹出系统通知。
|
||||
///
|
||||
/// 登录后每 30s 拉一次未读列表;已提醒过的最大消息 id 持久化,
|
||||
/// 重启后不会重复提醒历史消息。
|
||||
final noticeAlertProvider =
|
||||
NotifierProvider<NoticeAlertController, int>(NoticeAlertController.new);
|
||||
|
||||
class NoticeAlertController extends Notifier<int> {
|
||||
static const _interval = Duration(seconds: 30);
|
||||
|
||||
Timer? _timer;
|
||||
bool _busy = false;
|
||||
|
||||
@override
|
||||
int build() {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
|
||||
final loggedIn = ref.watch(sessionProvider.select((s) => s.isLoggedIn));
|
||||
if (!loggedIn) return 0;
|
||||
|
||||
ref.onDispose(() {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
});
|
||||
_timer = Timer.periodic(_interval, (_) => unawaited(_poll()));
|
||||
unawaited(_poll());
|
||||
return 0;
|
||||
}
|
||||
|
||||
Future<void> _poll() async {
|
||||
if (_busy) return;
|
||||
_busy = true;
|
||||
try {
|
||||
final res = await ref
|
||||
.read(noticeApiProvider)
|
||||
.list(pageSize: 20, isRead: false);
|
||||
if (!res.isOk || res.data == null || res.data!.list.isEmpty) return;
|
||||
|
||||
final notices = res.data!.list;
|
||||
final latest = notices.fold<int>(0, (m, n) => n.id > m ? n.id : m);
|
||||
final storage = ref.read(tokenStorageProvider);
|
||||
|
||||
// 首次运行只记录基线,避免把历史未读一次性弹成通知
|
||||
final maxId = storage.noticeAlertMaxId;
|
||||
if (maxId < 0) {
|
||||
await storage.setNoticeAlertMaxId(latest);
|
||||
return;
|
||||
}
|
||||
|
||||
final fresh = notices.where((n) => n.id > maxId).toList()
|
||||
..sort((a, b) => a.id.compareTo(b.id));
|
||||
if (fresh.isEmpty) return;
|
||||
|
||||
// 前台时只更新基线(界面内已有红点/消息中心),退到后台才弹通知
|
||||
final inBackground =
|
||||
WidgetsBinding.instance.lifecycleState != AppLifecycleState.resumed;
|
||||
if (inBackground) {
|
||||
for (final notice in fresh) {
|
||||
await LocalNotifications.instance.show(
|
||||
id: notice.id,
|
||||
title: notice.title,
|
||||
body: notice.content,
|
||||
route: noticeRoute(notice) ?? '/notice',
|
||||
);
|
||||
}
|
||||
}
|
||||
await storage.setNoticeAlertMaxId(latest);
|
||||
} on ApiException {
|
||||
// 网络波动忽略,下一轮再试
|
||||
} finally {
|
||||
_busy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import '../data/notice_api.dart';
|
||||
|
||||
/// 消息可跳转的订单路由;无法识别业务类型时返回 null。
|
||||
///
|
||||
/// 与后端通知投递的 `bizType` 约定一致:
|
||||
/// `survey_order` / `repair_dispatch` / `install_order` / `maintenance_order`。
|
||||
String? noticeRoute(Notice notice) {
|
||||
final id = notice.bizId;
|
||||
if (id == null || id <= 0) return null;
|
||||
final source = switch (notice.bizType) {
|
||||
'survey_order' => 'survey',
|
||||
'repair_dispatch' || 'repair_order' => 'repair',
|
||||
'install_order' => 'install',
|
||||
'maintenance_order' => 'maintenance',
|
||||
_ => null,
|
||||
};
|
||||
return source == null ? null : '/order/$source/$id';
|
||||
}
|
||||
Reference in new issue
Block a user