Skip to main content

Notifications

Bonus B-02 — event-triggered local/browser notification on chargeRaised, plus in-app feed and badge.

Kinds

  • Local nativeflutter_local_notifications (Android/iOS/macOS), triggered by NotificationsCubit.onChargeCreated(Stream<StoredEvent>).
  • Browser — Web Notifications API via dart:js_interop + web package; same trigger, requires permission grant.
  • In-app feed/notifications renders AppNotification { type, titleKey, bodyKey, chargeId?, createdAt, read } with unread filter and mark-read.

Permission

No background push — foreground only. NotificationsCubit.requestPermission() is called from a user gesture (Maintenance Hub chargeRaised banner or settings). Fallback: in-app banner when permission denied.

Data

Backend seeds notifications.json + emits notification.updated SSE on creation. Flutter subscribes via LiveSyncServiceLocalStore.notificationsStreamNotificationsCubit (badge count for KxNavShell "Notifications" badge). Mark read is POST /notifications/{id}/read with expectedStateEpoch (naturally idempotent, no Idempotency-Key).

UX

  • KxNavShell badge shows 2 unread with badgeSemanticLabel; reading in feed decrements live.
  • Each notification title/body via ARB titleKey/bodyKey, never inspector prose (that is never translated).

Tests

test/notifications/* asserts adapter permission, badge decrement, mark-read idempotent replay.

Limitations

  • Not FCM/APNs/Web Push background; app must be foreground or recently backgrounded.
  • flutter_local_notifications permission on Android 13+ is explicitly requested — denied state is recorded and retried on next chargeRaised.

See Offline & Live Sync for the SSE pipeline that delivers the trigger.