diff --git a/lib/features/settings/providers/announcements_provider.dart b/lib/features/settings/providers/announcements_provider.dart new file mode 100644 index 000000000..b44f874f2 --- /dev/null +++ b/lib/features/settings/providers/announcements_provider.dart @@ -0,0 +1,100 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:mostro/src/rust/api/announcements.dart' as announcements_api; + +/// The Announcements toggle (specs/006-announcement-channel §7). +/// +/// Unlike its four neighbours, which are `shared_preferences` keys, this one +/// lives in the Rust settings table: the value decides whether Rust opens a +/// relay subscription, and the authority for that is on the side that opens +/// it. Off means the subscription is closed at the tap, not that arriving +/// notices are hidden. + +/// The Rust side of the toggle. Injected so screen tests never reach the +/// bridge. +class AnnouncementsBridge { + const AnnouncementsBridge(); + + Future enabled() => announcements_api.getAnnouncementsEnabled(); + + Future setEnabled(bool enabled) => + announcements_api.setAnnouncementsEnabled(enabled: enabled); +} + +final announcementsBridgeProvider = Provider( + (ref) => const AnnouncementsBridge(), +); + +class AnnouncementsEnabledNotifier extends StateNotifier { + AnnouncementsEnabledNotifier({required AnnouncementsBridge bridge}) + : _bridge = bridge, + // On until Rust says otherwise: the persisted default (§7). + super(true) { + _load(); + } + + final AnnouncementsBridge _bridge; + + /// Taps so far. A load that answers after one read the setting before its + /// write, so its value is older than the screen's; a failed write reloads + /// only when no newer tap has replaced it. + int _taps = 0; + + /// Writes go one after another: two quick taps otherwise race to the + /// store, and the older one can land last. + Future _writes = Future.value(); + + Future _load() async { + try { + final enabled = await _bridge.enabled(); + if (mounted && _taps == 0) state = enabled; + } catch (e) { + debugPrint('[announcements] reading the toggle failed: $e'); + } + } + + /// Applies the change to the UI first and persists after, so the toggle + /// answers the tap; a failed write is reported by returning false and the + /// toggle going back to what Rust holds. + Future set(bool value) { + final tap = ++_taps; + final before = state; + state = value; + final write = _writes.then((_) => _write(value, tap, before)); + _writes = write.then((_) {}); + return write; + } + + Future _write(bool value, int tap, bool before) async { + try { + await _bridge.setEnabled(value); + return true; + } catch (e) { + debugPrint('[announcements] saving the toggle failed: $e'); + if (mounted && tap == _taps) await _reload(tap, before); + return false; + } + } + + /// After a refused write the screen shows a value Rust never took. What it + /// showed before the tap may be stale too — the load can still be in + /// flight — so Rust is asked again; a tap that lands meanwhile wins. + Future _reload(int tap, bool before) async { + bool enabled; + try { + enabled = await _bridge.enabled(); + } catch (e) { + debugPrint('[announcements] re-reading the toggle failed: $e'); + enabled = before; + } + if (mounted && tap == _taps) state = enabled; + } +} + +final announcementsEnabledProvider = + StateNotifierProvider( + (ref) => AnnouncementsEnabledNotifier( + bridge: ref.watch(announcementsBridgeProvider), + ), + ); diff --git a/lib/features/settings/screens/notification_settings_screen.dart b/lib/features/settings/screens/notification_settings_screen.dart index 3458cabef..b15d5b3ac 100644 --- a/lib/features/settings/screens/notification_settings_screen.dart +++ b/lib/features/settings/screens/notification_settings_screen.dart @@ -7,6 +7,7 @@ import 'package:mostro/core/app_routes.dart'; import 'package:mostro/core/order_book_palette.dart'; import 'package:mostro/core/settings_palette.dart'; import 'package:mostro/features/settings/models/push_status_line.dart'; +import 'package:mostro/features/settings/providers/announcements_provider.dart'; import 'package:mostro/features/settings/providers/notification_permission_provider.dart'; import 'package:mostro/features/settings/providers/notification_prefs_provider.dart'; import 'package:mostro/features/settings/providers/push_settings_provider.dart'; @@ -28,6 +29,12 @@ import 'package:mostro/shared/widgets/redesign_app_bar.dart'; /// /// The event rows gate the in-app cards, not the push: a push carries no /// type to filter on (docs/PUSH_NOTIFICATIONS.md §9.1). +/// +/// A third card holds the Announcements toggle (specs/006 §7): the project's +/// signed notices, read from the relays. Off closes that subscription at the +/// tap, which is why its value lives in Rust rather than in `shared_preferences` +/// like the four rows above it — and why the OS permission does not gate it: +/// an announcement posts no system notification. class NotificationSettingsScreen extends ConsumerWidget { const NotificationSettingsScreen({super.key}); @@ -125,6 +132,19 @@ class NotificationSettingsScreen extends ConsumerWidget { ), ), const SizedBox(height: settingsGroupGap), + Padding( + padding: const EdgeInsets.fromLTRB(2, 0, 2, 14), + child: Text( + l10n.announcementsGroupSubtitle, + style: TextStyle( + fontSize: 12, + height: 1.5, + color: book.textSecondary, + ), + ), + ), + const SettingsGroup(rows: [_AnnouncementsRow()]), + const SizedBox(height: settingsGroupGap), SettingsFootnote( icon: Icons.lock_outline, // Relevant in a privacy app: pushes travel through Google/Apple. @@ -188,7 +208,7 @@ class _PushMasterRowState extends ConsumerState<_PushMasterRow> { value ? Icons.notifications_active_outlined : Icons.notifications_off_outlined, - size: 17, + size: 16, color: value ? book.limeIcon : book.textTertiary, ), const SizedBox(width: 12), @@ -320,7 +340,7 @@ class _UnsupportedPlatformRow extends StatelessWidget { children: [ Icon( Icons.notifications_off_outlined, - size: 17, + size: 16, color: book.textTertiary, ), const SizedBox(width: 12), @@ -355,12 +375,99 @@ class _EventRow extends ConsumerWidget { final bool value; final bool enabled; + @override + Widget build(BuildContext context, WidgetRef ref) { + final (title, description) = _copy(AppLocalizations.of(context), event); + return _ToggleRow( + icon: icon, + title: title, + description: description, + value: value, + onChanged: enabled ? (next) => _set(context, ref, next) : null, + ); + } + + Future _set(BuildContext context, WidgetRef ref, bool next) async { + final ok = await ref + .read(notificationPrefsProvider.notifier) + .setEvent(event, next); + if (!ok && context.mounted) _saveFailed(context); + } + + static (String, String) _copy( + AppLocalizations l10n, + NotificationEvent event, + ) => switch (event) { + NotificationEvent.tradeUpdates => ( + l10n.notifTradeUpdatesTitle, + l10n.notifTradeUpdatesSubtitle, + ), + NotificationEvent.newMessages => ( + l10n.notifNewMessagesTitle, + l10n.notifNewMessagesSubtitle, + ), + NotificationEvent.paymentAlerts => ( + l10n.notifPaymentAlertsTitle, + l10n.notifPaymentAlertsSubtitle, + ), + NotificationEvent.disputeUpdates => ( + l10n.notifDisputeUpdatesTitle, + l10n.notifDisputeUpdatesSubtitle, + ), + }; +} + +/// The Announcements toggle (specs/006 §7), persisted by Rust: off closes +/// the relay subscription at the tap. Never gated by the OS permission. +class _AnnouncementsRow extends ConsumerWidget { + const _AnnouncementsRow(); + @override Widget build(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context); - final book = OrderBookPalette.of(context); - final (title, description) = _copy(l10n, event); + return _ToggleRow( + icon: Icons.campaign_outlined, + title: l10n.notifAnnouncementsTitle, + description: l10n.notifAnnouncementsSubtitle, + value: ref.watch(announcementsEnabledProvider), + onChanged: (next) => _set(context, ref, next), + ); + } + Future _set(BuildContext context, WidgetRef ref, bool next) async { + final ok = await ref.read(announcementsEnabledProvider.notifier).set(next); + if (!ok && context.mounted) _saveFailed(context); + } +} + +void _saveFailed(BuildContext context) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context).notificationPrefSaveFailed), + ), + ); +} + +/// One row of a settings group: a glyph, a 13/600 title over an 11/400 +/// description, and a toggle. [onChanged] null disables the toggle. +class _ToggleRow extends StatelessWidget { + const _ToggleRow({ + required this.icon, + required this.title, + required this.description, + required this.value, + required this.onChanged, + }); + + final IconData icon; + final String title; + final String description; + final bool value; + final ValueChanged? onChanged; + + @override + Widget build(BuildContext context) { + final book = OrderBookPalette.of(context); return Padding( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), child: Row( @@ -370,7 +477,7 @@ class _EventRow extends ConsumerWidget { children: [ Icon( icon, - size: 17, + size: 16, color: value ? book.limeIcon : book.textTertiary, ), const SizedBox(width: 12), @@ -402,49 +509,12 @@ class _EventRow extends ConsumerWidget { MostroToggle( value: value, semanticLabel: title, - onChanged: enabled ? (next) => _set(context, ref, next) : null, + onChanged: onChanged, ), ], ), ); } - - Future _set(BuildContext context, WidgetRef ref, bool next) async { - final ok = await ref - .read(notificationPrefsProvider.notifier) - .setEvent(event, next); - if (!ok && context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - AppLocalizations.of(context).notificationPrefSaveFailed, - ), - ), - ); - } - } - - static (String, String) _copy( - AppLocalizations l10n, - NotificationEvent event, - ) => switch (event) { - NotificationEvent.tradeUpdates => ( - l10n.notifTradeUpdatesTitle, - l10n.notifTradeUpdatesSubtitle, - ), - NotificationEvent.newMessages => ( - l10n.notifNewMessagesTitle, - l10n.notifNewMessagesSubtitle, - ), - NotificationEvent.paymentAlerts => ( - l10n.notifPaymentAlertsTitle, - l10n.notifPaymentAlertsSubtitle, - ), - NotificationEvent.disputeUpdates => ( - l10n.notifDisputeUpdatesTitle, - l10n.notifDisputeUpdatesSubtitle, - ), - }; } // ── Permission banners ──────────────────────────────────────────────────────── diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 8a9144236..d272d0d52 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -394,6 +394,9 @@ "notifPaymentAlertsSubtitle": "Bestätigungen und Fehler von Lightning-Zahlungen", "notifDisputeUpdatesTitle": "Streitfall-Updates", "notifDisputeUpdatesSubtitle": "Schlichter-Aktionen und Streitbeilegungen", + "notifAnnouncementsTitle": "Ankündigungen", + "notifAnnouncementsSubtitle": "Versionen, Ausfälle und Relay-Änderungen, signiert vom Mostro-Projekt", + "announcementsGroupSubtitle": "Ankündigungen werden von den Relays gelesen und nie beantwortet: Nichts über dich wird gesendet.", "searchCurrenciesHint": "Währungen suchen…", "noCurrenciesFoundMessage": "Keine Währungen gefunden", "shareLogsTooltip": "Protokolle teilen", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 44a688a11..be48dc4e7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1714,6 +1714,18 @@ "@notifDisputeUpdatesSubtitle": { "description": "Subtitle of the dispute updates notification toggle" }, + "notifAnnouncementsTitle": "Announcements", + "@notifAnnouncementsTitle": { + "description": "Title of the Announcements toggle in notification settings (specs/006)" + }, + "notifAnnouncementsSubtitle": "Releases, outages and relay changes, signed by the Mostro project", + "@notifAnnouncementsSubtitle": { + "description": "Subtitle of the Announcements toggle" + }, + "announcementsGroupSubtitle": "Announcements are read from the relays and never answered: nothing about you is sent.", + "@announcementsGroupSubtitle": { + "description": "Line above the Announcements toggle: the channel is one-way, nothing is reported back" + }, "searchCurrenciesHint": "Search currencies…", "@searchCurrenciesHint": { "description": "Hint text of the currency search field" diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index b94e44295..9e07ab136 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -394,6 +394,9 @@ "notifPaymentAlertsSubtitle": "Confirmaciones y fallos de pagos Lightning", "notifDisputeUpdatesTitle": "Actualizaciones de disputas", "notifDisputeUpdatesSubtitle": "Acciones de los resolutores y resoluciones de disputas", + "notifAnnouncementsTitle": "Anuncios", + "notifAnnouncementsSubtitle": "Versiones, caídas y cambios de relays, firmados por el proyecto Mostro", + "announcementsGroupSubtitle": "Los anuncios se leen de los relays y nunca se responden: no se envía nada sobre ti.", "searchCurrenciesHint": "Buscar monedas…", "noCurrenciesFoundMessage": "No se encontraron monedas", "shareLogsTooltip": "Compartir registros", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index c1563bfcc..42d7acb02 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -394,6 +394,9 @@ "notifPaymentAlertsSubtitle": "Confirmations et échecs de paiements Lightning", "notifDisputeUpdatesTitle": "Mises à jour des litiges", "notifDisputeUpdatesSubtitle": "Actions des médiateurs et résolutions de litiges", + "notifAnnouncementsTitle": "Annonces", + "notifAnnouncementsSubtitle": "Versions, pannes et changements de relais, signés par le projet Mostro", + "announcementsGroupSubtitle": "Les annonces sont lues depuis les relais et jamais répondues : rien vous concernant n'est envoyé.", "searchCurrenciesHint": "Rechercher des devises…", "noCurrenciesFoundMessage": "Aucune devise trouvée", "shareLogsTooltip": "Partager les journaux", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 07a4c712d..4fa08cdc8 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -394,6 +394,9 @@ "notifPaymentAlertsSubtitle": "Conferme ed errori dei pagamenti Lightning", "notifDisputeUpdatesTitle": "Aggiornamenti dispute", "notifDisputeUpdatesSubtitle": "Azioni dei mediatori e risoluzioni delle dispute", + "notifAnnouncementsTitle": "Annunci", + "notifAnnouncementsSubtitle": "Versioni, interruzioni e cambi di relay, firmati dal progetto Mostro", + "announcementsGroupSubtitle": "Gli annunci vengono letti dai relay e mai risposti: non viene inviato nulla su di te.", "searchCurrenciesHint": "Cerca valute…", "noCurrenciesFoundMessage": "Nessuna valuta trovata", "shareLogsTooltip": "Condividi i log", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 8a3ef2312..c78089e8c 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2466,6 +2466,24 @@ abstract class AppLocalizations { /// **'Resolver actions and dispute resolutions'** String get notifDisputeUpdatesSubtitle; + /// Title of the Announcements toggle in notification settings (specs/006) + /// + /// In en, this message translates to: + /// **'Announcements'** + String get notifAnnouncementsTitle; + + /// Subtitle of the Announcements toggle + /// + /// In en, this message translates to: + /// **'Releases, outages and relay changes, signed by the Mostro project'** + String get notifAnnouncementsSubtitle; + + /// Line above the Announcements toggle: the channel is one-way, nothing is reported back + /// + /// In en, this message translates to: + /// **'Announcements are read from the relays and never answered: nothing about you is sent.'** + String get announcementsGroupSubtitle; + /// Hint text of the currency search field /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 2d6276849..b4477185b 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1379,6 +1379,17 @@ class AppLocalizationsDe extends AppLocalizations { String get notifDisputeUpdatesSubtitle => 'Schlichter-Aktionen und Streitbeilegungen'; + @override + String get notifAnnouncementsTitle => 'Ankündigungen'; + + @override + String get notifAnnouncementsSubtitle => + 'Versionen, Ausfälle und Relay-Änderungen, signiert vom Mostro-Projekt'; + + @override + String get announcementsGroupSubtitle => + 'Ankündigungen werden von den Relays gelesen und nie beantwortet: Nichts über dich wird gesendet.'; + @override String get searchCurrenciesHint => 'Währungen suchen…'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 600195c21..720374a41 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1361,6 +1361,17 @@ class AppLocalizationsEn extends AppLocalizations { String get notifDisputeUpdatesSubtitle => 'Resolver actions and dispute resolutions'; + @override + String get notifAnnouncementsTitle => 'Announcements'; + + @override + String get notifAnnouncementsSubtitle => + 'Releases, outages and relay changes, signed by the Mostro project'; + + @override + String get announcementsGroupSubtitle => + 'Announcements are read from the relays and never answered: nothing about you is sent.'; + @override String get searchCurrenciesHint => 'Search currencies…'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index e5cc998f9..54acfa240 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1374,6 +1374,17 @@ class AppLocalizationsEs extends AppLocalizations { String get notifDisputeUpdatesSubtitle => 'Acciones de los resolutores y resoluciones de disputas'; + @override + String get notifAnnouncementsTitle => 'Anuncios'; + + @override + String get notifAnnouncementsSubtitle => + 'Versiones, caídas y cambios de relays, firmados por el proyecto Mostro'; + + @override + String get announcementsGroupSubtitle => + 'Los anuncios se leen de los relays y nunca se responden: no se envía nada sobre ti.'; + @override String get searchCurrenciesHint => 'Buscar monedas…'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 22415b963..5dcf08d0c 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1376,6 +1376,17 @@ class AppLocalizationsFr extends AppLocalizations { String get notifDisputeUpdatesSubtitle => 'Actions des médiateurs et résolutions de litiges'; + @override + String get notifAnnouncementsTitle => 'Annonces'; + + @override + String get notifAnnouncementsSubtitle => + 'Versions, pannes et changements de relais, signés par le projet Mostro'; + + @override + String get announcementsGroupSubtitle => + 'Les annonces sont lues depuis les relais et jamais répondues : rien vous concernant n\'est envoyé.'; + @override String get searchCurrenciesHint => 'Rechercher des devises…'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 45043ea71..3f2a1f8ca 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1373,6 +1373,17 @@ class AppLocalizationsIt extends AppLocalizations { String get notifDisputeUpdatesSubtitle => 'Azioni dei mediatori e risoluzioni delle dispute'; + @override + String get notifAnnouncementsTitle => 'Annunci'; + + @override + String get notifAnnouncementsSubtitle => + 'Versioni, interruzioni e cambi di relay, firmati dal progetto Mostro'; + + @override + String get announcementsGroupSubtitle => + 'Gli annunci vengono letti dai relay e mai risposti: non viene inviato nulla su di te.'; + @override String get searchCurrenciesHint => 'Cerca valute…'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index c9934f4a1..072b0e156 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1378,6 +1378,17 @@ class AppLocalizationsNl extends AppLocalizations { String get notifDisputeUpdatesSubtitle => 'Acties van de solver en uitkomsten van disputen'; + @override + String get notifAnnouncementsTitle => 'Aankondigingen'; + + @override + String get notifAnnouncementsSubtitle => + 'Versies, storingen en relaywijzigingen, ondertekend door het Mostro-project'; + + @override + String get announcementsGroupSubtitle => + 'Aankondigingen worden van de relays gelezen en nooit beantwoord: er wordt niets over jou verstuurd.'; + @override String get searchCurrenciesHint => 'Valuta zoeken…'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 37f5c92e9..f33c275f5 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1377,6 +1377,17 @@ class AppLocalizationsPt extends AppLocalizations { String get notifDisputeUpdatesSubtitle => 'Ações do mediador e resoluções de disputas'; + @override + String get notifAnnouncementsTitle => 'Anúncios'; + + @override + String get notifAnnouncementsSubtitle => + 'Versões, quedas e mudanças de relays, assinados pelo projeto Mostro'; + + @override + String get announcementsGroupSubtitle => + 'Os anúncios são lidos dos relays e nunca respondidos: nada sobre você é enviado.'; + @override String get searchCurrenciesHint => 'Buscar moedas…'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 5446fba16..d48b2fabb 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -394,6 +394,9 @@ "notifPaymentAlertsSubtitle": "Bevestigde en mislukte Lightning-betalingen", "notifDisputeUpdatesTitle": "Wijzigingen in disputen", "notifDisputeUpdatesSubtitle": "Acties van de solver en uitkomsten van disputen", + "notifAnnouncementsTitle": "Aankondigingen", + "notifAnnouncementsSubtitle": "Versies, storingen en relaywijzigingen, ondertekend door het Mostro-project", + "announcementsGroupSubtitle": "Aankondigingen worden van de relays gelezen en nooit beantwoord: er wordt niets over jou verstuurd.", "searchCurrenciesHint": "Valuta zoeken…", "noCurrenciesFoundMessage": "Geen valuta gevonden", "shareLogsTooltip": "Logboek delen", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 95d2d57ac..3d9c494ab 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -394,6 +394,9 @@ "notifPaymentAlertsSubtitle": "Confirmações e falhas de pagamentos Lightning", "notifDisputeUpdatesTitle": "Atualizações de disputas", "notifDisputeUpdatesSubtitle": "Ações do mediador e resoluções de disputas", + "notifAnnouncementsTitle": "Anúncios", + "notifAnnouncementsSubtitle": "Versões, quedas e mudanças de relays, assinados pelo projeto Mostro", + "announcementsGroupSubtitle": "Os anúncios são lidos dos relays e nunca respondidos: nada sobre você é enviado.", "searchCurrenciesHint": "Buscar moedas…", "noCurrenciesFoundMessage": "Nenhuma moeda encontrada", "shareLogsTooltip": "Compartilhar logs", diff --git a/lib/src/rust/api/announcements.dart b/lib/src/rust/api/announcements.dart new file mode 100644 index 000000000..2acd34816 --- /dev/null +++ b/lib/src/rust/api/announcements.dart @@ -0,0 +1,56 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.11.1. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'types.dart'; + +// These functions are ignored because they are not marked as `pub`: `emit`, `publish_current`, `set_flags`, `to_info`, `tx` + +/// Every announcement the cache holds right now: `critical` first, then +/// newest first (§3.4), dismissed ones included — a dismissed `critical` +/// stays listed and Dart is the one that knows (§3.4). Empty before the +/// database is up. +Future> getAnnouncements() => + RustLib.instance.api.crateApiAnnouncementsGetAnnouncements(); + +/// The Announcements toggle (§7): on unless the user turned it off. +Future getAnnouncementsEnabled() => + RustLib.instance.api.crateApiAnnouncementsGetAnnouncementsEnabled(); + +/// Flip the toggle. Off closes the relay subscription at the tap and +/// discards anything queued behind it; on reopens it, with the same 30-day +/// window a fresh install gets (§7). Persisted first: the subscription +/// follows the setting, never the other way round. +Future setAnnouncementsEnabled({required bool enabled}) => RustLib + .instance + .api + .crateApiAnnouncementsSetAnnouncementsEnabled(enabled: enabled); + +/// The user saw the announcement at `address`. Rust holds the flag because +/// it is Rust that decides whether a later revision re-announces (§5.3). +Future markAnnouncementRead({required String address}) => RustLib + .instance + .api + .crateApiAnnouncementsMarkAnnouncementRead(address: address); + +/// The user swiped the announcement at `address` away. Read as well: a +/// dismissed notice never counts as unread, whichever level keeps it listed. +Future dismissAnnouncement({required String address}) => RustLib + .instance + .api + .crateApiAnnouncementsDismissAnnouncement(address: address); + +/// Subscribe before the first [`get_announcements`], so a snapshot landing +/// in between is not missed. +Future onAnnouncementsChanged() => + RustLib.instance.api.crateApiAnnouncementsOnAnnouncementsChanged(); + +// Rust type: RustOpaqueMoi> +abstract class AnnouncementsStream implements RustOpaqueInterface { + /// The next snapshot; a lag skips to the newest rather than ending the + /// stream, since every snapshot is the whole list. + Future> next(); +} diff --git a/lib/src/rust/api/types.dart b/lib/src/rust/api/types.dart index 14cea2fcd..6a1bc2523 100644 --- a/lib/src/rust/api/types.dart +++ b/lib/src/rust/api/types.dart @@ -10,7 +10,7 @@ part 'types.freezed.dart'; // These functions are ignored because they are not marked as `pub`: `default_expiration_hours`, `default_expiration_seconds` // These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `AppState`, `MostroNodeInfo`, `QueuedMessageStatus`, `TradeHistoryEntry` -// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt` /// The `bond_claims` key for a node / order pair. Future bondClaimKey({ @@ -21,6 +21,116 @@ Future bondClaimKey({ orderId: orderId, ); +/// A project announcement the reader admitted (§5.2), with every locale: Rust +/// does not translate, Dart picks the copy for the locale on screen. +class AnnouncementInfo { + /// `38387::` — the announcement's identity across + /// revisions, and the id of its notification card. + final String address; + + /// `:`: changes with every correction published + /// under the same address, which is what makes it unread again (§5.3). + final String revision; + + /// Unix seconds, the event's `created_at`. + final PlatformInt64 createdAt; + + /// Unix seconds, the NIP-40 `expiration`: the announcement is gone once + /// it passes, online or not (§5.5). + final PlatformInt64 expiration; + final AnnouncementSeverity severity; + + /// Every supported locale, in the app's order. + final List texts; + + /// The one action link, `https` only, as parsed. + final String? url; + final bool read; + + /// Swiped away. A `Critical` stays listed, read, until it expires; the + /// other levels leave the list (§3.4). + final bool dismissed; + + const AnnouncementInfo({ + required this.address, + required this.revision, + required this.createdAt, + required this.expiration, + required this.severity, + required this.texts, + this.url, + required this.read, + required this.dismissed, + }); + + @override + int get hashCode => + address.hashCode ^ + revision.hashCode ^ + createdAt.hashCode ^ + expiration.hashCode ^ + severity.hashCode ^ + texts.hashCode ^ + url.hashCode ^ + read.hashCode ^ + dismissed.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AnnouncementInfo && + runtimeType == other.runtimeType && + address == other.address && + revision == other.revision && + createdAt == other.createdAt && + expiration == other.expiration && + severity == other.severity && + texts == other.texts && + url == other.url && + read == other.read && + dismissed == other.dismissed; +} + +/// How loudly an announcement is painted (specs/006-announcement-channel +/// §3.4). The publisher declares a level, never a colour; Dart maps it onto +/// the palette. A level this build does not know arrives as `Warning`. +enum AnnouncementSeverity { + /// Releases, new features, events. + info, + + /// Outages, a relay being retired, anything with a deadline. + warning, + + /// A security issue the user must act on now. + critical, +} + +/// One locale's copy of an announcement, plain text (§3.2). +class AnnouncementText { + /// `en`, `es`, … — one of the app's locales. + final String locale; + final String title; + final String body; + + const AnnouncementText({ + required this.locale, + required this.title, + required this.body, + }); + + @override + int get hashCode => locale.hashCode ^ title.hashCode ^ body.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AnnouncementText && + runtimeType == other.runtimeType && + locale == other.locale && + title == other.title && + body == other.body; +} + /// Aggregated user-facing application settings. /// /// `privacy_mode` is a read-only mirror of `IdentityInfo.privacy_mode` — diff --git a/lib/src/rust/frb_generated.dart b/lib/src/rust/frb_generated.dart index 84ec517d6..2f5dc49d0 100644 --- a/lib/src/rust/frb_generated.dart +++ b/lib/src/rust/frb_generated.dart @@ -4,6 +4,7 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field import 'api.dart'; +import 'api/announcements.dart'; import 'api/bond.dart'; import 'api/cashu.dart'; import 'api/disputes.dart'; @@ -89,7 +90,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.11.1'; @override - int get rustContentHash => -1544659396; + int get rustContentHash => -703973155; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -100,6 +101,10 @@ class RustLib extends BaseEntrypoint { } abstract class RustLibApi extends BaseApi { + Future> crateApiAnnouncementsAnnouncementsStreamNext({ + required AnnouncementsStream that, + }); + Future crateApiMessagesAnyMessageStreamNext({ required AnyMessageStream that, }); @@ -333,6 +338,10 @@ abstract class RustLibApi extends BaseApi { Future crateApiNwcDisconnectWallet(); + Future crateApiAnnouncementsDismissAnnouncement({ + required String address, + }); + Future crateApiMessagesDownloadAttachment({ required String messageId, }); @@ -373,6 +382,10 @@ abstract class RustLibApi extends BaseApi { Future> crateApiIdentityFundsAtRisk(); + Future> crateApiAnnouncementsGetAnnouncements(); + + Future crateApiAnnouncementsGetAnnouncementsEnabled(); + Future crateApiGetAppVersion(); Future crateApiMessagesGetAttachmentStatus({ @@ -500,10 +513,16 @@ abstract class RustLibApi extends BaseApi { BigInt? expirySecs, }); + Future crateApiAnnouncementsMarkAnnouncementRead({ + required String address, + }); + Future crateApiMessagesMarkAsRead({required String tradeId}); Future crateApiNodesNodeMetadataDefault(); + Future crateApiAnnouncementsOnAnnouncementsChanged(); + Future crateApiMessagesOnAnyNewMessage(); Future crateApiMessagesOnAttachmentProgress({ @@ -661,6 +680,10 @@ abstract class RustLibApi extends BaseApi { Future crateApiSettingsSetActiveMostroNode({required String pubkey}); + Future crateApiAnnouncementsSetAnnouncementsEnabled({ + required bool enabled, + }); + Future crateApiEscrowSetCashuMintUrlOverride({String? mintUrl}); Future crateApiSettingsSetDefaultFiatCode({String? code}); @@ -738,6 +761,15 @@ abstract class RustLibApi extends BaseApi { Future crateApiIdentityValidateMnemonic({required List words}); + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_AnnouncementsStream; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_AnnouncementsStream; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_AnnouncementsStreamPtr; + RustArcIncrementStrongCountFnType get rust_arc_increment_strong_count_AnyMessageStream; @@ -978,6 +1010,42 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { required super.portManager, }); + @override + Future> crateApiAnnouncementsAnnouncementsStreamNext({ + required AnnouncementsStream that, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + that, + serializer, + ); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 1, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_announcement_info, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiAnnouncementsAnnouncementsStreamNextConstMeta, + argValues: [that], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiAnnouncementsAnnouncementsStreamNextConstMeta => + const TaskConstMeta( + debugName: "AnnouncementsStream_next", + argNames: ["that"], + ); + @override Future crateApiMessagesAnyMessageStreamNext({ required AnyMessageStream that, @@ -993,7 +1061,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 1, + funcId: 2, port: port_, ); }, @@ -1029,7 +1097,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 2, + funcId: 3, port: port_, ); }, @@ -1065,7 +1133,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 3, + funcId: 4, port: port_, ); }, @@ -1101,7 +1169,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 4, + funcId: 5, port: port_, ); }, @@ -1137,7 +1205,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 5, + funcId: 6, port: port_, ); }, @@ -1173,7 +1241,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 6, + funcId: 7, port: port_, ); }, @@ -1209,7 +1277,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 7, + funcId: 8, port: port_, ); }, @@ -1242,7 +1310,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 8, + funcId: 9, port: port_, ); }, @@ -1278,7 +1346,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 9, + funcId: 10, port: port_, ); }, @@ -1311,7 +1379,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 10, + funcId: 11, port: port_, ); }, @@ -1344,7 +1412,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 11, + funcId: 12, port: port_, ); }, @@ -1380,7 +1448,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 12, + funcId: 13, port: port_, ); }, @@ -1414,7 +1482,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 13, + funcId: 14, port: port_, ); }, @@ -1441,7 +1509,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 14, + funcId: 15, port: port_, ); }, @@ -1477,7 +1545,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 15, + funcId: 16, port: port_, ); }, @@ -1515,7 +1583,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 16, + funcId: 17, port: port_, ); }, @@ -1545,7 +1613,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 17, + funcId: 18, port: port_, ); }, @@ -1581,7 +1649,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 18, + funcId: 19, port: port_, ); }, @@ -1619,7 +1687,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 19, + funcId: 20, port: port_, ); }, @@ -1659,7 +1727,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 20, + funcId: 21, port: port_, ); }, @@ -1697,7 +1765,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 21, + funcId: 22, port: port_, ); }, @@ -1733,7 +1801,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 22, + funcId: 23, port: port_, ); }, @@ -1769,7 +1837,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 23, + funcId: 24, port: port_, ); }, @@ -1802,7 +1870,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 24, + funcId: 25, port: port_, ); }, @@ -1838,7 +1906,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 25, + funcId: 26, port: port_, ); }, @@ -1871,7 +1939,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 26, + funcId: 27, port: port_, ); }, @@ -1907,7 +1975,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 27, + funcId: 28, port: port_, ); }, @@ -1943,7 +2011,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 28, + funcId: 29, port: port_, ); }, @@ -1980,7 +2048,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 29, + funcId: 30, port: port_, ); }, @@ -2013,7 +2081,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 30, + funcId: 31, port: port_, ); }, @@ -2049,7 +2117,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 31, + funcId: 32, port: port_, ); }, @@ -2085,7 +2153,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 32, + funcId: 33, port: port_, ); }, @@ -2121,7 +2189,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 33, + funcId: 34, port: port_, ); }, @@ -2157,7 +2225,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 34, + funcId: 35, port: port_, ); }, @@ -2188,7 +2256,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 35, + funcId: 36, port: port_, ); }, @@ -2223,7 +2291,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 36, + funcId: 37, port: port_, ); }, @@ -2254,7 +2322,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 37, + funcId: 38, port: port_, ); }, @@ -2282,7 +2350,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 38, + funcId: 39, port: port_, ); }, @@ -2317,7 +2385,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 39, + funcId: 40, port: port_, ); }, @@ -2349,7 +2417,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 40, + funcId: 41, port: port_, ); }, @@ -2380,7 +2448,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 41, + funcId: 42, port: port_, ); }, @@ -2410,7 +2478,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 42, + funcId: 43, port: port_, ); }, @@ -2437,7 +2505,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 43, + funcId: 44, port: port_, ); }, @@ -2467,7 +2535,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 44, + funcId: 45, port: port_, ); }, @@ -2497,7 +2565,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 45, + funcId: 46, port: port_, ); }, @@ -2525,7 +2593,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 46, + funcId: 47, port: port_, ); }, @@ -2553,7 +2621,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 47, + funcId: 48, port: port_, ); }, @@ -2581,7 +2649,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 48, + funcId: 49, port: port_, ); }, @@ -2611,7 +2679,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 49, + funcId: 50, port: port_, ); }, @@ -2641,7 +2709,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 50, + funcId: 51, port: port_, ); }, @@ -2671,7 +2739,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 51, + funcId: 52, port: port_, ); }, @@ -2703,7 +2771,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 52, + funcId: 53, port: port_, ); }, @@ -2735,7 +2803,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 53, + funcId: 54, port: port_, ); }, @@ -2763,7 +2831,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 54, + funcId: 55, port: port_, ); }, @@ -2793,7 +2861,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 55, + funcId: 56, port: port_, ); }, @@ -2820,7 +2888,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 56, + funcId: 57, port: port_, ); }, @@ -2858,7 +2926,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 57, + funcId: 58, port: port_, ); }, @@ -2898,7 +2966,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 58, + funcId: 59, port: port_, ); }, @@ -2931,7 +2999,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 59, + funcId: 60, port: port_, ); }, @@ -2961,7 +3029,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 60, + funcId: 61, port: port_, ); }, @@ -2988,7 +3056,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 61, + funcId: 62, port: port_, ); }, @@ -3016,7 +3084,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 62, + funcId: 63, port: port_, ); }, @@ -3047,7 +3115,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 63, + funcId: 64, port: port_, ); }, @@ -3074,7 +3142,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 64, + funcId: 65, port: port_, ); }, @@ -3104,7 +3172,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 65, + funcId: 66, port: port_, ); }, @@ -3134,7 +3202,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 66, + funcId: 67, port: port_, ); }, @@ -3161,7 +3229,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 67, + funcId: 68, port: port_, ); }, @@ -3188,7 +3256,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 68, + funcId: 69, port: port_, ); }, @@ -3215,7 +3283,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 69, + funcId: 70, port: port_, ); }, @@ -3233,6 +3301,39 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiNwcDisconnectWalletConstMeta => const TaskConstMeta(debugName: "disconnect_wallet", argNames: []); + @override + Future crateApiAnnouncementsDismissAnnouncement({ + required String address, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(address, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 71, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiAnnouncementsDismissAnnouncementConstMeta, + argValues: [address], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiAnnouncementsDismissAnnouncementConstMeta => + const TaskConstMeta( + debugName: "dismiss_announcement", + argNames: ["address"], + ); + @override Future crateApiMessagesDownloadAttachment({ required String messageId, @@ -3245,7 +3346,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 70, + funcId: 72, port: port_, ); }, @@ -3278,7 +3379,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 71, + funcId: 73, port: port_, ); }, @@ -3311,7 +3412,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 72, + funcId: 74, port: port_, ); }, @@ -3346,7 +3447,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 73, + funcId: 75, port: port_, ); }, @@ -3381,7 +3482,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 74, + funcId: 76, port: port_, ); }, @@ -3414,7 +3515,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 75, + funcId: 77, port: port_, ); }, @@ -3447,7 +3548,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 76, + funcId: 78, port: port_, ); }, @@ -3480,7 +3581,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 77, + funcId: 79, port: port_, ); }, @@ -3510,7 +3611,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 78, + funcId: 80, port: port_, ); }, @@ -3538,7 +3639,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 79, + funcId: 81, port: port_, ); }, @@ -3568,7 +3669,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 80, + funcId: 82, port: port_, ); }, @@ -3587,7 +3688,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { const TaskConstMeta(debugName: "funds_at_risk", argNames: []); @override - Future crateApiGetAppVersion() { + Future> crateApiAnnouncementsGetAnnouncements() { return handler.executeNormal( NormalTask( callFfi: (port_) { @@ -3595,54 +3696,108 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 81, + funcId: 83, port: port_, ); }, codec: SseCodec( - decodeSuccessData: sse_decode_String, - decodeErrorData: null, + decodeSuccessData: sse_decode_list_announcement_info, + decodeErrorData: sse_decode_AnyhowException, ), - constMeta: kCrateApiGetAppVersionConstMeta, + constMeta: kCrateApiAnnouncementsGetAnnouncementsConstMeta, argValues: [], apiImpl: this, ), ); } - TaskConstMeta get kCrateApiGetAppVersionConstMeta => - const TaskConstMeta(debugName: "get_app_version", argNames: []); + TaskConstMeta get kCrateApiAnnouncementsGetAnnouncementsConstMeta => + const TaskConstMeta(debugName: "get_announcements", argNames: []); @override - Future crateApiMessagesGetAttachmentStatus({ - required String messageId, - }) { + Future crateApiAnnouncementsGetAnnouncementsEnabled() { return handler.executeNormal( NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); - sse_encode_String(messageId, serializer); pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 82, + funcId: 84, port: port_, ); }, codec: SseCodec( - decodeSuccessData: sse_decode_opt_box_autoadd_download_status, + decodeSuccessData: sse_decode_bool, decodeErrorData: sse_decode_AnyhowException, ), - constMeta: kCrateApiMessagesGetAttachmentStatusConstMeta, - argValues: [messageId], + constMeta: kCrateApiAnnouncementsGetAnnouncementsEnabledConstMeta, + argValues: [], apiImpl: this, ), ); } - TaskConstMeta get kCrateApiMessagesGetAttachmentStatusConstMeta => - const TaskConstMeta( - debugName: "get_attachment_status", + TaskConstMeta get kCrateApiAnnouncementsGetAnnouncementsEnabledConstMeta => + const TaskConstMeta(debugName: "get_announcements_enabled", argNames: []); + + @override + Future crateApiGetAppVersion() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 85, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: null, + ), + constMeta: kCrateApiGetAppVersionConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiGetAppVersionConstMeta => + const TaskConstMeta(debugName: "get_app_version", argNames: []); + + @override + Future crateApiMessagesGetAttachmentStatus({ + required String messageId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(messageId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 86, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_opt_box_autoadd_download_status, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiMessagesGetAttachmentStatusConstMeta, + argValues: [messageId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiMessagesGetAttachmentStatusConstMeta => + const TaskConstMeta( + debugName: "get_attachment_status", argNames: ["messageId"], ); @@ -3655,7 +3810,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 83, + funcId: 87, port: port_, ); }, @@ -3683,7 +3838,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 84, + funcId: 88, port: port_, ); }, @@ -3715,7 +3870,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 85, + funcId: 89, port: port_, ); }, @@ -3745,7 +3900,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 86, + funcId: 90, port: port_, ); }, @@ -3772,7 +3927,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 87, + funcId: 91, port: port_, ); }, @@ -3800,7 +3955,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 88, + funcId: 92, port: port_, ); }, @@ -3827,7 +3982,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 89, + funcId: 93, port: port_, ); }, @@ -3854,7 +4009,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 90, + funcId: 94, port: port_, ); }, @@ -3884,7 +4039,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 91, + funcId: 95, port: port_, ); }, @@ -3914,7 +4069,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 92, + funcId: 96, port: port_, ); }, @@ -3944,7 +4099,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 93, + funcId: 97, port: port_, ); }, @@ -3974,7 +4129,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 94, + funcId: 98, port: port_, ); }, @@ -4005,7 +4160,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 95, + funcId: 99, port: port_, ); }, @@ -4032,7 +4187,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 96, + funcId: 100, port: port_, ); }, @@ -4060,7 +4215,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 97, + funcId: 101, port: port_, ); }, @@ -4088,7 +4243,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 98, + funcId: 102, port: port_, ); }, @@ -4121,7 +4276,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 99, + funcId: 103, port: port_, ); }, @@ -4148,7 +4303,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 100, + funcId: 104, port: port_, ); }, @@ -4178,7 +4333,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 101, + funcId: 105, port: port_, ); }, @@ -4208,7 +4363,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 102, + funcId: 106, port: port_, ); }, @@ -4235,7 +4390,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 103, + funcId: 107, )!; }, codec: SseCodec( @@ -4261,7 +4416,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 104, + funcId: 108, port: port_, ); }, @@ -4289,7 +4444,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 105, + funcId: 109, port: port_, ); }, @@ -4317,7 +4472,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 106, + funcId: 110, port: port_, ); }, @@ -4344,7 +4499,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 107, + funcId: 111, port: port_, ); }, @@ -4371,7 +4526,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 108, + funcId: 112, port: port_, ); }, @@ -4399,7 +4554,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 109, + funcId: 113, port: port_, ); }, @@ -4430,7 +4585,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 110, + funcId: 114, port: port_, ); }, @@ -4465,7 +4620,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 111, + funcId: 115, port: port_, ); }, @@ -4502,7 +4657,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 112, + funcId: 116, port: port_, ); }, @@ -4532,7 +4687,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 113, + funcId: 117, port: port_, ); }, @@ -4564,7 +4719,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 114, + funcId: 118, port: port_, ); }, @@ -4595,7 +4750,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 115, + funcId: 119, port: port_, ); }, @@ -4625,7 +4780,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 116, + funcId: 120, port: port_, ); }, @@ -4655,7 +4810,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 117, + funcId: 121, port: port_, ); }, @@ -4683,7 +4838,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 118, + funcId: 122, port: port_, ); }, @@ -4711,7 +4866,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 119, + funcId: 123, port: port_, ); }, @@ -4738,7 +4893,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 120, + funcId: 124, port: port_, ); }, @@ -4765,7 +4920,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 121, + funcId: 125, port: port_, ); }, @@ -4792,7 +4947,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 122, + funcId: 126, port: port_, ); }, @@ -4819,7 +4974,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 123, + funcId: 127, port: port_, ); }, @@ -4855,7 +5010,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 124, + funcId: 128, port: port_, ); }, @@ -4886,7 +5041,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 125, + funcId: 129, port: port_, ); }, @@ -4920,7 +5075,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 126, + funcId: 130, port: port_, ); }, @@ -4940,6 +5095,39 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["amountSats", "description", "expirySecs"], ); + @override + Future crateApiAnnouncementsMarkAnnouncementRead({ + required String address, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(address, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 131, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiAnnouncementsMarkAnnouncementReadConstMeta, + argValues: [address], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiAnnouncementsMarkAnnouncementReadConstMeta => + const TaskConstMeta( + debugName: "mark_announcement_read", + argNames: ["address"], + ); + @override Future crateApiMessagesMarkAsRead({required String tradeId}) { return handler.executeNormal( @@ -4950,7 +5138,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 127, + funcId: 132, port: port_, ); }, @@ -4977,7 +5165,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 128, + funcId: 133, port: port_, ); }, @@ -4995,6 +5183,34 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiNodesNodeMetadataDefaultConstMeta => const TaskConstMeta(debugName: "node_metadata_default", argNames: []); + @override + Future crateApiAnnouncementsOnAnnouncementsChanged() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 134, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream, + decodeErrorData: null, + ), + constMeta: kCrateApiAnnouncementsOnAnnouncementsChangedConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiAnnouncementsOnAnnouncementsChangedConstMeta => + const TaskConstMeta(debugName: "on_announcements_changed", argNames: []); + @override Future crateApiMessagesOnAnyNewMessage() { return handler.executeNormal( @@ -5004,7 +5220,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 129, + funcId: 135, port: port_, ); }, @@ -5035,7 +5251,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 130, + funcId: 136, port: port_, ); }, @@ -5066,7 +5282,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 131, + funcId: 137, port: port_, ); }, @@ -5094,7 +5310,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 132, + funcId: 138, port: port_, ); }, @@ -5122,7 +5338,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 133, + funcId: 139, port: port_, ); }, @@ -5150,7 +5366,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 134, + funcId: 140, port: port_, ); }, @@ -5184,7 +5400,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 135, + funcId: 141, port: port_, ); }, @@ -5215,7 +5431,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 136, + funcId: 142, port: port_, ); }, @@ -5243,7 +5459,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 137, + funcId: 143, port: port_, ); }, @@ -5274,7 +5490,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 138, + funcId: 144, port: port_, ); }, @@ -5305,7 +5521,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 139, + funcId: 145, port: port_, ); }, @@ -5336,7 +5552,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 140, + funcId: 146, port: port_, ); }, @@ -5364,7 +5580,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 141, + funcId: 147, port: port_, ); }, @@ -5392,7 +5608,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 142, + funcId: 148, port: port_, ); }, @@ -5420,7 +5636,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 143, + funcId: 149, port: port_, ); }, @@ -5448,7 +5664,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 144, + funcId: 150, port: port_, ); }, @@ -5476,7 +5692,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 145, + funcId: 151, port: port_, ); }, @@ -5504,7 +5720,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 146, + funcId: 152, port: port_, ); }, @@ -5532,7 +5748,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 147, + funcId: 153, port: port_, ); }, @@ -5560,7 +5776,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 148, + funcId: 154, port: port_, ); }, @@ -5588,7 +5804,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 149, + funcId: 155, port: port_, ); }, @@ -5619,7 +5835,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 150, + funcId: 156, port: port_, ); }, @@ -5647,7 +5863,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 151, + funcId: 157, port: port_, ); }, @@ -5675,7 +5891,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 152, + funcId: 158, port: port_, ); }, @@ -5703,7 +5919,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 153, + funcId: 159, port: port_, ); }, @@ -5736,7 +5952,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 154, + funcId: 160, port: port_, ); }, @@ -5766,7 +5982,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 155, + funcId: 161, port: port_, ); }, @@ -5795,7 +6011,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 156, + funcId: 162, )!; }, codec: SseCodec( @@ -5827,7 +6043,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 157, + funcId: 163, port: port_, ); }, @@ -5855,7 +6071,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 158, + funcId: 164, port: port_, ); }, @@ -5885,7 +6101,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 159, + funcId: 165, port: port_, ); }, @@ -5916,7 +6132,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 160, + funcId: 166, port: port_, ); }, @@ -5949,7 +6165,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 161, + funcId: 167, port: port_, ); }, @@ -5979,7 +6195,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 162, + funcId: 168, port: port_, ); }, @@ -6006,7 +6222,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 163, + funcId: 169, port: port_, ); }, @@ -6033,7 +6249,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 164, + funcId: 170, port: port_, ); }, @@ -6060,7 +6276,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 165, + funcId: 171, port: port_, ); }, @@ -6090,7 +6306,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 166, + funcId: 172, port: port_, ); }, @@ -6120,7 +6336,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 167, + funcId: 173, port: port_, ); }, @@ -6147,7 +6363,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 168, + funcId: 174, port: port_, ); }, @@ -6177,7 +6393,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 169, + funcId: 175, port: port_, ); }, @@ -6208,7 +6424,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 170, + funcId: 176, port: port_, ); }, @@ -6236,7 +6452,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 171, + funcId: 177, port: port_, ); }, @@ -6267,7 +6483,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 172, + funcId: 178, port: port_, ); }, @@ -6297,7 +6513,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 173, + funcId: 179, )!; }, codec: SseCodec( @@ -6330,7 +6546,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 174, + funcId: 180, port: port_, ); }, @@ -6363,7 +6579,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 175, + funcId: 181, port: port_, ); }, @@ -6390,7 +6606,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 176, + funcId: 182, port: port_, ); }, @@ -6420,7 +6636,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 177, + funcId: 183, port: port_, ); }, @@ -6447,7 +6663,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 178, + funcId: 184, port: port_, ); }, @@ -6483,7 +6699,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 179, + funcId: 185, port: port_, ); }, @@ -6514,7 +6730,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 180, + funcId: 186, port: port_, ); }, @@ -6550,7 +6766,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 181, + funcId: 187, port: port_, ); }, @@ -6590,7 +6806,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 182, + funcId: 188, port: port_, ); }, @@ -6630,7 +6846,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 183, + funcId: 189, port: port_, ); }, @@ -6667,7 +6883,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 184, + funcId: 190, port: port_, ); }, @@ -6698,7 +6914,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 185, + funcId: 191, port: port_, ); }, @@ -6719,6 +6935,39 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["pubkey"], ); + @override + Future crateApiAnnouncementsSetAnnouncementsEnabled({ + required bool enabled, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_bool(enabled, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 192, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiAnnouncementsSetAnnouncementsEnabledConstMeta, + argValues: [enabled], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiAnnouncementsSetAnnouncementsEnabledConstMeta => + const TaskConstMeta( + debugName: "set_announcements_enabled", + argNames: ["enabled"], + ); + @override Future crateApiEscrowSetCashuMintUrlOverride({String? mintUrl}) { return handler.executeNormal( @@ -6729,7 +6978,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 186, + funcId: 193, port: port_, ); }, @@ -6760,7 +7009,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 187, + funcId: 194, port: port_, ); }, @@ -6791,7 +7040,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 188, + funcId: 195, port: port_, ); }, @@ -6822,7 +7071,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 189, + funcId: 196, port: port_, ); }, @@ -6853,7 +7102,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 190, + funcId: 197, port: port_, ); }, @@ -6881,7 +7130,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 191, + funcId: 198, port: port_, ); }, @@ -6912,7 +7161,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 192, + funcId: 199, port: port_, ); }, @@ -6940,7 +7189,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 193, + funcId: 200, port: port_, ); }, @@ -6972,7 +7221,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 194, + funcId: 201, port: port_, ); }, @@ -7002,7 +7251,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 195, + funcId: 202, port: port_, ); }, @@ -7033,7 +7282,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 196, + funcId: 203, port: port_, ); }, @@ -7063,7 +7312,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 197, + funcId: 204, port: port_, ); }, @@ -7098,7 +7347,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 198, + funcId: 205, port: port_, ); }, @@ -7133,7 +7382,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 199, + funcId: 206, port: port_, ); }, @@ -7167,7 +7416,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 200, + funcId: 207, port: port_, ); }, @@ -7202,7 +7451,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 201, + funcId: 208, port: port_, ); }, @@ -7237,7 +7486,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 202, + funcId: 209, port: port_, ); }, @@ -7267,7 +7516,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 203, + funcId: 210, port: port_, ); }, @@ -7294,7 +7543,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 204, + funcId: 211, port: port_, ); }, @@ -7330,7 +7579,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 205, + funcId: 212, port: port_, ); }, @@ -7362,7 +7611,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 206, + funcId: 213, port: port_, ); }, @@ -7393,7 +7642,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 207, + funcId: 214, port: port_, ); }, @@ -7423,7 +7672,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 208, + funcId: 215, port: port_, ); }, @@ -7454,7 +7703,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 209, + funcId: 216, port: port_, ); }, @@ -7472,6 +7721,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiIdentityValidateMnemonicConstMeta => const TaskConstMeta(debugName: "validate_mnemonic", argNames: ["words"]); + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_AnnouncementsStream => + wire.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_AnnouncementsStream => + wire.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream; + RustArcIncrementStrongCountFnType get rust_arc_increment_strong_count_AnyMessageStream => wire.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream; @@ -7686,6 +7943,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return AnyhowException(raw as String); } + @protected + AnnouncementsStream + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return AnnouncementsStreamImpl.frbInternalDcoDecode(raw as List); + } + @protected AnyMessageStream dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -7922,6 +8188,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return WalletStatusStreamImpl.frbInternalDcoDecode(raw as List); } + @protected + AnnouncementsStream + dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return AnnouncementsStreamImpl.frbInternalDcoDecode(raw as List); + } + @protected AnyMessageStream dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -8158,6 +8433,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return OrderBookImpl.frbInternalDcoDecode(raw as List); } + @protected + AnnouncementsStream + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return AnnouncementsStreamImpl.frbInternalDcoDecode(raw as List); + } + @protected AnyMessageStream dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -8400,6 +8684,44 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as String; } + @protected + AnnouncementInfo dco_decode_announcement_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 9) + throw Exception('unexpected arr length: expect 9 but see ${arr.length}'); + return AnnouncementInfo( + address: dco_decode_String(arr[0]), + revision: dco_decode_String(arr[1]), + createdAt: dco_decode_i_64(arr[2]), + expiration: dco_decode_i_64(arr[3]), + severity: dco_decode_announcement_severity(arr[4]), + texts: dco_decode_list_announcement_text(arr[5]), + url: dco_decode_opt_String(arr[6]), + read: dco_decode_bool(arr[7]), + dismissed: dco_decode_bool(arr[8]), + ); + } + + @protected + AnnouncementSeverity dco_decode_announcement_severity(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return AnnouncementSeverity.values[raw as int]; + } + + @protected + AnnouncementText dco_decode_announcement_text(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return AnnouncementText( + locale: dco_decode_String(arr[0]), + title: dco_decode_String(arr[1]), + body: dco_decode_String(arr[2]), + ); + } + @protected AppSettings dco_decode_app_settings(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -9115,6 +9437,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (raw as List).map(dco_decode_String).toList(); } + @protected + List dco_decode_list_announcement_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_announcement_info).toList(); + } + + @protected + List dco_decode_list_announcement_text(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_announcement_text).toList(); + } + @protected List dco_decode_list_bond_claim(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -10133,6 +10467,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return AnyhowException(inner); } + @protected + AnnouncementsStream + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return AnnouncementsStreamImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + @protected AnyMessageStream sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -10445,6 +10791,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + AnnouncementsStream + sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return AnnouncementsStreamImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + @protected AnyMessageStream sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -10757,6 +11115,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + AnnouncementsStream + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return AnnouncementsStreamImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + @protected AnyMessageStream sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -11076,6 +11446,53 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return utf8.decoder.convert(inner); } + @protected + AnnouncementInfo sse_decode_announcement_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_address = sse_decode_String(deserializer); + var var_revision = sse_decode_String(deserializer); + var var_createdAt = sse_decode_i_64(deserializer); + var var_expiration = sse_decode_i_64(deserializer); + var var_severity = sse_decode_announcement_severity(deserializer); + var var_texts = sse_decode_list_announcement_text(deserializer); + var var_url = sse_decode_opt_String(deserializer); + var var_read = sse_decode_bool(deserializer); + var var_dismissed = sse_decode_bool(deserializer); + return AnnouncementInfo( + address: var_address, + revision: var_revision, + createdAt: var_createdAt, + expiration: var_expiration, + severity: var_severity, + texts: var_texts, + url: var_url, + read: var_read, + dismissed: var_dismissed, + ); + } + + @protected + AnnouncementSeverity sse_decode_announcement_severity( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_i_32(deserializer); + return AnnouncementSeverity.values[inner]; + } + + @protected + AnnouncementText sse_decode_announcement_text(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_locale = sse_decode_String(deserializer); + var var_title = sse_decode_String(deserializer); + var var_body = sse_decode_String(deserializer); + return AnnouncementText( + locale: var_locale, + title: var_title, + body: var_body, + ); + } + @protected AppSettings sse_decode_app_settings(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -11943,6 +12360,34 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return ans_; } + @protected + List sse_decode_list_announcement_info( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_announcement_info(deserializer)); + } + return ans_; + } + + @protected + List sse_decode_list_announcement_text( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_announcement_text(deserializer)); + } + return ans_; + } + @protected List sse_decode_list_bond_claim(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -13435,6 +13880,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_String(self.message, serializer); } + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + AnnouncementsStream self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as AnnouncementsStreamImpl).frbInternalSseEncode(move: true), + serializer, + ); + } + @protected void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -13773,6 +14231,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + void + sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + AnnouncementsStream self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as AnnouncementsStreamImpl).frbInternalSseEncode(move: false), + serializer, + ); + } + @protected void sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -14111,6 +14582,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + AnnouncementsStream self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as AnnouncementsStreamImpl).frbInternalSseEncode(move: null), + serializer, + ); + } + @protected void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -14455,6 +14939,43 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); } + @protected + void sse_encode_announcement_info( + AnnouncementInfo self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.address, serializer); + sse_encode_String(self.revision, serializer); + sse_encode_i_64(self.createdAt, serializer); + sse_encode_i_64(self.expiration, serializer); + sse_encode_announcement_severity(self.severity, serializer); + sse_encode_list_announcement_text(self.texts, serializer); + sse_encode_opt_String(self.url, serializer); + sse_encode_bool(self.read, serializer); + sse_encode_bool(self.dismissed, serializer); + } + + @protected + void sse_encode_announcement_severity( + AnnouncementSeverity self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.index, serializer); + } + + @protected + void sse_encode_announcement_text( + AnnouncementText self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.locale, serializer); + sse_encode_String(self.title, serializer); + sse_encode_String(self.body, serializer); + } + @protected void sse_encode_app_settings(AppSettings self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -15234,6 +15755,30 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_list_announcement_info( + List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_announcement_info(item, serializer); + } + } + + @protected + void sse_encode_list_announcement_text( + List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_announcement_text(item, serializer); + } + } + @protected void sse_encode_list_bond_claim( List self, @@ -16495,6 +17040,43 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } +@sealed +class AnnouncementsStreamImpl extends RustOpaque + implements AnnouncementsStream { + // Not to be used by end users + AnnouncementsStreamImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + AnnouncementsStreamImpl.frbInternalSseDecode( + BigInt ptr, + int externalSizeOnNative, + ) : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: + RustLib + .instance + .api + .rust_arc_increment_strong_count_AnnouncementsStream, + rustArcDecrementStrongCount: + RustLib + .instance + .api + .rust_arc_decrement_strong_count_AnnouncementsStream, + rustArcDecrementStrongCountPtr: + RustLib + .instance + .api + .rust_arc_decrement_strong_count_AnnouncementsStreamPtr, + ); + + /// The next snapshot; a lag skips to the newest rather than ending the + /// stream, since every snapshot is the whole list. + Future> next() => RustLib.instance.api + .crateApiAnnouncementsAnnouncementsStreamNext(that: this); +} + @sealed class AnyMessageStreamImpl extends RustOpaque implements AnyMessageStream { // Not to be used by end users diff --git a/lib/src/rust/frb_generated.io.dart b/lib/src/rust/frb_generated.io.dart index cc4089f89..9f4d5814d 100644 --- a/lib/src/rust/frb_generated.io.dart +++ b/lib/src/rust/frb_generated.io.dart @@ -4,6 +4,7 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field import 'api.dart'; +import 'api/announcements.dart'; import 'api/bond.dart'; import 'api/cashu.dart'; import 'api/disputes.dart'; @@ -41,6 +42,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { required super.portManager, }); + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_AnnouncementsStreamPtr => + wire._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStreamPtr; + CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_AnyMessageStreamPtr => wire._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStreamPtr; @@ -147,6 +152,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AnyhowException dco_decode_AnyhowException(dynamic raw); + @protected + AnnouncementsStream + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + dynamic raw, + ); + @protected AnyMessageStream dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -303,6 +314,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { dynamic raw, ); + @protected + AnnouncementsStream + dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + dynamic raw, + ); + @protected AnyMessageStream dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -459,6 +476,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { dynamic raw, ); + @protected + AnnouncementsStream + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + dynamic raw, + ); + @protected AnyMessageStream dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -618,6 +641,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String dco_decode_String(dynamic raw); + @protected + AnnouncementInfo dco_decode_announcement_info(dynamic raw); + + @protected + AnnouncementSeverity dco_decode_announcement_severity(dynamic raw); + + @protected + AnnouncementText dco_decode_announcement_text(dynamic raw); + @protected AppSettings dco_decode_app_settings(dynamic raw); @@ -859,6 +891,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List dco_decode_list_String(dynamic raw); + @protected + List dco_decode_list_announcement_info(dynamic raw); + + @protected + List dco_decode_list_announcement_text(dynamic raw); + @protected List dco_decode_list_bond_claim(dynamic raw); @@ -1199,6 +1237,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); + @protected + AnnouncementsStream + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + SseDeserializer deserializer, + ); + @protected AnyMessageStream sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -1355,6 +1399,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + AnnouncementsStream + sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + SseDeserializer deserializer, + ); + @protected AnyMessageStream sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -1511,6 +1561,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + AnnouncementsStream + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + SseDeserializer deserializer, + ); + @protected AnyMessageStream sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -1670,6 +1726,17 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String sse_decode_String(SseDeserializer deserializer); + @protected + AnnouncementInfo sse_decode_announcement_info(SseDeserializer deserializer); + + @protected + AnnouncementSeverity sse_decode_announcement_severity( + SseDeserializer deserializer, + ); + + @protected + AnnouncementText sse_decode_announcement_text(SseDeserializer deserializer); + @protected AppSettings sse_decode_app_settings(SseDeserializer deserializer); @@ -1953,6 +2020,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List sse_decode_list_String(SseDeserializer deserializer); + @protected + List sse_decode_list_announcement_info( + SseDeserializer deserializer, + ); + + @protected + List sse_decode_list_announcement_text( + SseDeserializer deserializer, + ); + @protected List sse_decode_list_bond_claim(SseDeserializer deserializer); @@ -2374,6 +2451,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + AnnouncementsStream self, + SseSerializer serializer, + ); + @protected void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -2556,6 +2640,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void + sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + AnnouncementsStream self, + SseSerializer serializer, + ); + @protected void sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -2738,6 +2829,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + AnnouncementsStream self, + SseSerializer serializer, + ); + @protected void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -2923,6 +3021,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_String(String self, SseSerializer serializer); + @protected + void sse_encode_announcement_info( + AnnouncementInfo self, + SseSerializer serializer, + ); + + @protected + void sse_encode_announcement_severity( + AnnouncementSeverity self, + SseSerializer serializer, + ); + + @protected + void sse_encode_announcement_text( + AnnouncementText self, + SseSerializer serializer, + ); + @protected void sse_encode_app_settings(AppSettings self, SseSerializer serializer); @@ -3319,6 +3435,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_list_String(List self, SseSerializer serializer); + @protected + void sse_encode_list_announcement_info( + List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_list_announcement_text( + List self, + SseSerializer serializer, + ); + @protected void sse_encode_list_bond_claim( List self, @@ -3847,6 +3975,40 @@ class RustLibWire implements BaseWire { RustLibWire(ffi.DynamicLibrary dynamicLibrary) : _lookup = dynamicLibrary.lookup; + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStreamPtr = + _lookup)>>( + 'frbgen_mostro_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream', + ); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStreamPtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStreamPtr = + _lookup)>>( + 'frbgen_mostro_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream', + ); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStreamPtr + .asFunction)>(); + void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( ffi.Pointer ptr, diff --git a/lib/src/rust/frb_generated.web.dart b/lib/src/rust/frb_generated.web.dart index a0e93e13e..93c25847c 100644 --- a/lib/src/rust/frb_generated.web.dart +++ b/lib/src/rust/frb_generated.web.dart @@ -7,6 +7,7 @@ // ignore_for_file: argument_type_not_assignable import 'api.dart'; +import 'api/announcements.dart'; import 'api/bond.dart'; import 'api/cashu.dart'; import 'api/disputes.dart'; @@ -43,6 +44,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { required super.portManager, }); + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_AnnouncementsStreamPtr => + wire.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream; + CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_AnyMessageStreamPtr => wire.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream; @@ -149,6 +154,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AnyhowException dco_decode_AnyhowException(dynamic raw); + @protected + AnnouncementsStream + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + dynamic raw, + ); + @protected AnyMessageStream dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -305,6 +316,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { dynamic raw, ); + @protected + AnnouncementsStream + dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + dynamic raw, + ); + @protected AnyMessageStream dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -461,6 +478,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { dynamic raw, ); + @protected + AnnouncementsStream + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + dynamic raw, + ); + @protected AnyMessageStream dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -620,6 +643,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String dco_decode_String(dynamic raw); + @protected + AnnouncementInfo dco_decode_announcement_info(dynamic raw); + + @protected + AnnouncementSeverity dco_decode_announcement_severity(dynamic raw); + + @protected + AnnouncementText dco_decode_announcement_text(dynamic raw); + @protected AppSettings dco_decode_app_settings(dynamic raw); @@ -861,6 +893,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List dco_decode_list_String(dynamic raw); + @protected + List dco_decode_list_announcement_info(dynamic raw); + + @protected + List dco_decode_list_announcement_text(dynamic raw); + @protected List dco_decode_list_bond_claim(dynamic raw); @@ -1201,6 +1239,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); + @protected + AnnouncementsStream + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + SseDeserializer deserializer, + ); + @protected AnyMessageStream sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -1357,6 +1401,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + AnnouncementsStream + sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + SseDeserializer deserializer, + ); + @protected AnyMessageStream sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -1513,6 +1563,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + AnnouncementsStream + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + SseDeserializer deserializer, + ); + @protected AnyMessageStream sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -1672,6 +1728,17 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String sse_decode_String(SseDeserializer deserializer); + @protected + AnnouncementInfo sse_decode_announcement_info(SseDeserializer deserializer); + + @protected + AnnouncementSeverity sse_decode_announcement_severity( + SseDeserializer deserializer, + ); + + @protected + AnnouncementText sse_decode_announcement_text(SseDeserializer deserializer); + @protected AppSettings sse_decode_app_settings(SseDeserializer deserializer); @@ -1955,6 +2022,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List sse_decode_list_String(SseDeserializer deserializer); + @protected + List sse_decode_list_announcement_info( + SseDeserializer deserializer, + ); + + @protected + List sse_decode_list_announcement_text( + SseDeserializer deserializer, + ); + @protected List sse_decode_list_bond_claim(SseDeserializer deserializer); @@ -2376,6 +2453,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + AnnouncementsStream self, + SseSerializer serializer, + ); + @protected void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -2558,6 +2642,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void + sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + AnnouncementsStream self, + SseSerializer serializer, + ); + @protected void sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -2740,6 +2831,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + AnnouncementsStream self, + SseSerializer serializer, + ); + @protected void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( @@ -2925,6 +3023,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_String(String self, SseSerializer serializer); + @protected + void sse_encode_announcement_info( + AnnouncementInfo self, + SseSerializer serializer, + ); + + @protected + void sse_encode_announcement_severity( + AnnouncementSeverity self, + SseSerializer serializer, + ); + + @protected + void sse_encode_announcement_text( + AnnouncementText self, + SseSerializer serializer, + ); + @protected void sse_encode_app_settings(AppSettings self, SseSerializer serializer); @@ -3321,6 +3437,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_list_String(List self, SseSerializer serializer); + @protected + void sse_encode_list_announcement_info( + List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_list_announcement_text( + List self, + SseSerializer serializer, + ); + @protected void sse_encode_list_bond_claim( List self, @@ -3840,6 +3968,22 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { class RustLibWire implements BaseWire { RustLibWire.fromExternalLibrary(ExternalLibrary lib); + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + int ptr, + ) => wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + ptr, + ); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + int ptr, + ) => wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + ptr, + ); + void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( int ptr, @@ -4263,6 +4407,16 @@ external RustLibWasmModule get wasmModule; @JS() @anonymous extension type RustLibWasmModule._(JSObject _) implements JSObject { + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + int ptr, + ); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + int ptr, + ); + external void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( int ptr, diff --git a/rust/src/api/announcements.rs b/rust/src/api/announcements.rs new file mode 100644 index 000000000..2eff496e1 --- /dev/null +++ b/rust/src/api/announcements.rs @@ -0,0 +1,275 @@ +//! The announcement channel's bridge surface +//! (`specs/006-announcement-channel/spec.md` §6.1, §7): what Dart reads, the +//! stream it follows, the Announcements toggle, and the read / dismissed +//! flags it reports back. +//! +//! Dart renders what this hands it and decides nothing: every announcement +//! here already passed the reader's door (`nostr::announcement_reader`), and +//! it carries all seven locales because Rust does not translate. + +use anyhow::{anyhow, bail, Result}; +use nostr_sdk::prelude::*; +use tokio::sync::broadcast; +use tokio::sync::broadcast::error::RecvError; + +use crate::api::types::{AnnouncementInfo, AnnouncementSeverity, AnnouncementText}; +use crate::db::{settings_keys, Storage}; +use crate::nostr::announcement_reader::{self, CurrentAnnouncement}; +use crate::nostr::announcements::{self, Severity}; + +/// Every announcement the cache holds right now: `critical` first, then +/// newest first (§3.4), dismissed ones included — a dismissed `critical` +/// stays listed and Dart is the one that knows (§3.4). Empty before the +/// database is up. +pub async fn get_announcements() -> Result> { + let Some(db) = crate::db::app_db::db() else { + return Ok(Vec::new()); + }; + let current = announcement_reader::restore( + db, + &announcements::allowed_authors(), + Timestamp::now(), + &announcements::app_version(), + ) + .await?; + Ok(current.iter().map(to_info).collect()) +} + +/// The Announcements toggle (§7): on unless the user turned it off. +pub async fn get_announcements_enabled() -> Result { + let db = crate::db::app_db::db().ok_or_else(|| anyhow!("StorageUnavailable"))?; + Ok(announcement_reader::enabled_from_setting( + db.get_setting(settings_keys::ANNOUNCEMENTS_ENABLED) + .await? + .as_deref(), + )) +} + +/// Flip the toggle. Off closes the relay subscription at the tap and +/// discards anything queued behind it; on reopens it, with the same 30-day +/// window a fresh install gets (§7). Persisted first: the subscription +/// follows the setting, never the other way round. +pub async fn set_announcements_enabled(enabled: bool) -> Result<()> { + let db = crate::db::app_db::db().ok_or_else(|| anyhow!("StorageUnavailable"))?; + db.set_setting( + settings_keys::ANNOUNCEMENTS_ENABLED, + if enabled { "true" } else { "false" }, + ) + .await?; + announcement_reader::set_enabled(enabled).await; + Ok(()) +} + +/// The user saw the announcement at `address`. Rust holds the flag because +/// it is Rust that decides whether a later revision re-announces (§5.3). +pub async fn mark_announcement_read(address: String) -> Result<()> { + set_flags(&address, Some(true), None).await +} + +/// The user swiped the announcement at `address` away. Read as well: a +/// dismissed notice never counts as unread, whichever level keeps it listed. +pub async fn dismiss_announcement(address: String) -> Result<()> { + set_flags(&address, Some(true), Some(true)).await +} + +async fn set_flags(address: &str, read: Option, dismissed: Option) -> Result<()> { + let db = crate::db::app_db::db().ok_or_else(|| anyhow!("StorageUnavailable"))?; + if !announcement_reader::set_flags(db, address, read, dismissed).await? { + // Expired or swept since the card was drawn: nothing to record, and + // the next emit takes the card with it. + log::debug!("[announcements] {address} is no longer cached; flag dropped"); + } + publish_current().await; + Ok(()) +} + +/// The bridge's view of a cached announcement. +pub(crate) fn to_info(current: &CurrentAnnouncement) -> AnnouncementInfo { + let announcement = ¤t.announcement; + let body = &announcement.body; + AnnouncementInfo { + address: current.address.clone(), + revision: announcement_reader::revision_of(announcement), + created_at: announcement.created_at.as_secs() as i64, + expiration: body.expiration.as_secs() as i64, + severity: match body.severity { + Severity::Info => AnnouncementSeverity::Info, + Severity::Warning => AnnouncementSeverity::Warning, + Severity::Critical => AnnouncementSeverity::Critical, + }, + texts: body + .texts + .iter() + .map(|text| AnnouncementText { + locale: text.locale.clone(), + title: text.title.clone(), + body: text.body.clone(), + }) + .collect(), + url: body.url.clone(), + read: current.read, + dismissed: current.dismissed, + } +} + +// ── Stream ──────────────────────────────────────────────────────────────────── + +/// A few snapshots at most sit unread: each is the whole list, so a lagging +/// subscriber loses nothing by skipping to the newest. +const CHANNEL_CAPACITY: usize = 8; + +fn tx() -> &'static broadcast::Sender> { + static TX: std::sync::OnceLock>> = + std::sync::OnceLock::new(); + TX.get_or_init(|| broadcast::channel(CHANNEL_CAPACITY).0) +} + +/// Hand Dart the cache as it stands. Called with the result of every pass +/// that may have changed it: a sweep, an arrival, a flag. +pub(crate) fn emit(current: &[CurrentAnnouncement]) { + // An error only means nobody is listening yet. + let _ = tx().send(current.iter().map(to_info).collect()); +} + +/// Re-read the cache and [`emit`] it. For the paths that changed one row +/// and do not hold the whole list. +pub(crate) async fn publish_current() { + match get_announcements().await { + Ok(list) => { + let _ = tx().send(list); + } + Err(e) => log::warn!("[announcements] re-reading the cache failed: {e}"), + } +} + +/// The cache, whole, every time it changes (§6.1). +pub struct AnnouncementsStream { + rx: broadcast::Receiver>, +} + +impl AnnouncementsStream { + /// The next snapshot; a lag skips to the newest rather than ending the + /// stream, since every snapshot is the whole list. + pub async fn next(&mut self) -> Result> { + loop { + match self.rx.recv().await { + Ok(list) => return Ok(list), + Err(RecvError::Lagged(_)) => continue, + Err(RecvError::Closed) => bail!("AnnouncementsStream closed: sender dropped"), + } + } + } +} + +/// Subscribe before the first [`get_announcements`], so a snapshot landing +/// in between is not missed. +pub fn on_announcements_changed() -> AnnouncementsStream { + AnnouncementsStream { + rx: tx().subscribe(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::nostr::announcements::{parse_body, Announcement}; + use serde_json::json; + + fn current(severity: &str, read: bool, dismissed: bool) -> CurrentAnnouncement { + let keys = Keys::generate(); + let locale = |l: &str| json!({ "title": format!("T {l}"), "body": "B" }); + let content = json!({ + "v": 1, + "severity": severity, + "locales": { + "en": locale("en"), "es": locale("es"), "fr": locale("fr"), + "de": locale("de"), "it": locale("it"), "nl": locale("nl"), + "pt": locale("pt"), + }, + "url": "https://mostro.network/blog", + }) + .to_string(); + let tags = vec![ + Tag::identifier("release-2.1"), + Tag::expiration(Timestamp::from_secs(1_900_000_000)), + ]; + let event = EventBuilder::new(Kind::from(38387u16), &content) + .tags(tags.clone()) + .custom_created_at(Timestamp::from_secs(1_800_000_000)) + .finalize(&keys) + .unwrap(); + CurrentAnnouncement { + address: announcement_reader::address_of(&keys.public_key(), "release-2.1"), + announcement: Announcement { + author: keys.public_key(), + event_id: event.id, + created_at: event.created_at, + body: parse_body(&Tags::from_list(tags), &content).unwrap(), + }, + read, + dismissed, + } + } + + #[test] + fn the_bridge_view_carries_every_locale_and_the_flags() { + // Arrange + let current = current("critical", true, false); + + // Act + let info = to_info(¤t); + + // Assert + assert_eq!(info.address, current.address); + assert_eq!( + info.revision, + format!("1800000000:{}", current.announcement.event_id.to_hex()) + ); + assert_eq!(info.created_at, 1_800_000_000); + assert_eq!(info.expiration, 1_900_000_000); + assert_eq!(info.severity, AnnouncementSeverity::Critical); + assert_eq!(info.url.as_deref(), Some("https://mostro.network/blog")); + assert!(info.read); + assert!(!info.dismissed); + let locales: Vec<&str> = info.texts.iter().map(|t| t.locale.as_str()).collect(); + assert_eq!(locales, announcements::SUPPORTED_LOCALES); + assert_eq!(info.texts[1].title, "T es"); + } + + #[test] + fn each_severity_maps_to_its_own_level() { + for (token, level) in [ + ("info", AnnouncementSeverity::Info), + ("warning", AnnouncementSeverity::Warning), + ("critical", AnnouncementSeverity::Critical), + ] { + assert_eq!(to_info(¤t(token, false, false)).severity, level); + } + } + + #[tokio::test] + async fn an_emit_reaches_a_subscriber_whole() { + // Arrange: subscribed before the emit, as Dart must be. + let mut stream = on_announcements_changed(); + let list = vec![current("info", false, true)]; + + // Act + emit(&list); + + // Assert: other tests emit too, so read until this list arrives. + loop { + let got = stream.next().await.unwrap(); + if got.len() == 1 && got[0].address == list[0].address { + assert!(got[0].dismissed); + break; + } + } + } + + #[test] + fn the_toggle_defaults_to_on() { + assert!(announcement_reader::enabled_from_setting(None)); + assert!(announcement_reader::enabled_from_setting(Some("true"))); + assert!(!announcement_reader::enabled_from_setting(Some("false"))); + } +} diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index 3b2dc5324..f7beb7ae1 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -1,3 +1,4 @@ +pub mod announcements; pub mod bond; pub mod bridge_handler; pub mod cashu; diff --git a/rust/src/api/types.rs b/rust/src/api/types.rs index e55f88ee6..2e26c1d45 100644 --- a/rust/src/api/types.rs +++ b/rust/src/api/types.rs @@ -172,6 +172,54 @@ impl PushPlatform { } } +/// How loudly an announcement is painted (specs/006-announcement-channel +/// §3.4). The publisher declares a level, never a colour; Dart maps it onto +/// the palette. A level this build does not know arrives as `Warning`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum AnnouncementSeverity { + /// Releases, new features, events. + Info, + /// Outages, a relay being retired, anything with a deadline. + Warning, + /// A security issue the user must act on now. + Critical, +} + +/// One locale's copy of an announcement, plain text (§3.2). +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct AnnouncementText { + /// `en`, `es`, … — one of the app's locales. + pub locale: String, + pub title: String, + pub body: String, +} + +/// A project announcement the reader admitted (§5.2), with every locale: Rust +/// does not translate, Dart picks the copy for the locale on screen. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct AnnouncementInfo { + /// `38387::` — the announcement's identity across + /// revisions, and the id of its notification card. + pub address: String, + /// `:`: changes with every correction published + /// under the same address, which is what makes it unread again (§5.3). + pub revision: String, + /// Unix seconds, the event's `created_at`. + pub created_at: i64, + /// Unix seconds, the NIP-40 `expiration`: the announcement is gone once + /// it passes, online or not (§5.5). + pub expiration: i64, + pub severity: AnnouncementSeverity, + /// Every supported locale, in the app's order. + pub texts: Vec, + /// The one action link, `https` only, as parsed. + pub url: Option, + pub read: bool, + /// Swiped away. A `Critical` stays listed, read, until it expires; the + /// other levels leave the list (§3.4). + pub dismissed: bool, +} + /// What the notification settings screen shows about push registration /// (docs/PUSH_NOTIFICATIONS.md §8.1, §9.1). Capability (can this platform /// push at all) and permission are Dart's to know; this is the token and diff --git a/rust/src/db/mod.rs b/rust/src/db/mod.rs index 441242a98..a309fc7ab 100644 --- a/rust/src/db/mod.rs +++ b/rust/src/db/mod.rs @@ -85,6 +85,13 @@ pub mod settings_keys { /// unix seconds of the `403`; each entry clears per §7.1. pub const PUSH_NODE_REFUSALS: &str = "push_node_refusals"; + // ── Announcements (specs/006-announcement-channel §7) ──────────────────── + + /// The Announcements toggle, `"true"` / `"false"`; absent reads as + /// enabled. Device-scoped, like the push toggle: it says whether this + /// install opens the announcement subscription, not anything about a user. + pub const ANNOUNCEMENTS_ENABLED: &str = "announcements_enabled"; + /// User-added Mostro nodes, JSON array of `crate::api::nodes::CustomNode`. /// The trusted registry is compiled in (`crate::config::TRUSTED_MOSTRO_NODES`); /// only user additions are persisted. diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index f5fdc382a..f9fc3c2be 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -25,6 +25,7 @@ // Section: imports +use crate::api::announcements::*; use crate::api::bond::*; use crate::api::cashu::*; use crate::api::disputes::*; @@ -53,7 +54,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1544659396; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -703973155; // Section: executor @@ -61,6 +62,64 @@ pub use crate::api::bridge_handler::FLUTTER_RUST_BRIDGE_HANDLER; // Section: wire_funcs +fn wire__crate__api__announcements__AnnouncementsStream_next_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "AnnouncementsStream_next", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, true, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref_mut().await) + } + _ => unreachable!(), + } + } + let mut api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::announcements::AnnouncementsStream::next( + &mut *api_that_guard, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__messages__AnyMessageStream_next_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -3243,6 +3302,43 @@ fn wire__crate__api__nwc__disconnect_wallet_impl( }, ) } +fn wire__crate__api__announcements__dismiss_announcement_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "dismiss_announcement", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_address = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::api::announcements::dismiss_announcement(api_address).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__messages__download_attachment_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -3653,6 +3749,77 @@ fn wire__crate__api__identity__funds_at_risk_impl( }, ) } +fn wire__crate__api__announcements__get_announcements_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_announcements", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::api::announcements::get_announcements().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__announcements__get_announcements_enabled_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_announcements_enabled", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::api::announcements::get_announcements_enabled().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__get_app_version_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -5312,6 +5479,43 @@ fn wire__crate__api__nwc__make_invoice_impl( }, ) } +fn wire__crate__api__announcements__mark_announcement_read_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "mark_announcement_read", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_address = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::api::announcements::mark_announcement_read(api_address).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__messages__mark_as_read_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -5381,6 +5585,39 @@ fn wire__crate__api__nodes__node_metadata_default_impl( }, ) } +fn wire__crate__api__announcements__on_announcements_changed_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "on_announcements_changed", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::announcements::on_announcements_changed())?; + Ok(output_ok) + })()) + } + }, + ) +} fn wire__crate__api__messages__on_any_new_message_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -7416,6 +7653,44 @@ fn wire__crate__api__settings__set_active_mostro_node_impl( }, ) } +fn wire__crate__api__announcements__set_announcements_enabled_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "set_announcements_enabled", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_enabled = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = + crate::api::announcements::set_announcements_enabled(api_enabled) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__escrow__set_cashu_mint_url_override_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -8318,6 +8593,9 @@ fn wire__crate__api__identity__validate_mnemonic_impl( // Section: related_funcs +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); flutter_rust_bridge::frb_generated_moi_arc_impl_value!( flutter_rust_bridge::for_generated::RustAutoOpaqueInner ); @@ -8407,6 +8685,16 @@ impl SseDecode for flutter_rust_bridge::for_generated::anyhow::Error { } } +impl SseDecode for AnnouncementsStream { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + impl SseDecode for AnyMessageStream { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -8667,6 +8955,16 @@ impl SseDecode for WalletStatusStream { } } +impl SseDecode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + impl SseDecode for RustOpaqueMoi> { @@ -8941,6 +9239,59 @@ impl SseDecode for String { } } +impl SseDecode for crate::api::types::AnnouncementInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_address = ::sse_decode(deserializer); + let mut var_revision = ::sse_decode(deserializer); + let mut var_createdAt = ::sse_decode(deserializer); + let mut var_expiration = ::sse_decode(deserializer); + let mut var_severity = ::sse_decode(deserializer); + let mut var_texts = >::sse_decode(deserializer); + let mut var_url = >::sse_decode(deserializer); + let mut var_read = ::sse_decode(deserializer); + let mut var_dismissed = ::sse_decode(deserializer); + return crate::api::types::AnnouncementInfo { + address: var_address, + revision: var_revision, + created_at: var_createdAt, + expiration: var_expiration, + severity: var_severity, + texts: var_texts, + url: var_url, + read: var_read, + dismissed: var_dismissed, + }; + } +} + +impl SseDecode for crate::api::types::AnnouncementSeverity { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return match inner { + 0 => crate::api::types::AnnouncementSeverity::Info, + 1 => crate::api::types::AnnouncementSeverity::Warning, + 2 => crate::api::types::AnnouncementSeverity::Critical, + _ => unreachable!("Invalid variant for AnnouncementSeverity: {}", inner), + }; + } +} + +impl SseDecode for crate::api::types::AnnouncementText { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_locale = ::sse_decode(deserializer); + let mut var_title = ::sse_decode(deserializer); + let mut var_body = ::sse_decode(deserializer); + return crate::api::types::AnnouncementText { + locale: var_locale, + title: var_title, + body: var_body, + }; + } +} + impl SseDecode for crate::api::types::AppSettings { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -9653,6 +10004,34 @@ impl SseDecode for Vec { } } +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode( + deserializer, + )); + } + return ans_; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode( + deserializer, + )); + } + return ans_; + } +} + impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -11292,607 +11671,651 @@ fn pde_ffi_dispatcher_primary_impl( ) { // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { - 1 => wire__crate__api__messages__AnyMessageStream_next_impl( + 1 => wire__crate__api__announcements__AnnouncementsStream_next_impl( port, ptr, rust_vec_len, data_len, ), - 2 => wire__crate__api__messages__AttachmentProgressStream_next_impl( + 2 => wire__crate__api__messages__AnyMessageStream_next_impl( port, ptr, rust_vec_len, data_len, ), - 3 => wire__crate__api__bond__BondClaimStream_next_impl(port, ptr, rust_vec_len, data_len), - 4 => wire__crate__api__bond__BondSlashedStream_next_impl(port, ptr, rust_vec_len, data_len), - 5 => { + 3 => wire__crate__api__messages__AttachmentProgressStream_next_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 4 => wire__crate__api__bond__BondClaimStream_next_impl(port, ptr, rust_vec_len, data_len), + 5 => wire__crate__api__bond__BondSlashedStream_next_impl(port, ptr, rust_vec_len, data_len), + 6 => { wire__crate__api__cashu__CashuWalletStream_next_impl(port, ptr, rust_vec_len, data_len) } - 6 => wire__crate__api__nostr__ConnectionStateStream_next_impl( + 7 => wire__crate__api__nostr__ConnectionStateStream_next_impl( port, ptr, rust_vec_len, data_len, ), - 7 => wire__crate__api__disputes__DisputeStream_next_impl(port, ptr, rust_vec_len, data_len), - 8 => { + 8 => wire__crate__api__disputes__DisputeStream_next_impl(port, ptr, rust_vec_len, data_len), + 9 => { wire__crate__api__escrow__EscrowModeStream_next_impl(port, ptr, rust_vec_len, data_len) } - 9 => wire__crate__api__logging__LogEntryStream_next_impl(port, ptr, rust_vec_len, data_len), 10 => { + wire__crate__api__logging__LogEntryStream_next_impl(port, ptr, rust_vec_len, data_len) + } + 11 => { wire__crate__api__messages__MessageStream_next_impl(port, ptr, rust_vec_len, data_len) } - 11 => wire__crate__api__messages__MessageUpdateStream_next_impl( + 12 => wire__crate__api__messages__MessageUpdateStream_next_impl( port, ptr, rust_vec_len, data_len, ), - 12 => wire__crate__api__my_reputation__MyReputationStream_next_impl( + 13 => wire__crate__api__my_reputation__MyReputationStream_next_impl( port, ptr, rust_vec_len, data_len, ), - 13 => wire__crate__api__orders__OrderBook_clear_impl(port, ptr, rust_vec_len, data_len), - 14 => wire__crate__api__orders__OrderBook_default_impl(port, ptr, rust_vec_len, data_len), - 15 => wire__crate__api__orders__OrderBook_get_order_impl(port, ptr, rust_vec_len, data_len), - 16 => { + 14 => wire__crate__api__orders__OrderBook_clear_impl(port, ptr, rust_vec_len, data_len), + 15 => wire__crate__api__orders__OrderBook_default_impl(port, ptr, rust_vec_len, data_len), + 16 => wire__crate__api__orders__OrderBook_get_order_impl(port, ptr, rust_vec_len, data_len), + 17 => { wire__crate__api__orders__OrderBook_get_orders_impl(port, ptr, rust_vec_len, data_len) } - 17 => wire__crate__api__orders__OrderBook_new_impl(port, ptr, rust_vec_len, data_len), - 18 => { + 18 => wire__crate__api__orders__OrderBook_new_impl(port, ptr, rust_vec_len, data_len), + 19 => { wire__crate__api__orders__OrderBook_remove_order_impl(port, ptr, rust_vec_len, data_len) } - 19 => { + 20 => { wire__crate__api__orders__OrderBook_set_orders_impl(port, ptr, rust_vec_len, data_len) } - 20 => wire__crate__api__orders__OrderBook_update_order_status_impl( + 21 => wire__crate__api__orders__OrderBook_update_order_status_impl( port, ptr, rust_vec_len, data_len, ), - 21 => { + 22 => { wire__crate__api__orders__OrderBook_upsert_order_impl(port, ptr, rust_vec_len, data_len) } - 22 => { + 23 => { wire__crate__api__orders__OrderDeltaStream_next_impl(port, ptr, rust_vec_len, data_len) } - 23 => wire__crate__api__orders__OrdersStream_next_impl(port, ptr, rust_vec_len, data_len), - 24 => wire__crate__api__push__PushStatusStream_next_impl(port, ptr, rust_vec_len, data_len), - 25 => { + 24 => wire__crate__api__orders__OrdersStream_next_impl(port, ptr, rust_vec_len, data_len), + 25 => wire__crate__api__push__PushStatusStream_next_impl(port, ptr, rust_vec_len, data_len), + 26 => { wire__crate__api__reputation__RatingStream_next_impl(port, ptr, rust_vec_len, data_len) } - 26 => wire__crate__api__nostr__RelayAutoSyncStream_next_impl( + 27 => wire__crate__api__nostr__RelayAutoSyncStream_next_impl( port, ptr, rust_vec_len, data_len, ), - 27 => { + 28 => { wire__crate__api__nostr__RelayStatusStream_next_impl(port, ptr, rust_vec_len, data_len) } - 28 => wire__crate__api__restore_progress__RestoreProgressStream_next_impl( + 29 => wire__crate__api__restore_progress__RestoreProgressStream_next_impl( port, ptr, rust_vec_len, data_len, ), - 29 => { + 30 => { wire__crate__api__settings__SettingsStream_next_impl(port, ptr, rust_vec_len, data_len) } - 30 => wire__crate__api__identity__TradeKeyIndexStream_next_impl( + 31 => wire__crate__api__identity__TradeKeyIndexStream_next_impl( port, ptr, rust_vec_len, data_len, ), - 31 => wire__crate__api__trade_touch__TradeTouchStream_next_impl( + 32 => wire__crate__api__trade_touch__TradeTouchStream_next_impl( port, ptr, rust_vec_len, data_len, ), - 32 => wire__crate__api__orders__TradeUpdatesStream_next_impl( + 33 => wire__crate__api__orders__TradeUpdatesStream_next_impl( port, ptr, rust_vec_len, data_len, ), - 33 => wire__crate__api__messages__UnreadCountStream_next_impl( + 34 => wire__crate__api__messages__UnreadCountStream_next_impl( port, ptr, rust_vec_len, data_len, ), - 34 => { + 35 => { wire__crate__api__nwc__WalletStatusStream_next_impl(port, ptr, rust_vec_len, data_len) } - 35 => wire__crate__api__bond__abandon_bonded_order_impl(port, ptr, rust_vec_len, data_len), - 36 => { + 36 => wire__crate__api__bond__abandon_bonded_order_impl(port, ptr, rust_vec_len, data_len), + 37 => { wire__crate__api__nodes__add_custom_mostro_node_impl(port, ptr, rust_vec_len, data_len) } - 37 => wire__crate__api__nostr__add_relay_impl(port, ptr, rust_vec_len, data_len), - 38 => { + 38 => wire__crate__api__nostr__add_relay_impl(port, ptr, rust_vec_len, data_len), + 39 => { wire__crate__api__messages__attachment_web_probe_impl(port, ptr, rust_vec_len, data_len) } - 39 => wire__crate__api__types__bond_claim_key_impl(port, ptr, rust_vec_len, data_len), - 40 => wire__crate__api__types__bond_claim_phase_is_terminal_impl( + 40 => wire__crate__api__types__bond_claim_key_impl(port, ptr, rust_vec_len, data_len), + 41 => wire__crate__api__types__bond_claim_phase_is_terminal_impl( port, ptr, rust_vec_len, data_len, ), - 41 => { + 42 => { wire__crate__api__types__bond_claim_storage_id_impl(port, ptr, rust_vec_len, data_len) } - 42 => wire__crate__api__types__bond_policy_default_impl(port, ptr, rust_vec_len, data_len), - 43 => wire__crate__api__types__bond_policy_info_default_impl( + 43 => wire__crate__api__types__bond_policy_default_impl(port, ptr, rust_vec_len, data_len), + 44 => wire__crate__api__types__bond_policy_info_default_impl( port, ptr, rust_vec_len, data_len, ), - 44 => wire__crate__api__node_stats__cached_mostro_node_stats_impl( + 45 => wire__crate__api__node_stats__cached_mostro_node_stats_impl( port, ptr, rust_vec_len, data_len, ), - 45 => wire__crate__api__my_reputation__cached_my_reputation_impl( + 46 => wire__crate__api__my_reputation__cached_my_reputation_impl( port, ptr, rust_vec_len, data_len, ), - 46 => wire__crate__api__orders__cancel_order_impl(port, ptr, rust_vec_len, data_len), - 47 => wire__crate__api__cashu__cashu_connect_impl(port, ptr, rust_vec_len, data_len), - 48 => wire__crate__api__cashu__cashu_create_token_impl(port, ptr, rust_vec_len, data_len), - 49 => wire__crate__api__cashu__cashu_disconnect_impl(port, ptr, rust_vec_len, data_len), - 50 => wire__crate__api__cashu__cashu_escrow_quote_impl(port, ptr, rust_vec_len, data_len), - 51 => wire__crate__api__cashu__cashu_get_balance_impl(port, ptr, rust_vec_len, data_len), - 52 => wire__crate__api__cashu__cashu_melt_impl(port, ptr, rust_vec_len, data_len), - 53 => wire__crate__api__cashu__cashu_melt_quote_impl(port, ptr, rust_vec_len, data_len), - 54 => wire__crate__api__cashu__cashu_receive_token_impl(port, ptr, rust_vec_len, data_len), - 55 => wire__crate__api__cashu__cashu_status_impl(port, ptr, rust_vec_len, data_len), - 56 => wire__crate__api__cashu__cashu_sweep_spent_proofs_impl( + 47 => wire__crate__api__orders__cancel_order_impl(port, ptr, rust_vec_len, data_len), + 48 => wire__crate__api__cashu__cashu_connect_impl(port, ptr, rust_vec_len, data_len), + 49 => wire__crate__api__cashu__cashu_create_token_impl(port, ptr, rust_vec_len, data_len), + 50 => wire__crate__api__cashu__cashu_disconnect_impl(port, ptr, rust_vec_len, data_len), + 51 => wire__crate__api__cashu__cashu_escrow_quote_impl(port, ptr, rust_vec_len, data_len), + 52 => wire__crate__api__cashu__cashu_get_balance_impl(port, ptr, rust_vec_len, data_len), + 53 => wire__crate__api__cashu__cashu_melt_impl(port, ptr, rust_vec_len, data_len), + 54 => wire__crate__api__cashu__cashu_melt_quote_impl(port, ptr, rust_vec_len, data_len), + 55 => wire__crate__api__cashu__cashu_receive_token_impl(port, ptr, rust_vec_len, data_len), + 56 => wire__crate__api__cashu__cashu_status_impl(port, ptr, rust_vec_len, data_len), + 57 => wire__crate__api__cashu__cashu_sweep_spent_proofs_impl( port, ptr, rust_vec_len, data_len, ), - 57 => { + 58 => { wire__crate__api__invoice__check_buyer_invoice_impl(port, ptr, rust_vec_len, data_len) } - 58 => wire__crate__api__reputation_transfer__check_reputation_import_impl( + 59 => wire__crate__api__reputation_transfer__check_reputation_import_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 60 => wire__crate__api__invoice__classify_payment_destination_impl( port, ptr, rust_vec_len, data_len, ), - 59 => wire__crate__api__invoice__classify_payment_destination_impl( + 61 => wire__crate__api__logging__clear_logs_impl(port, ptr, rust_vec_len, data_len), + 62 => wire__crate__api__push__clear_push_token_impl(port, ptr, rust_vec_len, data_len), + 63 => wire__crate__api__bond__close_expired_bond_window_impl( port, ptr, rust_vec_len, data_len, ), - 60 => wire__crate__api__logging__clear_logs_impl(port, ptr, rust_vec_len, data_len), - 61 => wire__crate__api__push__clear_push_token_impl(port, ptr, rust_vec_len, data_len), - 62 => wire__crate__api__bond__close_expired_bond_window_impl( + 64 => wire__crate__api__nwc__connect_wallet_impl(port, ptr, rust_vec_len, data_len), + 65 => wire__crate__api__identity__create_identity_impl(port, ptr, rust_vec_len, data_len), + 66 => wire__crate__api__orders__create_order_impl(port, ptr, rust_vec_len, data_len), + 67 => wire__crate__api__invoice__decode_bolt11_impl(port, ptr, rust_vec_len, data_len), + 68 => wire__crate__api__identity__delete_identity_impl(port, ptr, rust_vec_len, data_len), + 69 => wire__crate__api__identity__derive_trade_key_impl(port, ptr, rust_vec_len, data_len), + 70 => wire__crate__api__nwc__disconnect_wallet_impl(port, ptr, rust_vec_len, data_len), + 71 => wire__crate__api__announcements__dismiss_announcement_impl( port, ptr, rust_vec_len, data_len, ), - 63 => wire__crate__api__nwc__connect_wallet_impl(port, ptr, rust_vec_len, data_len), - 64 => wire__crate__api__identity__create_identity_impl(port, ptr, rust_vec_len, data_len), - 65 => wire__crate__api__orders__create_order_impl(port, ptr, rust_vec_len, data_len), - 66 => wire__crate__api__invoice__decode_bolt11_impl(port, ptr, rust_vec_len, data_len), - 67 => wire__crate__api__identity__delete_identity_impl(port, ptr, rust_vec_len, data_len), - 68 => wire__crate__api__identity__derive_trade_key_impl(port, ptr, rust_vec_len, data_len), - 69 => wire__crate__api__nwc__disconnect_wallet_impl(port, ptr, rust_vec_len, data_len), - 70 => { + 72 => { wire__crate__api__messages__download_attachment_impl(port, ptr, rust_vec_len, data_len) } - 71 => wire__crate__api__bond__estimate_bond_sats_impl(port, ptr, rust_vec_len, data_len), - 72 => wire__crate__api__identity__export_encrypted_backup_impl( + 73 => wire__crate__api__bond__estimate_bond_sats_impl(port, ptr, rust_vec_len, data_len), + 74 => wire__crate__api__identity__export_encrypted_backup_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 75 => wire__crate__api__reputation_transfer__export_reputation_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 76 => wire__crate__api__nostr__fetch_exchange_rate_impl(port, ptr, rust_vec_len, data_len), + 77 => wire__crate__api__nostr__fetch_mostro_instance_tags_impl( port, ptr, rust_vec_len, data_len, ), - 73 => wire__crate__api__reputation_transfer__export_reputation_impl( + 78 => wire__crate__api__node_stats__fetch_mostro_node_stats_impl( port, ptr, rust_vec_len, data_len, ), - 74 => wire__crate__api__nostr__fetch_exchange_rate_impl(port, ptr, rust_vec_len, data_len), - 75 => wire__crate__api__nostr__fetch_mostro_instance_tags_impl( + 79 => wire__crate__api__nostr__fetch_price_sources_impl(port, ptr, rust_vec_len, data_len), + 80 => wire__crate__api__nostr__flush_message_queue_impl(port, ptr, rust_vec_len, data_len), + 81 => wire__crate__api__nodes__forget_mostro_node_picture_impl( port, ptr, rust_vec_len, data_len, ), - 76 => wire__crate__api__node_stats__fetch_mostro_node_stats_impl( + 82 => wire__crate__api__identity__funds_at_risk_impl(port, ptr, rust_vec_len, data_len), + 83 => wire__crate__api__announcements__get_announcements_impl( port, ptr, rust_vec_len, data_len, ), - 77 => wire__crate__api__nostr__fetch_price_sources_impl(port, ptr, rust_vec_len, data_len), - 78 => wire__crate__api__nostr__flush_message_queue_impl(port, ptr, rust_vec_len, data_len), - 79 => wire__crate__api__nodes__forget_mostro_node_picture_impl( + 84 => wire__crate__api__announcements__get_announcements_enabled_impl( port, ptr, rust_vec_len, data_len, ), - 80 => wire__crate__api__identity__funds_at_risk_impl(port, ptr, rust_vec_len, data_len), - 81 => wire__crate__api__get_app_version_impl(port, ptr, rust_vec_len, data_len), - 82 => wire__crate__api__messages__get_attachment_status_impl( + 85 => wire__crate__api__get_app_version_impl(port, ptr, rust_vec_len, data_len), + 86 => wire__crate__api__messages__get_attachment_status_impl( port, ptr, rust_vec_len, data_len, ), - 83 => wire__crate__api__nwc__get_balance_impl(port, ptr, rust_vec_len, data_len), - 84 => wire__crate__api__bond__get_bond_claim_impl(port, ptr, rust_vec_len, data_len), - 85 => wire__crate__api__bond__get_bond_claim_from_impl(port, ptr, rust_vec_len, data_len), - 86 => wire__crate__api__bond__get_bond_policy_impl(port, ptr, rust_vec_len, data_len), - 87 => wire__crate__api__nostr__get_connection_state_impl(port, ptr, rust_vec_len, data_len), - 88 => wire__crate__api__disputes__get_dispute_impl(port, ptr, rust_vec_len, data_len), - 89 => wire__crate__api__escrow__get_escrow_mode_impl(port, ptr, rust_vec_len, data_len), - 90 => wire__crate__api__identity__get_identity_impl(port, ptr, rust_vec_len, data_len), - 91 => wire__crate__api__messages__get_messages_impl(port, ptr, rust_vec_len, data_len), - 92 => { + 87 => wire__crate__api__nwc__get_balance_impl(port, ptr, rust_vec_len, data_len), + 88 => wire__crate__api__bond__get_bond_claim_impl(port, ptr, rust_vec_len, data_len), + 89 => wire__crate__api__bond__get_bond_claim_from_impl(port, ptr, rust_vec_len, data_len), + 90 => wire__crate__api__bond__get_bond_policy_impl(port, ptr, rust_vec_len, data_len), + 91 => wire__crate__api__nostr__get_connection_state_impl(port, ptr, rust_vec_len, data_len), + 92 => wire__crate__api__disputes__get_dispute_impl(port, ptr, rust_vec_len, data_len), + 93 => wire__crate__api__escrow__get_escrow_mode_impl(port, ptr, rust_vec_len, data_len), + 94 => wire__crate__api__identity__get_identity_impl(port, ptr, rust_vec_len, data_len), + 95 => wire__crate__api__messages__get_messages_impl(port, ptr, rust_vec_len, data_len), + 96 => { wire__crate__api__nodes__get_mostro_node_picture_impl(port, ptr, rust_vec_len, data_len) } - 93 => wire__crate__api__settings__get_mostro_pubkey_impl(port, ptr, rust_vec_len, data_len), - 94 => wire__crate__api__identity__get_nym_identity_impl(port, ptr, rust_vec_len, data_len), - 95 => wire__crate__api__orders__get_order_impl(port, ptr, rust_vec_len, data_len), - 96 => wire__crate__api__orders__get_order_book_snapshot_impl( + 97 => wire__crate__api__settings__get_mostro_pubkey_impl(port, ptr, rust_vec_len, data_len), + 98 => wire__crate__api__identity__get_nym_identity_impl(port, ptr, rust_vec_len, data_len), + 99 => wire__crate__api__orders__get_order_impl(port, ptr, rust_vec_len, data_len), + 100 => wire__crate__api__orders__get_order_book_snapshot_impl( port, ptr, rust_vec_len, data_len, ), - 97 => wire__crate__api__orders__get_orders_impl(port, ptr, rust_vec_len, data_len), - 98 => wire__crate__api__reputation_transfer__get_pending_reputation_attestation_impl( + 101 => wire__crate__api__orders__get_orders_impl(port, ptr, rust_vec_len, data_len), + 102 => wire__crate__api__reputation_transfer__get_pending_reputation_attestation_impl( port, ptr, rust_vec_len, data_len, ), - 99 => { + 103 => { wire__crate__api__reputation__get_privacy_mode_impl(port, ptr, rust_vec_len, data_len) } - 100 => wire__crate__api__push__get_push_status_impl(port, ptr, rust_vec_len, data_len), - 101 => wire__crate__api__reputation__get_rating_for_trade_impl( + 104 => wire__crate__api__push__get_push_status_impl(port, ptr, rust_vec_len, data_len), + 105 => wire__crate__api__reputation__get_rating_for_trade_impl( port, ptr, rust_vec_len, data_len, ), - 102 => wire__crate__api__nostr__get_relays_impl(port, ptr, rust_vec_len, data_len), - 104 => wire__crate__api__settings__get_settings_impl(port, ptr, rust_vec_len, data_len), - 105 => wire__crate__api__identity__get_trade_key_impl(port, ptr, rust_vec_len, data_len), - 106 => wire__crate__api__orders__get_trade_role_impl(port, ptr, rust_vec_len, data_len), - 107 => wire__crate__api__messages__get_unread_count_impl(port, ptr, rust_vec_len, data_len), - 108 => wire__crate__api__nwc__get_wallet_impl(port, ptr, rust_vec_len, data_len), - 109 => wire__crate__api__disputes__handle_admin_canceled_impl( + 106 => wire__crate__api__nostr__get_relays_impl(port, ptr, rust_vec_len, data_len), + 108 => wire__crate__api__settings__get_settings_impl(port, ptr, rust_vec_len, data_len), + 109 => wire__crate__api__identity__get_trade_key_impl(port, ptr, rust_vec_len, data_len), + 110 => wire__crate__api__orders__get_trade_role_impl(port, ptr, rust_vec_len, data_len), + 111 => wire__crate__api__messages__get_unread_count_impl(port, ptr, rust_vec_len, data_len), + 112 => wire__crate__api__nwc__get_wallet_impl(port, ptr, rust_vec_len, data_len), + 113 => wire__crate__api__disputes__handle_admin_canceled_impl( port, ptr, rust_vec_len, data_len, ), - 110 => { + 114 => { wire__crate__api__disputes__handle_admin_settled_impl(port, ptr, rust_vec_len, data_len) } - 111 => wire__crate__api__disputes__handle_admin_took_dispute_impl( + 115 => wire__crate__api__disputes__handle_admin_took_dispute_impl( port, ptr, rust_vec_len, data_len, ), - 112 => wire__crate__api__reputation__handle_rating_received_impl( + 116 => wire__crate__api__reputation__handle_rating_received_impl( port, ptr, rust_vec_len, data_len, ), - 113 => wire__crate__api__identity__has_pending_identity_wipe_impl( + 117 => wire__crate__api__identity__has_pending_identity_wipe_impl( port, ptr, rust_vec_len, data_len, ), - 114 => { + 118 => { wire__crate__api__identity__import_from_mnemonic_impl(port, ptr, rust_vec_len, data_len) } - 115 => wire__crate__api__identity__import_from_nsec_impl(port, ptr, rust_vec_len, data_len), - 116 => wire__crate__api__reputation_transfer__import_reputation_impl( + 119 => wire__crate__api__identity__import_from_nsec_impl(port, ptr, rust_vec_len, data_len), + 120 => wire__crate__api__reputation_transfer__import_reputation_impl( port, ptr, rust_vec_len, data_len, ), - 117 => wire__crate__api__logging__init_app_impl(port, ptr, rust_vec_len, data_len), - 118 => wire__crate__api__init_db_impl(port, ptr, rust_vec_len, data_len), - 119 => wire__crate__api__nostr__initialize_impl(port, ptr, rust_vec_len, data_len), - 120 => { + 121 => wire__crate__api__logging__init_app_impl(port, ptr, rust_vec_len, data_len), + 122 => wire__crate__api__init_db_impl(port, ptr, rust_vec_len, data_len), + 123 => wire__crate__api__nostr__initialize_impl(port, ptr, rust_vec_len, data_len), + 124 => { wire__crate__api__logging__install_log_bridge_impl(port, ptr, rust_vec_len, data_len) } - 121 => wire__crate__api__bond__list_bond_claims_impl(port, ptr, rust_vec_len, data_len), - 122 => wire__crate__api__nodes__list_mostro_nodes_impl(port, ptr, rust_vec_len, data_len), - 123 => wire__crate__api__orders__list_trades_impl(port, ptr, rust_vec_len, data_len), - 124 => wire__crate__api__identity__load_identity_from_mnemonic_impl( + 125 => wire__crate__api__bond__list_bond_claims_impl(port, ptr, rust_vec_len, data_len), + 126 => wire__crate__api__nodes__list_mostro_nodes_impl(port, ptr, rust_vec_len, data_len), + 127 => wire__crate__api__orders__list_trades_impl(port, ptr, rust_vec_len, data_len), + 128 => wire__crate__api__identity__load_identity_from_mnemonic_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 129 => wire__crate__api__cashu__lock_escrow_impl(port, ptr, rust_vec_len, data_len), + 130 => wire__crate__api__nwc__make_invoice_impl(port, ptr, rust_vec_len, data_len), + 131 => wire__crate__api__announcements__mark_announcement_read_impl( port, ptr, rust_vec_len, data_len, ), - 125 => wire__crate__api__cashu__lock_escrow_impl(port, ptr, rust_vec_len, data_len), - 126 => wire__crate__api__nwc__make_invoice_impl(port, ptr, rust_vec_len, data_len), - 127 => wire__crate__api__messages__mark_as_read_impl(port, ptr, rust_vec_len, data_len), - 128 => { + 132 => wire__crate__api__messages__mark_as_read_impl(port, ptr, rust_vec_len, data_len), + 133 => { wire__crate__api__nodes__node_metadata_default_impl(port, ptr, rust_vec_len, data_len) } - 129 => { + 134 => wire__crate__api__announcements__on_announcements_changed_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 135 => { wire__crate__api__messages__on_any_new_message_impl(port, ptr, rust_vec_len, data_len) } - 130 => wire__crate__api__messages__on_attachment_progress_impl( + 136 => wire__crate__api__messages__on_attachment_progress_impl( port, ptr, rust_vec_len, data_len, ), - 131 => { + 137 => { wire__crate__api__bond__on_bond_claim_updated_impl(port, ptr, rust_vec_len, data_len) } - 132 => wire__crate__api__bond__on_bond_slashed_impl(port, ptr, rust_vec_len, data_len), - 133 => { + 138 => wire__crate__api__bond__on_bond_slashed_impl(port, ptr, rust_vec_len, data_len), + 139 => { wire__crate__api__cashu__on_cashu_wallet_changed_impl(port, ptr, rust_vec_len, data_len) } - 134 => wire__crate__api__nostr__on_connection_state_changed_impl( + 140 => wire__crate__api__nostr__on_connection_state_changed_impl( port, ptr, rust_vec_len, data_len, ), - 135 => { + 141 => { wire__crate__api__disputes__on_dispute_updated_impl(port, ptr, rust_vec_len, data_len) } - 136 => { + 142 => { wire__crate__api__escrow__on_escrow_mode_changed_impl(port, ptr, rust_vec_len, data_len) } - 137 => wire__crate__api__logging__on_log_entry_impl(port, ptr, rust_vec_len, data_len), - 138 => { + 143 => wire__crate__api__logging__on_log_entry_impl(port, ptr, rust_vec_len, data_len), + 144 => { wire__crate__api__messages__on_message_updated_impl(port, ptr, rust_vec_len, data_len) } - 139 => wire__crate__api__my_reputation__on_my_reputation_changed_impl( + 145 => wire__crate__api__my_reputation__on_my_reputation_changed_impl( port, ptr, rust_vec_len, data_len, ), - 140 => wire__crate__api__messages__on_new_message_impl(port, ptr, rust_vec_len, data_len), - 141 => wire__crate__api__orders__on_order_deltas_impl(port, ptr, rust_vec_len, data_len), - 142 => wire__crate__api__orders__on_orders_updated_impl(port, ptr, rust_vec_len, data_len), - 143 => { + 146 => wire__crate__api__messages__on_new_message_impl(port, ptr, rust_vec_len, data_len), + 147 => wire__crate__api__orders__on_order_deltas_impl(port, ptr, rust_vec_len, data_len), + 148 => wire__crate__api__orders__on_orders_updated_impl(port, ptr, rust_vec_len, data_len), + 149 => { wire__crate__api__push__on_push_status_changed_impl(port, ptr, rust_vec_len, data_len) } - 144 => { + 150 => { wire__crate__api__reputation__on_rating_received_impl(port, ptr, rust_vec_len, data_len) } - 145 => { + 151 => { wire__crate__api__nostr__on_relay_auto_synced_impl(port, ptr, rust_vec_len, data_len) } - 146 => { + 152 => { wire__crate__api__nostr__on_relay_status_changed_impl(port, ptr, rust_vec_len, data_len) } - 147 => wire__crate__api__restore_progress__on_restore_progress_impl( + 153 => wire__crate__api__restore_progress__on_restore_progress_impl( port, ptr, rust_vec_len, data_len, ), - 148 => { + 154 => { wire__crate__api__settings__on_settings_changed_impl(port, ptr, rust_vec_len, data_len) } - 149 => wire__crate__api__identity__on_trade_key_index_changed_impl( + 155 => wire__crate__api__identity__on_trade_key_index_changed_impl( port, ptr, rust_vec_len, data_len, ), - 150 => { + 156 => { wire__crate__api__trade_touch__on_trade_touched_impl(port, ptr, rust_vec_len, data_len) } - 151 => wire__crate__api__orders__on_trade_updated_impl(port, ptr, rust_vec_len, data_len), - 152 => wire__crate__api__messages__on_unread_count_changed_impl( + 157 => wire__crate__api__orders__on_trade_updated_impl(port, ptr, rust_vec_len, data_len), + 158 => wire__crate__api__messages__on_unread_count_changed_impl( port, ptr, rust_vec_len, data_len, ), - 153 => { + 159 => { wire__crate__api__nwc__on_wallet_status_changed_impl(port, ptr, rust_vec_len, data_len) } - 154 => wire__crate__api__disputes__open_dispute_impl(port, ptr, rust_vec_len, data_len), - 155 => { + 160 => wire__crate__api__disputes__open_dispute_impl(port, ptr, rust_vec_len, data_len), + 161 => { wire__crate__api__orders__order_filters_default_impl(port, ptr, rust_vec_len, data_len) } - 157 => wire__crate__api__nwc__pay_invoice_impl(port, ptr, rust_vec_len, data_len), - 158 => wire__crate__api__orders__payout_amount_impl(port, ptr, rust_vec_len, data_len), - 159 => { + 163 => wire__crate__api__nwc__pay_invoice_impl(port, ptr, rust_vec_len, data_len), + 164 => wire__crate__api__orders__payout_amount_impl(port, ptr, rust_vec_len, data_len), + 165 => { wire__crate__api__orders__payout_destinations_impl(port, ptr, rust_vec_len, data_len) } - 160 => { + 166 => { wire__crate__api__types__push_platform_as_wire_impl(port, ptr, rust_vec_len, data_len) } - 161 => { + 167 => { wire__crate__api__types__push_platform_from_wire_impl(port, ptr, rust_vec_len, data_len) } - 162 => wire__crate__api__logging__recent_logs_impl(port, ptr, rust_vec_len, data_len), - 163 => wire__crate__api__push__reconcile_push_impl(port, ptr, rust_vec_len, data_len), - 164 => wire__crate__api__orders__recover_trades_impl(port, ptr, rust_vec_len, data_len), - 165 => wire__crate__api__node_stats__refresh_mostro_node_info_cache_impl( + 168 => wire__crate__api__logging__recent_logs_impl(port, ptr, rust_vec_len, data_len), + 169 => wire__crate__api__push__reconcile_push_impl(port, ptr, rust_vec_len, data_len), + 170 => wire__crate__api__orders__recover_trades_impl(port, ptr, rust_vec_len, data_len), + 171 => wire__crate__api__node_stats__refresh_mostro_node_info_cache_impl( port, ptr, rust_vec_len, data_len, ), - 166 => wire__crate__api__nodes__refresh_mostro_node_metadata_impl( + 172 => wire__crate__api__nodes__refresh_mostro_node_metadata_impl( port, ptr, rust_vec_len, data_len, ), - 167 => wire__crate__api__my_reputation__refresh_my_reputation_impl( + 173 => wire__crate__api__my_reputation__refresh_my_reputation_impl( port, ptr, rust_vec_len, data_len, ), - 168 => wire__crate__api__settings__rehydrate_active_mostro_node_impl( + 174 => wire__crate__api__settings__rehydrate_active_mostro_node_impl( port, ptr, rust_vec_len, data_len, ), - 169 => wire__crate__api__escrow__rehydrate_escrow_overrides_impl( + 175 => wire__crate__api__escrow__rehydrate_escrow_overrides_impl( port, ptr, rust_vec_len, data_len, ), - 170 => wire__crate__api__orders__release_order_impl(port, ptr, rust_vec_len, data_len), - 171 => wire__crate__api__nodes__remove_custom_mostro_node_impl( + 176 => wire__crate__api__orders__release_order_impl(port, ptr, rust_vec_len, data_len), + 177 => wire__crate__api__nodes__remove_custom_mostro_node_impl( port, ptr, rust_vec_len, data_len, ), - 172 => wire__crate__api__nostr__remove_relay_impl(port, ptr, rust_vec_len, data_len), - 174 => wire__crate__api__orders__request_bond_invoice_again_impl( + 178 => wire__crate__api__nostr__remove_relay_impl(port, ptr, rust_vec_len, data_len), + 180 => wire__crate__api__orders__request_bond_invoice_again_impl( port, ptr, rust_vec_len, data_len, ), - 175 => { + 181 => { wire__crate__api__order_link__resolve_order_link_impl(port, ptr, rust_vec_len, data_len) } - 176 => wire__crate__api__orders__restart_orders_subscription_impl( + 182 => wire__crate__api__orders__restart_orders_subscription_impl( port, ptr, rust_vec_len, data_len, ), - 177 => wire__crate__api__identity__restore_identity_session_impl( + 183 => wire__crate__api__identity__restore_identity_session_impl( port, ptr, rust_vec_len, data_len, ), - 178 => wire__crate__api__nostr__resync_impl(port, ptr, rust_vec_len, data_len), - 179 => { + 184 => wire__crate__api__nostr__resync_impl(port, ptr, rust_vec_len, data_len), + 185 => { wire__crate__api__disputes__send_dispute_file_impl(port, ptr, rust_vec_len, data_len) } - 180 => wire__crate__api__orders__send_fiat_sent_impl(port, ptr, rust_vec_len, data_len), - 181 => wire__crate__api__messages__send_file_impl(port, ptr, rust_vec_len, data_len), - 182 => wire__crate__api__orders__send_invoice_impl(port, ptr, rust_vec_len, data_len), - 183 => wire__crate__api__messages__send_message_impl(port, ptr, rust_vec_len, data_len), - 184 => wire__crate__api__messages__send_reaction_impl(port, ptr, rust_vec_len, data_len), - 185 => wire__crate__api__settings__set_active_mostro_node_impl( + 186 => wire__crate__api__orders__send_fiat_sent_impl(port, ptr, rust_vec_len, data_len), + 187 => wire__crate__api__messages__send_file_impl(port, ptr, rust_vec_len, data_len), + 188 => wire__crate__api__orders__send_invoice_impl(port, ptr, rust_vec_len, data_len), + 189 => wire__crate__api__messages__send_message_impl(port, ptr, rust_vec_len, data_len), + 190 => wire__crate__api__messages__send_reaction_impl(port, ptr, rust_vec_len, data_len), + 191 => wire__crate__api__settings__set_active_mostro_node_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 192 => wire__crate__api__announcements__set_announcements_enabled_impl( port, ptr, rust_vec_len, data_len, ), - 186 => wire__crate__api__escrow__set_cashu_mint_url_override_impl( + 193 => wire__crate__api__escrow__set_cashu_mint_url_override_impl( port, ptr, rust_vec_len, data_len, ), - 187 => wire__crate__api__settings__set_default_fiat_code_impl( + 194 => wire__crate__api__settings__set_default_fiat_code_impl( port, ptr, rust_vec_len, data_len, ), - 188 => wire__crate__api__settings__set_default_lightning_address_impl( + 195 => wire__crate__api__settings__set_default_lightning_address_impl( port, ptr, rust_vec_len, data_len, ), - 189 => wire__crate__api__escrow__set_escrow_mode_override_impl( + 196 => wire__crate__api__escrow__set_escrow_mode_override_impl( port, ptr, rust_vec_len, data_len, ), - 190 => wire__crate__api__settings__set_language_impl(port, ptr, rust_vec_len, data_len), - 191 => { + 197 => wire__crate__api__settings__set_language_impl(port, ptr, rust_vec_len, data_len), + 198 => { wire__crate__api__settings__set_logging_enabled_impl(port, ptr, rust_vec_len, data_len) } - 192 => { + 199 => { wire__crate__api__reputation__set_privacy_mode_impl(port, ptr, rust_vec_len, data_len) } - 193 => wire__crate__api__push__set_push_enabled_impl(port, ptr, rust_vec_len, data_len), - 194 => wire__crate__api__push__set_push_token_impl(port, ptr, rust_vec_len, data_len), - 195 => wire__crate__api__settings__set_test_order_expiry_impl( + 200 => wire__crate__api__push__set_push_enabled_impl(port, ptr, rust_vec_len, data_len), + 201 => wire__crate__api__push__set_push_token_impl(port, ptr, rust_vec_len, data_len), + 202 => wire__crate__api__settings__set_test_order_expiry_impl( port, ptr, rust_vec_len, data_len, ), - 196 => wire__crate__api__settings__set_theme_impl(port, ptr, rust_vec_len, data_len), - 197 => wire__crate__api__disputes__share_chat_key_with_solver_impl( + 203 => wire__crate__api__settings__set_theme_impl(port, ptr, rust_vec_len, data_len), + 204 => wire__crate__api__disputes__share_chat_key_with_solver_impl( port, ptr, rust_vec_len, data_len, ), - 198 => wire__crate__api__reputation_transfer__sign_reputation_rebind_impl( + 205 => wire__crate__api__reputation_transfer__sign_reputation_rebind_impl( port, ptr, rust_vec_len, data_len, ), - 199 => wire__crate__api__disputes__solver_role_impl(port, ptr, rust_vec_len, data_len), - 200 => wire__crate__api__bond__submit_bond_payout_invoice_impl( + 206 => wire__crate__api__disputes__solver_role_impl(port, ptr, rust_vec_len, data_len), + 207 => wire__crate__api__bond__submit_bond_payout_invoice_impl( port, ptr, rust_vec_len, data_len, ), - 201 => wire__crate__api__disputes__submit_evidence_impl(port, ptr, rust_vec_len, data_len), - 202 => wire__crate__api__reputation__submit_rating_impl(port, ptr, rust_vec_len, data_len), - 203 => wire__crate__api__orders__subscribe_orders_impl(port, ptr, rust_vec_len, data_len), - 204 => wire__crate__api__nodes__sync_mostro_node_pictures_impl( + 208 => wire__crate__api__disputes__submit_evidence_impl(port, ptr, rust_vec_len, data_len), + 209 => wire__crate__api__reputation__submit_rating_impl(port, ptr, rust_vec_len, data_len), + 210 => wire__crate__api__orders__subscribe_orders_impl(port, ptr, rust_vec_len, data_len), + 211 => wire__crate__api__nodes__sync_mostro_node_pictures_impl( port, ptr, rust_vec_len, data_len, ), - 205 => wire__crate__api__orders__take_order_impl(port, ptr, rust_vec_len, data_len), - 206 => wire__crate__api__escrow_deadline__trade_escrow_deadline_impl( + 212 => wire__crate__api__orders__take_order_impl(port, ptr, rust_vec_len, data_len), + 213 => wire__crate__api__escrow_deadline__trade_escrow_deadline_impl( port, ptr, rust_vec_len, data_len, ), - 207 => wire__crate__api__orders__trade_node_of_impl(port, ptr, rust_vec_len, data_len), - 208 => { + 214 => wire__crate__api__orders__trade_node_of_impl(port, ptr, rust_vec_len, data_len), + 215 => { wire__crate__api__invoice__trade_step_started_at_impl(port, ptr, rust_vec_len, data_len) } - 209 => { + 216 => { wire__crate__api__identity__validate_mnemonic_impl(port, ptr, rust_vec_len, data_len) } _ => unreachable!(), @@ -11907,17 +12330,17 @@ fn pde_ffi_dispatcher_sync_impl( ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { - 103 => wire__crate__api__reputation_transfer__get_reputation_support_impl( + 107 => wire__crate__api__reputation_transfer__get_reputation_support_impl( ptr, rust_vec_len, data_len, ), - 156 => wire__crate__api__reputation_transfer__parse_reputation_attestation_impl( + 162 => wire__crate__api__reputation_transfer__parse_reputation_attestation_impl( ptr, rust_vec_len, data_len, ), - 173 => wire__crate__api__reputation_transfer__reputation_identity_npub_impl( + 179 => wire__crate__api__reputation_transfer__reputation_identity_npub_impl( ptr, rust_vec_len, data_len, @@ -11928,6 +12351,24 @@ fn pde_ffi_dispatcher_sync_impl( // Section: rust2dart +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} + +impl flutter_rust_bridge::IntoIntoDart> for AnnouncementsStream { + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + // Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for FrbWrapper { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { @@ -12351,6 +12792,78 @@ impl flutter_rust_bridge::IntoIntoDart> for Walle } } +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::types::AnnouncementInfo { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.address.into_into_dart().into_dart(), + self.revision.into_into_dart().into_dart(), + self.created_at.into_into_dart().into_dart(), + self.expiration.into_into_dart().into_dart(), + self.severity.into_into_dart().into_dart(), + self.texts.into_into_dart().into_dart(), + self.url.into_into_dart().into_dart(), + self.read.into_into_dart().into_dart(), + self.dismissed.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::types::AnnouncementInfo +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::types::AnnouncementInfo +{ + fn into_into_dart(self) -> crate::api::types::AnnouncementInfo { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::types::AnnouncementSeverity { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self { + Self::Info => 0.into_dart(), + Self::Warning => 1.into_dart(), + Self::Critical => 2.into_dart(), + _ => unreachable!(), + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::types::AnnouncementSeverity +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::types::AnnouncementSeverity +{ + fn into_into_dart(self) -> crate::api::types::AnnouncementSeverity { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::types::AnnouncementText { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.locale.into_into_dart().into_dart(), + self.title.into_into_dart().into_dart(), + self.body.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::types::AnnouncementText +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::types::AnnouncementText +{ + fn into_into_dart(self) -> crate::api::types::AnnouncementText { + self + } +} // Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::api::types::AppSettings { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { @@ -14400,6 +14913,13 @@ impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error { } } +impl SseEncode for AnnouncementsStream { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), serializer); + } +} + impl SseEncode for AnyMessageStream { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -14597,6 +15117,17 @@ impl SseEncode for WalletStatusStream { } } +impl SseEncode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + impl SseEncode for RustOpaqueMoi> { @@ -14896,6 +15427,47 @@ impl SseEncode for String { } } +impl SseEncode for crate::api::types::AnnouncementInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.address, serializer); + ::sse_encode(self.revision, serializer); + ::sse_encode(self.created_at, serializer); + ::sse_encode(self.expiration, serializer); + ::sse_encode(self.severity, serializer); + >::sse_encode(self.texts, serializer); + >::sse_encode(self.url, serializer); + ::sse_encode(self.read, serializer); + ::sse_encode(self.dismissed, serializer); + } +} + +impl SseEncode for crate::api::types::AnnouncementSeverity { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode( + match self { + crate::api::types::AnnouncementSeverity::Info => 0, + crate::api::types::AnnouncementSeverity::Warning => 1, + crate::api::types::AnnouncementSeverity::Critical => 2, + _ => { + unimplemented!(""); + } + }, + serializer, + ); + } +} + +impl SseEncode for crate::api::types::AnnouncementText { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.locale, serializer); + ::sse_encode(self.title, serializer); + ::sse_encode(self.body, serializer); + } +} + impl SseEncode for crate::api::types::AppSettings { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -15491,6 +16063,26 @@ impl SseEncode for Vec { } } +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + impl SseEncode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -16860,6 +17452,7 @@ mod io { // Section: imports use super::*; + use crate::api::announcements::*; use crate::api::bond::*; use crate::api::cashu::*; use crate::api::disputes::*; @@ -16886,6 +17479,20 @@ mod io { flutter_rust_bridge::frb_generated_boilerplate_io!(); + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_mostro_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_mostro_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_mostro_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( ptr: *const std::ffi::c_void, @@ -17262,6 +17869,7 @@ mod web { // Section: imports use super::*; + use crate::api::announcements::*; use crate::api::bond::*; use crate::api::cashu::*; use crate::api::disputes::*; @@ -17290,6 +17898,20 @@ mod web { flutter_rust_bridge::frb_generated_boilerplate_web!(); + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnnouncementsStream( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + #[wasm_bindgen] pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAnyMessageStream( ptr: *const std::ffi::c_void, diff --git a/rust/src/nostr/announcement_reader.rs b/rust/src/nostr/announcement_reader.rs index ed1665baa..0c439ea3b 100644 --- a/rust/src/nostr/announcement_reader.rs +++ b/rust/src/nostr/announcement_reader.rs @@ -19,7 +19,7 @@ use nostr_sdk::prelude::*; use semver::Version; use serde::{Deserialize, Serialize}; -use crate::db::Storage; +use crate::db::{settings_keys, Storage}; use crate::nostr::announcements::{ self, admit, Announcement, AnnouncementError, Severity, KIND_ANNOUNCEMENT, }; @@ -101,6 +101,17 @@ pub fn address_of(author: &PublicKey, identifier: &str) -> String { format!("{KIND_ANNOUNCEMENT}:{}:{identifier}", author.to_hex()) } +/// The revision of an announcement as Dart sees it: `created_at` and the +/// event id, the pair [`replaceable_rank`] orders by. Two revisions at one +/// address are the same text only when this is equal. +pub fn revision_of(announcement: &Announcement) -> String { + format!( + "{}:{}", + announcement.created_at.as_secs(), + announcement.event_id.to_hex() + ) +} + /// §5.2 steps 1–6, at `now`, for an app at `version`. Step 7 — winning at /// its address — needs the cache, and is [`ingest`]'s. pub fn door( @@ -308,6 +319,38 @@ async fn sweep( Ok(kept) } +/// Record what the user did with the announcement at `address` (§6.1): a +/// `read` and a `dismissed` flag, each left alone when `None`. The flags +/// belong to the cached revision — a correction arriving later starts +/// unread again. Returns `false` when the address is not cached: nothing +/// may refer to an address the cache no longer holds, and a flag for one +/// that just expired is not an error. +pub async fn set_flags( + db: &S, + address: &str, + read: Option, + dismissed: Option, +) -> anyhow::Result { + let _section = exclusive().await; + let Some(row) = db + .list_announcements() + .await? + .into_iter() + .find(|row| row.address == address) + else { + return Ok(false); + }; + let updated = StoredAnnouncement { + read: read.unwrap_or(row.read), + dismissed: dismissed.unwrap_or(row.dismissed), + ..row.clone() + }; + if updated != row { + db.save_announcement(&updated).await?; + } + Ok(true) +} + /// A cached row through the door again, from its signed event. fn recheck( row: &StoredAnnouncement, @@ -335,57 +378,135 @@ impl Drop for LoopGuard { } } -/// Bring the channel up to date: re-check the cache against the clock, then -/// start the relay loop or, when it already runs, move its filter's window -/// to now. Called on every `Online` and every resume, like the order book; -/// with an empty allowlist it does nothing at all. +/// The Announcements toggle (§7), mirrored from the settings table by +/// [`subscribe_announcements`] and [`set_enabled`], so the relay pump can +/// check it per event without a database read. Off means the subscription +/// is closed and whatever still arrives behind it is discarded. +static ENABLED: AtomicBool = AtomicBool::new(true); + +/// What a stored toggle value means: absent reads as enabled (§7, default on). +pub fn enabled_from_setting(value: Option<&str>) -> bool { + value != Some("false") +} + +/// Whether the channel is on, as of the last subscribe or toggle. +pub fn is_enabled() -> bool { + ENABLED.load(Ordering::Acquire) +} + +/// What a read of the stored toggle means. A read that failed is not consent: +/// the value may well be `"false"`, so the channel stays off until a later +/// `Online` or resume reads it (every refresh reads it again). +fn enabled_from_read(read: anyhow::Result>) -> bool { + match read { + Ok(value) => enabled_from_setting(value.as_deref()), + Err(e) => { + log::warn!("[announcements] reading the toggle failed: {e}; staying off"); + false + } + } +} + +async fn load_enabled(db: &S) -> bool { + enabled_from_read(db.get_setting(settings_keys::ANNOUNCEMENTS_ENABLED).await) +} + +/// One toggle change or refresh at a time. A refresh (`Online`, resume) reads +/// the setting, then awaits the cache sweep before it stores what it read; a +/// tap in between would be undone — the subscription reopened with the +/// persisted switch off — if the two could interleave. +static TRANSITION: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(()); + +/// Apply the toggle, already persisted by the caller: on reopens the +/// subscription through [`subscribe_announcements`], off closes it at the +/// tap — nothing waits for the next background transition (§7). +pub async fn set_enabled(enabled: bool) { + let _transition = TRANSITION.lock().await; + ENABLED.store(enabled, Ordering::Release); + if enabled { + refresh_subscription().await; + } else { + close_subscription().await; + } +} + +/// Close the relay subscription, if one was ever opened. The pump task +/// stays: it is gated by [`ENABLED`], and a later on re-issues the filter. +async fn close_subscription() { + if !LOOP_ACTIVE.load(Ordering::Acquire) { + return; + } + if let Ok(pool) = crate::api::nostr::get_pool() { + crate::nostr::live_subs::live_subs() + .close(&pool.client(), &SubscriptionId::new(SUBSCRIPTION_ID)) + .await; + log::info!("[announcements] subscription closed: toggle off"); + } +} + +/// Bring the channel up to date: re-check the cache against the clock, hand +/// Dart what is left, then start the relay loop or, when it already runs, +/// move its filter's window to now. Called on every `Online` and every +/// resume, like the order book; with an empty allowlist, or the toggle off, +/// nothing is subscribed — the cache is still swept, since what it holds is +/// the last thing the project said and the expiry rule holds offline (§5.4). pub async fn subscribe_announcements() { + let _transition = TRANSITION.lock().await; + refresh_subscription().await; +} + +/// [`subscribe_announcements`] with [`TRANSITION`] already held. +async fn refresh_subscription() { let authors = announcements::allowed_authors(); let version = announcements::app_version(); + let mut enabled = true; // Even with no key left to subscribe to: a release that retires the last // one must still sweep what that key left in the cache. if let Some(db) = crate::db::app_db::db() { - if let Err(e) = restore(db, &authors, Timestamp::now(), &version).await { - log::warn!("[announcements] re-checking the cache failed: {e}"); + enabled = load_enabled(db).await; + match restore(db, &authors, Timestamp::now(), &version).await { + Ok(current) => crate::api::announcements::emit(¤t), + Err(e) => log::warn!("[announcements] re-checking the cache failed: {e}"), } } + ENABLED.store(enabled, Ordering::Release); + if !enabled { + close_subscription().await; + log::debug!("[announcements] toggle off: no subscription"); + return; + } let Some(filter) = announcement_filter(&authors, Timestamp::now()) else { log::debug!("[announcements] allowlist empty: no subscription"); return; }; + let Ok(pool) = crate::api::nostr::get_pool() else { + log::warn!("[announcements] no relay pool: not subscribing"); + return; + }; + let client = pool.client(); + let id = SubscriptionId::new(SUBSCRIPTION_ID); if LOOP_ACTIVE .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) .is_err() { // `live_subs` re-issues the recorded filter on every reconnect; left // alone, its `since` would trail further behind each day it runs. - if let Ok(pool) = crate::api::nostr::get_pool() { - if let Err(e) = crate::nostr::live_subs::live_subs() - .replace(&pool.client(), SubscriptionId::new(SUBSCRIPTION_ID), filter) - .await - { - log::warn!("[announcements] refreshing the filter failed: {e}"); - } + if let Err(e) = crate::nostr::live_subs::live_subs() + .replace(&client, id, filter) + .await + { + log::warn!("[announcements] refreshing the filter failed: {e}"); } return; } - crate::rt::spawn(async move { - let _guard = LoopGuard; - run_subscription(authors, filter, version).await; - }); -} - -/// Subscribe on the shared pool — keyed with an ephemeral key, never a trade -/// key (§4.4) — and store whatever wins at its address. Publishes nothing. -async fn run_subscription(authors: Vec, filter: Filter, version: Version) { - let Ok(pool) = crate::api::nostr::get_pool() else { - log::warn!("[announcements] no relay pool: not subscribing"); - return; - }; - let client = pool.client(); - // Before subscribing, so nothing arrives between the two unheard. - let mut rx = client.notifications(); - let id = SubscriptionId::new(SUBSCRIPTION_ID); + let guard = LoopGuard; + // The first open happens here, under `TRANSITION`, not in the pump task: + // spawned, it could reach the relay after a tap closed the subscription + // and reopen it behind the user's back. A pump that never subscribed + // drops the guard, so the next refresh tries again. + // + // Taken before subscribing, so nothing arrives between the two unheard. + let rx = client.notifications(); if let Err(e) = crate::nostr::live_subs::live_subs() .replace(&client, id.clone(), filter) .await @@ -394,7 +515,21 @@ async fn run_subscription(authors: Vec, filter: Filter, version: Vers return; } log::info!("[announcements] subscribed to {} key(s)", authors.len()); + crate::rt::spawn(async move { + let _guard = guard; + run_subscription(rx, id, authors, version).await; + }); +} +/// Pump the subscription [`refresh_subscription`] opened on the shared pool — +/// keyed with an ephemeral key, never a trade key (§4.4) — and store whatever +/// wins at its address. Publishes nothing. +async fn run_subscription( + mut rx: std::pin::Pin + Send>>, + id: SubscriptionId, + authors: Vec, + version: Version, +) { while let Some(notification) = rx.next().await { let ClientNotification::Event { subscription_id, @@ -407,10 +542,20 @@ async fn run_subscription(authors: Vec, filter: Filter, version: Vers if subscription_id != id { continue; } + // Checked per event, after the subscription was closed: an event + // queued behind the close is discarded, as the toggle promises (§7). + if !is_enabled() { + log::debug!("[announcements] {} discarded: toggle off", event.id); + continue; + } let Some(db) = crate::db::app_db::db() else { continue; }; match ingest(db, &event, &authors, Timestamp::now(), &version).await { + Ok(outcome @ (Ingested::New | Ingested::Superseded)) => { + log::info!("[announcements] {}: {outcome:?}", event.id); + crate::api::announcements::publish_current().await; + } Ok(outcome) => log::debug!("[announcements] {}: {outcome:?}", event.id), Err(e) => log::warn!("[announcements] storing {} failed: {e}", event.id), } @@ -982,6 +1127,82 @@ mod tests { assert_eq!(current[0].announcement.body.severity, Severity::Critical); } + // ── Read and dismissed (§6.1) ─────────────────────────────────────── + + #[tokio::test] + async fn flags_are_recorded_on_the_cached_row() { + // Arrange + let db = store().await; + let keys = Keys::generate(); + let allow = [keys.public_key()]; + let event = draft("flags").sign(&keys); + ingest_now(&db, &event, &allow).await; + let address = address_of(&keys.public_key(), "flags"); + + // Act + let read = set_flags(&db, &address, Some(true), None).await.unwrap(); + let dismissed = set_flags(&db, &address, None, Some(true)).await.unwrap(); + + // Assert: both persisted, each leaving the other alone. + assert!(read && dismissed); + let current = restore(&db, &allow, now(), &app()).await.unwrap(); + assert!(current[0].read); + assert!(current[0].dismissed); + } + + #[tokio::test] + async fn a_flag_for_an_address_not_cached_is_dropped() { + let db = store().await; + + let recorded = set_flags(&db, "38387:aa:gone", Some(true), None) + .await + .unwrap(); + + assert!(!recorded); + assert!(db.list_announcements().await.unwrap().is_empty()); + } + + #[tokio::test] + async fn a_correction_clears_a_dismissal_too() { + // Arrange: dismissed, then a newer revision at the same address. + let db = store().await; + let keys = Keys::generate(); + let allow = [keys.public_key()]; + ingest_now(&db, &draft("c").sign(&keys), &allow).await; + let address = address_of(&keys.public_key(), "c"); + set_flags(&db, &address, Some(true), Some(true)) + .await + .unwrap(); + + // Act + let outcome = ingest_now(&db, &draft("c").at(NOW + 60).sign(&keys), &allow).await; + + // Assert: the correction is a new notice, not a dismissed one. + assert_eq!(outcome, Ingested::Superseded); + let current = restore(&db, &allow, now(), &app()).await.unwrap(); + assert!(!current[0].read); + assert!(!current[0].dismissed); + } + + // ── The toggle (§7) ───────────────────────────────────────────────── + + #[test] + fn the_toggle_reads_absent_as_on() { + assert!(enabled_from_setting(None)); + assert!(enabled_from_setting(Some("true"))); + assert!(enabled_from_setting(Some("anything else"))); + assert!(!enabled_from_setting(Some("false"))); + } + + /// A read that failed may be hiding a `"false"`: it is not consent. + #[test] + fn a_failed_toggle_read_keeps_the_channel_off() { + assert!(enabled_from_read(Ok(None))); + assert!(enabled_from_read(Ok(Some("true".into())))); + assert!(!enabled_from_read(Ok(Some("false".into())))); + assert!(!enabled_from_read(Err(anyhow::anyhow!("db locked")))); + } + /// Announcements are addressed to the install: a new identity keeps them /// (CLAUDE.md, "every new store must say which side it is on"). #[tokio::test] diff --git a/specs/006-announcement-channel/spec.md b/specs/006-announcement-channel/spec.md index 4d04302de..c5f7ad075 100644 --- a/specs/006-announcement-channel/spec.md +++ b/specs/006-announcement-channel/spec.md @@ -2,9 +2,10 @@ **Feature Branch**: `006-announcement-channel` **Created**: 2026-08-26 -**Status**: In progress — steps 1 and 2 of §10 are built: parsing and the allowlist in +**Status**: In progress — steps 1 to 3 of §10 are built: parsing and the allowlist in `rust/src/nostr/announcements.rs`, the reader and its cache in -`rust/src/nostr/announcement_reader.rs` +`rust/src/nostr/announcement_reader.rs`, the bridge and the Announcements toggle in +`rust/src/api/announcements.rs` and `lib/features/settings/providers/announcements_provider.dart` **Input**: Give the project one authenticated way to reach people already running the app — "update to 2.1", "the node is down for maintenance", "this relay is gone" — without a backend, an account, an email address, or a push token. @@ -556,6 +557,24 @@ translate." Adding `rust/src/api/announcements.rs` means `./scripts/frb-generate.sh` must run, and the generated `lib/src/rust/` is never hand-edited. +**What the bridge settled** (step 3 of §10): + +- The bridge type is `AnnouncementInfo` (`rust/src/api/types.rs`), to keep clear of the + reader's `Announcement`. Its `revision` is `:` — the pair §5.3 + orders by — so Dart tells a correction from a redelivery by string equality alone. +- `get_announcements()` returns the cache **whole**: `critical` first, then newest first, + dismissed rows included with their `read` and `dismissed` flags. Dart decides what a + dismissed row means by its level (§3.4), so Rust must not filter it out. +- `on_announcements_changed()` emits that same whole list after every pass that may have + changed it — a sweep (every `Online` and every resume), an arrival that won at its + address, a flag. A lagging subscriber skips to the newest snapshot, since each one is + complete. +- `dismiss_announcement` also marks the row read: a dismissed notice never counts as + unread, whichever level keeps it listed. A flag for an address the cache no longer + holds is dropped silently, not an error — the card was drawn before the row expired. +- Read and dismissed belong to the revision: a correction clears both (§5.3), and the + bridge test `a_correction_clears_a_dismissal_too` pins it. + ### 6.2 Rendering Each announcement becomes a `NotificationModel`: @@ -612,6 +631,16 @@ filter asks for on the next open — `since = now - 30 days` is the same window install gets, so an announcement published while the switch was off is picked up if it is still fresh, and is not if it is not. +**How it is built.** The value is the `settings` key `announcements_enabled` +(`"true"` / `"false"`, absent reads as on), device-scoped like the push toggle. The reader +mirrors it into a process flag at every subscribe and at every tap: off closes the +`announcements` subscription through `live_subs` and the relay pump discards, per event, +whatever still arrives behind the close — the pump task itself stays, gated, and the next +on re-issues the filter through the same pump. **Off leaves the cache alone**: what it holds +is the last thing the project said, it stays shown, and the clock still sweeps it (§5.4). +Hiding it would make the switch mean two things. The OS notification permission does not +gate the toggle either: an announcement posts no system notification. + **Default: on.** A judgement call, stated plainly: the channel is low-frequency and product-related, and nothing here posts a system notification, so nothing escapes the app the user just opened. The day it does post one, Android 13+ requires a runtime permission @@ -704,7 +733,7 @@ PR per step, per the workflow in CLAUDE.md. | 0 | ~~Fix the app-version source (§4.3)~~ — **done in this PR** | Version targeting is unusable while the version is not the shipped one, so it could not be left as a follow-up | | 1 | §3 parsing and validation + the §4.1 allowlist, as pure Rust functions — no relay, no DB, no UI | Everything rests on it, and it is the cheapest thing to get wrong quietly. Ships with the allowlist **empty**, which is inert by construction | | 2 | Fetch, verify, freshness, expiry, the `announcements` table and the schema bump (§4.2, §5) | Testable without a screen | -| 3 | The bridge surface + the Announcements toggle (§6.1, §7), then `./scripts/frb-generate.sh` | The off switch before the surface | +| 3 | ~~The bridge surface + the Announcements toggle (§6.1, §7), then `./scripts/frb-generate.sh`~~ — **done** | The off switch before the surface | | 4 | Rendering into the existing notification surface, seven locales of chrome (§6.2) | Last, when there is something to show | | 5 | `rust/src/bin/announce.rs` + the publishing doc (§8) | With or just after step 1, sharing its validator | | 6 | **Add the real npubs** — one line, its own PR, npubs checked against a source outside this repo | The only commit that makes the channel live | diff --git a/test/features/settings/providers/announcements_provider_test.dart b/test/features/settings/providers/announcements_provider_test.dart new file mode 100644 index 000000000..bf56044fe --- /dev/null +++ b/test/features/settings/providers/announcements_provider_test.dart @@ -0,0 +1,149 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mostro/features/settings/providers/announcements_provider.dart'; + +/// The Announcements toggle (specs/006-announcement-channel §7): the UI +/// answers the tap, Rust is told, and a refused write rolls back. +class _FakeBridge extends AnnouncementsBridge { + _FakeBridge({ + this.stored = true, + this.failSet = false, + this.gate, + this.holdWrites = false, + }); + + bool stored; + final bool failSet; + + /// When set, the first [enabled] waits on it: the load is in flight. + final Completer? gate; + + /// When true, each [setEnabled] waits on its own entry in [writes]. + final bool holdWrites; + final writes = >[]; + final asked = []; + int reads = 0; + + @override + Future enabled() async { + if (reads++ == 0 && gate != null) await gate!.future; + return stored; + } + + @override + Future setEnabled(bool enabled) async { + asked.add(enabled); + if (holdWrites) { + final gate = Completer(); + writes.add(gate); + await gate.future; + } + if (failSet) throw Exception('StorageUnavailable'); + stored = enabled; + } +} + +void main() { + test('starts on, then loads what Rust persisted', () async { + // Arrange + final bridge = _FakeBridge(stored: false); + + // Act + final notifier = AnnouncementsEnabledNotifier(bridge: bridge); + expect(notifier.state, isTrue); + await Future.delayed(Duration.zero); + + // Assert + expect(notifier.state, isFalse); + }); + + test('a tap is applied first and handed to Rust after', () async { + // Arrange + final bridge = _FakeBridge(); + final notifier = AnnouncementsEnabledNotifier(bridge: bridge); + await Future.delayed(Duration.zero); + + // Act + final ok = await notifier.set(false); + + // Assert + expect(ok, isTrue); + expect(notifier.state, isFalse); + expect(bridge.asked, [false]); + }); + + test('a write Rust refuses rolls the toggle back', () async { + // Arrange + final bridge = _FakeBridge(failSet: true); + final notifier = AnnouncementsEnabledNotifier(bridge: bridge); + await Future.delayed(Duration.zero); + + // Act + final ok = await notifier.set(false); + + // Assert + expect(ok, isFalse); + expect(notifier.state, isTrue); + }); + + test('a load that lands after a tap does not undo it', () async { + // Arrange: the load is still reading when the user taps. + final gate = Completer(); + final bridge = _FakeBridge(stored: true, gate: gate); + final notifier = AnnouncementsEnabledNotifier(bridge: bridge); + + // Act + await notifier.set(false); + gate.complete(); + await Future.delayed(Duration.zero); + + // Assert: the stale `true` the load read is not applied. + expect(notifier.state, isFalse); + }); + + test( + 'two quick taps reach Rust one after the other, last one wins', + () async { + // Arrange: writes hang until the test lets each one through. + final bridge = _FakeBridge(holdWrites: true); + final notifier = AnnouncementsEnabledNotifier(bridge: bridge); + await Future.delayed(Duration.zero); + + // Act + final first = notifier.set(false); + final second = notifier.set(true); + await Future.delayed(Duration.zero); + expect(bridge.writes, hasLength(1), reason: 'second waits for first'); + bridge.writes[0].complete(); + await first; + await Future.delayed(Duration.zero); + expect(bridge.writes, hasLength(2)); + bridge.writes[1].complete(); + await second; + + // Assert + expect(bridge.asked, [false, true]); + expect(bridge.stored, isTrue); + expect(notifier.state, isTrue); + }, + ); + + test('a write refused while the load is in flight lands on what Rust ' + 'holds, not on the pre-tap default', () async { + // Arrange: persisted off, the load still reading, the write refused. + final gate = Completer(); + final bridge = _FakeBridge(stored: false, failSet: true, gate: gate); + final notifier = AnnouncementsEnabledNotifier(bridge: bridge); + expect(notifier.state, isTrue, reason: 'the default until the load lands'); + + // Act + final ok = await notifier.set(false); + gate.complete(); + await Future.delayed(Duration.zero); + + // Assert: re-read from Rust, not rolled back to the stale `true`. + expect(ok, isFalse); + expect(notifier.state, isFalse); + }); +} diff --git a/test/features/settings/screens/goldens/settings_10d_notifications_dark.png b/test/features/settings/screens/goldens/settings_10d_notifications_dark.png index 9ea0d67fc..34b94f803 100644 Binary files a/test/features/settings/screens/goldens/settings_10d_notifications_dark.png and b/test/features/settings/screens/goldens/settings_10d_notifications_dark.png differ diff --git a/test/features/settings/screens/goldens/settings_10d_notifications_denied_dark.png b/test/features/settings/screens/goldens/settings_10d_notifications_denied_dark.png index 3a28f0bf2..f4b30c816 100644 Binary files a/test/features/settings/screens/goldens/settings_10d_notifications_denied_dark.png and b/test/features/settings/screens/goldens/settings_10d_notifications_denied_dark.png differ diff --git a/test/features/settings/screens/goldens/settings_10d_notifications_denied_light.png b/test/features/settings/screens/goldens/settings_10d_notifications_denied_light.png index fa958b51e..a37391350 100644 Binary files a/test/features/settings/screens/goldens/settings_10d_notifications_denied_light.png and b/test/features/settings/screens/goldens/settings_10d_notifications_denied_light.png differ diff --git a/test/features/settings/screens/goldens/settings_10d_notifications_disabled_dark.png b/test/features/settings/screens/goldens/settings_10d_notifications_disabled_dark.png index a18e6f304..0068de36c 100644 Binary files a/test/features/settings/screens/goldens/settings_10d_notifications_disabled_dark.png and b/test/features/settings/screens/goldens/settings_10d_notifications_disabled_dark.png differ diff --git a/test/features/settings/screens/goldens/settings_10d_notifications_disabled_light.png b/test/features/settings/screens/goldens/settings_10d_notifications_disabled_light.png index b2ae21182..dd973e8e6 100644 Binary files a/test/features/settings/screens/goldens/settings_10d_notifications_disabled_light.png and b/test/features/settings/screens/goldens/settings_10d_notifications_disabled_light.png differ diff --git a/test/features/settings/screens/goldens/settings_10d_notifications_light.png b/test/features/settings/screens/goldens/settings_10d_notifications_light.png index 7932fdadf..bf94144d0 100644 Binary files a/test/features/settings/screens/goldens/settings_10d_notifications_light.png and b/test/features/settings/screens/goldens/settings_10d_notifications_light.png differ diff --git a/test/features/settings/screens/goldens/settings_10d_notifications_refused_dark.png b/test/features/settings/screens/goldens/settings_10d_notifications_refused_dark.png index 9aa3e7d19..024dcace9 100644 Binary files a/test/features/settings/screens/goldens/settings_10d_notifications_refused_dark.png and b/test/features/settings/screens/goldens/settings_10d_notifications_refused_dark.png differ diff --git a/test/features/settings/screens/goldens/settings_10d_notifications_refused_light.png b/test/features/settings/screens/goldens/settings_10d_notifications_refused_light.png index 79f0f0b9c..e4391f286 100644 Binary files a/test/features/settings/screens/goldens/settings_10d_notifications_refused_light.png and b/test/features/settings/screens/goldens/settings_10d_notifications_refused_light.png differ diff --git a/test/features/settings/screens/goldens/settings_10d_notifications_unsupported_dark.png b/test/features/settings/screens/goldens/settings_10d_notifications_unsupported_dark.png index 5a24475ba..32cd07521 100644 Binary files a/test/features/settings/screens/goldens/settings_10d_notifications_unsupported_dark.png and b/test/features/settings/screens/goldens/settings_10d_notifications_unsupported_dark.png differ diff --git a/test/features/settings/screens/goldens/settings_10d_notifications_unsupported_light.png b/test/features/settings/screens/goldens/settings_10d_notifications_unsupported_light.png index f33a4b18f..bafb490c1 100644 Binary files a/test/features/settings/screens/goldens/settings_10d_notifications_unsupported_light.png and b/test/features/settings/screens/goldens/settings_10d_notifications_unsupported_light.png differ diff --git a/test/features/settings/screens/settings_screens_test.dart b/test/features/settings/screens/settings_screens_test.dart index 0228dc692..287d4a1a5 100644 --- a/test/features/settings/screens/settings_screens_test.dart +++ b/test/features/settings/screens/settings_screens_test.dart @@ -9,6 +9,7 @@ import 'package:mostro/core/automation/automation_ids.dart'; import 'package:mostro/core/settings_palette.dart'; import 'package:mostro/features/about/screens/about_screen.dart' show appVersionProvider; +import 'package:mostro/features/settings/providers/announcements_provider.dart'; import 'package:mostro/features/settings/providers/mostro_nodes_provider.dart'; import 'package:mostro/features/settings/providers/notification_permission_provider.dart'; import 'package:mostro/features/settings/providers/notification_prefs_provider.dart'; @@ -65,6 +66,23 @@ PushStatus _push({ : nodeRefusedUntil.millisecondsSinceEpoch ~/ 1000, ); +/// The Announcements toggle's Rust side, recording what it was asked. +class _RecordingAnnouncements extends AnnouncementsBridge { + _RecordingAnnouncements({this.stored = true}); + + bool stored; + final asked = []; + + @override + Future enabled() async => stored; + + @override + Future setEnabled(bool enabled) async { + asked.add(enabled); + stored = enabled; + } +} + /// Records what the master toggle asked for, without a bridge or a device. class _RecordingToggle implements PushToggle { final asked = []; @@ -86,7 +104,11 @@ List _overrides({ bool pushExpiresWithTab = false, PushStatus? push, PushToggle? toggle, + AnnouncementsBridge? announcements, }) => [ + announcementsBridgeProvider.overrideWithValue( + announcements ?? _RecordingAnnouncements(), + ), pushSupportedProvider.overrideWithValue(pushSupported), pushExpiresWithTabProvider.overrideWithValue(pushExpiresWithTab), pushStatusProvider.overrideWith((ref) => Stream.value(push ?? _push())), @@ -449,8 +471,8 @@ void main() { ) async { await _pump(tester, const NotificationSettingsScreen()); - // The master push toggle, then the four events. - expect(find.byType(MostroToggle), findsNWidgets(5)); + // The master push toggle, the four events, then Announcements. + expect(find.byType(MostroToggle), findsNWidgets(6)); expect(find.text('Trade updates'), findsOneWidget); expect(find.text('Dispute updates'), findsOneWidget); }); @@ -619,8 +641,8 @@ void main() { findsOneWidget, ); expect(_masterToggle, findsNothing); - // The in-app event rows still apply. - expect(find.byType(MostroToggle), findsNWidgets(4)); + // The in-app event rows and Announcements still apply. + expect(find.byType(MostroToggle), findsNWidgets(5)); }); testWidgets('no banner while the system permission is granted', ( @@ -653,6 +675,9 @@ void main() { find.byType(MostroToggle), )) { if (toggle.semanticLabel == 'Push notifications') continue; + // Announcements post no system notification: the OS permission + // does not gate them. + if (toggle.semanticLabel == 'Announcements') continue; expect(toggle.onChanged, isNull); } // Turning push off still unregisters every trade from the server. @@ -721,5 +746,47 @@ void main() { final prefs = await SharedPreferences.getInstance(); expect(prefs.getBool(NotificationEvent.tradeUpdates.prefsKey), isFalse); }); + + // ── Announcements (specs/006 §7) ────────────────────────────────────── + + testWidgets('shows the Announcements toggle with what Rust persisted', ( + tester, + ) async { + await _pump( + tester, + const NotificationSettingsScreen(), + overrides: _overrides( + announcements: _RecordingAnnouncements(stored: false), + ), + ); + await tester.pump(); + + expect(find.text('Announcements'), findsOneWidget); + expect( + find.textContaining('signed by the Mostro project'), + findsOneWidget, + ); + expect(find.textContaining('nothing about you is sent'), findsOneWidget); + expect(tester.widget(_announcementsToggle).value, isFalse); + }); + + testWidgets('turning Announcements off goes through Rust', (tester) async { + final bridge = _RecordingAnnouncements(); + await _pump( + tester, + const NotificationSettingsScreen(), + overrides: _overrides(announcements: bridge), + ); + + await tester.tap(_announcementsToggle); + await tester.pumpAndSettle(); + + expect(bridge.asked, [false]); + expect(tester.widget(_announcementsToggle).value, isFalse); + }); }); } + +final _announcementsToggle = find.byWidgetPredicate( + (w) => w is MostroToggle && w.semanticLabel == 'Announcements', +);