feat(social): add SavedSearchAlertService + notification alerts channel
One discovery subscription over the shared connection for the user's current zone; matches each incoming offer against saved searches and fires a text-free OS alert on the new 'alerts' channel for fresh matches. Skips own/blocked/hidden/non-active offers and dedups via seen keys. The replay-on-connect doubles as the catch-up scan. Notification payloads are now prefixed so taps route to chat or the saved search.
This commit is contained in:
parent
530bf96358
commit
c0cd299408
3 changed files with 341 additions and 1 deletions
|
|
@ -25,7 +25,16 @@ class NotificationService {
|
|||
/// app wires this to `router.push('/chat/<pubkey>')` once the router exists.
|
||||
void Function(String peerPubkey)? onTapChat;
|
||||
|
||||
/// Called with a saved-search id when the user taps a search-alert
|
||||
/// notification. The app wires this to open the saved-searches list.
|
||||
void Function(String searchId)? onTapSearch;
|
||||
|
||||
static const _channelId = 'messages';
|
||||
static const _alertsChannelId = 'alerts';
|
||||
|
||||
/// Tap payloads are prefixed so one handler can route by kind. A bare payload
|
||||
/// (no prefix) stays a chat peer pubkey, for backward compatibility.
|
||||
static const _searchPayloadPrefix = 'search:';
|
||||
|
||||
static bool get _platformSupported {
|
||||
if (kIsWeb) return false;
|
||||
|
|
@ -57,7 +66,12 @@ class NotificationService {
|
|||
),
|
||||
onDidReceiveNotificationResponse: (response) {
|
||||
final payload = response.payload;
|
||||
if (payload != null && payload.isNotEmpty) onTapChat?.call(payload);
|
||||
if (payload == null || payload.isEmpty) return;
|
||||
if (payload.startsWith(_searchPayloadPrefix)) {
|
||||
onTapSearch?.call(payload.substring(_searchPayloadPrefix.length));
|
||||
} else {
|
||||
onTapChat?.call(payload);
|
||||
}
|
||||
},
|
||||
);
|
||||
await _plugin
|
||||
|
|
@ -100,4 +114,35 @@ class NotificationService {
|
|||
await _plugin.show(peerPubkey.hashCode, title, null, details,
|
||||
payload: peerPubkey);
|
||||
}
|
||||
|
||||
/// Shows a notification for a new offer matching a saved search. [title] is a
|
||||
/// generic line built by the caller (e.g. "New match: Tomatoes"); [searchId]
|
||||
/// rides along so a tap opens that search. No-op on unsupported platforms.
|
||||
Future<void> showSearchAlert({
|
||||
required String searchId,
|
||||
required String title,
|
||||
}) async {
|
||||
if (!_supported) return;
|
||||
const details = NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
_alertsChannelId,
|
||||
'Search alerts',
|
||||
channelDescription: 'New offers matching your saved searches',
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
),
|
||||
iOS: DarwinNotificationDetails(),
|
||||
macOS: DarwinNotificationDetails(),
|
||||
linux: LinuxNotificationDetails(),
|
||||
);
|
||||
// One notification per search (same id replaces the previous), so repeated
|
||||
// matches for one search don't stack up.
|
||||
await _plugin.show(
|
||||
_searchPayloadPrefix.hashCode ^ searchId.hashCode,
|
||||
title,
|
||||
null,
|
||||
details,
|
||||
payload: '$_searchPayloadPrefix$searchId',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue