-
Notifications
You must be signed in to change notification settings - Fork 1
feat: wire relay toggle, mostro node selector, log stream, and disput… #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9594227
bd1630f
edd2431
e0b37f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
|
|
||
| import 'package:mostro/src/rust/api/logging.dart' as logging_api; | ||
| import 'package:mostro/src/rust/api/types.dart'; | ||
|
|
||
| /// Live log entries from the Rust backend, newest first. | ||
| /// | ||
| /// Caps at 500 entries to bound memory usage. The stream is cancelled | ||
| /// when the provider is disposed (e.g. when LogReportScreen is popped). | ||
| final logEntriesProvider = StreamProvider.autoDispose<List<LogEntry>>((ref) async* { | ||
| var cancelled = false; | ||
| ref.onDispose(() => cancelled = true); | ||
|
|
||
| final stream = await logging_api.onLogEntry(); | ||
| final entries = <LogEntry>[]; | ||
| while (!cancelled) { | ||
| final entry = await stream.next(); | ||
| if (entry == null || cancelled) break; | ||
| entries.insert(0, entry); // newest first | ||
| if (entries.length > 500) entries.removeLast(); | ||
| yield List.unmodifiable(entries); | ||
| } | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -38,9 +38,6 @@ class _RelayManagementCardState extends ConsumerState<RelayManagementCard> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Defaults mirror rust/src/config.rs — imported from core/mostro_defaults.dart. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static const _defaultRelays = defaultMostroRelays; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO(bridge): replace _relays local state with a Riverpod provider backed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // by the Rust bridge (get_relays / add_relay / remove_relay) so configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // persists across navigations and stays in sync with the backend (Phase 18+). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| late List<_RelayEntry> _relays; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool _loading = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -73,8 +70,29 @@ class _RelayManagementCardState extends ConsumerState<RelayManagementCard> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void _toggleRelay(int index, bool value) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Future<void> _toggleRelay(int index, bool value) async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final url = _relays[index].url; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setState(() => _relays[index].isActive = value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (value) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await nostr_api.addRelay(url: url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await nostr_api.removeRelay(url: url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| debugPrint('[RelayManagement] toggleRelay failed: $e'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!mounted) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setState(() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final currentIndex = _relays.indexWhere((r) => r.url == url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (currentIndex != -1) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _relays[currentIndex].isActive = !value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+82
to
+89
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle typed relay errors and stale rollback explicitly. Lines 82-89 revert with Proposed hardening } catch (e) {
debugPrint('[RelayManagement] toggleRelay failed: $e');
if (!mounted) return;
+ final err = e.toString();
+ // Enabling an already-present relay is effectively desired state.
+ if (value && err.contains('RelayAlreadyExists')) {
+ return;
+ }
setState(() {
final currentIndex = _relays.indexWhere((r) => r.url == url);
- if (currentIndex != -1) {
+ // Avoid stale rollback if user already toggled again.
+ if (currentIndex != -1 && _relays[currentIndex].isActive == value) {
_relays[currentIndex].isActive = !value;
}
});
+ await _loadRelays(); // resync from source of truth after failure
final l10n = AppLocalizations.of(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(value ? l10n.relayAddFailed : l10n.relayRemoveFailed)),
);
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final l10n = AppLocalizations.of(context); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ScaffoldMessenger.of(context).showSnackBar( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SnackBar(content: Text(value ? l10n.relayAddFailed : l10n.relayRemoveFailed)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Future<void> _removeRelay(int index) async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: MostroP2P/app
Length of output: 550
🏁 Script executed:
Repository: MostroP2P/app
Length of output: 287
🏁 Script executed:
Repository: MostroP2P/app
Length of output: 2841
🏁 Script executed:
Repository: MostroP2P/app
Length of output: 39
🏁 Script executed:
# Examine the mostro_node_selector.dart file for the full provider context cat -n lib/features/settings/widgets/mostro_node_selector.dartRepository: MostroP2P/app
Length of output: 9291
🏁 Script executed:
Repository: MostroP2P/app
Length of output: 39
🏁 Script executed:
Repository: MostroP2P/app
Length of output: 1557
🏁 Script executed:
Repository: MostroP2P/app
Length of output: 3948
🏁 Script executed:
Repository: MostroP2P/app
Length of output: 922
Custom Mostro node selection is ephemeral—will reset on app restart.
The
mostroPubkeyProviderinitializes with_defaultMostroPubkeyon every app launch. Although the Rust bridge persists the value viaset_mostro_pubkey(), Dart never restores it:main.dartdoes not callgetMostroPubkey()on startup, andAppSettingsState.fromPrefs()does not includemostroPubkey.To persist across restarts, either:
mostroPubkeytoAppSettingsStateandSharedPreferencespersistence, orsettings_api.getMostroPubkey()during app initialization to hydrate the provider from Rust's persisted value.🤖 Prompt for AI Agents