Basic UI to send an emergency alert#188
Conversation
WalkthroughA new emergency alert feature for group admins was introduced, including backend logic and a user interface for sending alerts to groups. The emergency alert controller and its provider were added to handle sending alerts using the Nostr protocol. A Flutter screen for composing and sending emergency alerts was implemented, complete with input validation and UI state management. The group detail view was updated to show an emergency alert button for admins, which opens the new alert screen in a modal. Supporting changes include updating the changelog, incrementing the version, and modifying event tags in group creation. Changes
Sequence Diagram(s)sequenceDiagram
participant AdminUser as Admin User
participant GroupDetailWidget
participant EmergencyAlertScreen
participant EmergencyAlertController
participant NostrSDK
AdminUser->>GroupDetailWidget: Tap Emergency Alert Button
GroupDetailWidget->>EmergencyAlertScreen: Open modal with group identifier
AdminUser->>EmergencyAlertScreen: Compose message, check emergency, tap Send
EmergencyAlertScreen->>EmergencyAlertController: sendEmergencyAlert(message, groupId)
EmergencyAlertController->>NostrSDK: Send Event (groupNote, tags: [groupId, broadcast])
NostrSDK-->>EmergencyAlertController: Event result
EmergencyAlertController-->>EmergencyAlertScreen: Return Event or error
EmergencyAlertScreen-->>GroupDetailWidget: Pop modal, pass Event
GroupDetailWidget->>GroupDetailWidget: handleDirectEvent(Event)
Poem
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (7)
lib/provider/note_provider.dart (1)
10-28: Clean implementation with proper error handling.The NoteProvider implementation is well-structured with:
- Strong parameter typing with the
requiredkeyword- Null checking of the Nostr client before usage
- Try-catch block for proper error handling
- Logging of the published event for debugging purposes
Consider enhancing the implementation with the following improvements:
Future<void> sendNote({ required String content, required int kind, required List<List<String>> tags, - }) async { + }) async { if (nostr == null) { throw Exception('Nostr client not initialized'); } try { final event = Event(nostr!.publicKey, kind, tags, content); final publishedEvent = await nostr!.sendEvent(event); log(jsonEncode(publishedEvent?.toJson())); + return publishedEvent; } catch (e) { + log('Error sending note: $e'); rethrow; } }This change would:
- Return the published event for consumers that need it
- Add more specific error logging before rethrowing
lib/router/group/group_detail_widget.dart (1)
158-192: Well-implemented emergency alert button with admin access control.The emergency alert floating action button implementation:
- Uses a Column layout to organize multiple FABs vertically
- Shows the emergency alert button only to admins
- Uses appropriate styling (red color, warning icon) to indicate emergency nature
- Properly handles the event returned from the EmergencyAlertScreen
- Includes good spacing between buttons with the Padding widget
The implementation is solid, but consider these refinements:
floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ if (isAdmin) Padding( padding: const EdgeInsets.only(bottom: 8.0), child: FloatingActionButton( heroTag: 'emergency_alert', onPressed: () { showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => EmergencyAlertScreen( groupIdentifier: groupIdentifier, ), - ).then((event) { - if (event != null && - _groupDetailProvider.isGroupNote(event)) { - _groupDetailProvider.handleDirectEvent(event); - } - }); + ).then(_handleReturnedEvent); }, backgroundColor: Colors.red, + tooltip: localization.EmergencyAlert, child: const Icon(Icons.warning_amber, color: Colors.white), ), ), FloatingActionButton( heroTag: 'add_note', onPressed: _jumpToAddNote, backgroundColor: themeData.customColors.accentColor, shape: const CircleBorder(), child: const Icon(Icons.add, color: Colors.white, size: 29), ), ], )) + // Add this method to handle returned events from both button actions + void _handleReturnedEvent(dynamic event) { + if (event != null && _groupDetailProvider.isGroupNote(event)) { + _groupDetailProvider.handleDirectEvent(event); + } + }This refactoring:
- Extracts the event handling logic to a reusable method
- Adds a tooltip to the emergency alert button for accessibility
lib/features/emergency_alert/emergency_alert_controller.dart (2)
1-5: Review imported dependenciesThe file imports
note_provider.dartbut doesn't appear to use anything from it. Consider removing unused imports to keep the code clean.import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:nostr_sdk/nostr_sdk.dart'; -import 'package:nostrmo/provider/note_provider.dart'; import 'package:nostrmo/main.dart';
10-32: Add documentation for better code maintainabilityThe
EmergencyAlertControllerclass and its methods lack documentation. Consider adding dartdoc comments to explain the purpose, parameters, return values, and potential exceptions for better maintainability.+/// Controller responsible for sending emergency alerts to Nostr groups. class EmergencyAlertController { final Ref _ref; EmergencyAlertController(this._ref); + /// Sends an emergency alert message to the specified group. + /// + /// [message] The alert message to send. + /// [groupId] The ID of the group to send the alert to. + /// + /// Returns the sent [Event] on success. + /// Throws an [Exception] if sending fails or if Nostr SDK is not initialized. Future<Event> sendEmergencyAlert(String message, String groupId) async { // Implementation... } }lib/features/emergency_alert/emergency_alert_screen.dart (3)
7-12: Consider adding comments to explain state providersThe state providers lack documentation explaining their purpose. Adding comments would make it easier for other developers to understand how they're used throughout the widget.
+/// Controls whether the message is marked as an emergency (default: true) final isEmergencyProvider = StateProvider<bool>((ref) => true); +/// Provides and manages the text controller for the emergency message input final messageProvider = StateProvider<TextEditingController>((ref) { final controller = TextEditingController(); ref.onDispose(() => controller.dispose()); return controller; });
136-141: Improve button accessibilityThe Send button is always red, which might not be accessible for all users, especially those with color blindness. Consider adding an icon and making sure the color contrast meets accessibility standards.
style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: Colors.red, ), -child: const Text('Send'), +child: const Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.warning_amber_rounded), + SizedBox(width: 8), + Text('Send Emergency Alert', style: TextStyle(fontWeight: FontWeight.bold)), + ], +),
58-68: Reconsider UI for emergency checkboxSince
isEmergencyProvideris initialized to true (line 7), and the UI has validation that forces the user to check this box, consider whether this checkbox is actually necessary. If emergency alerts must always be emergencies, you might want to remove this checkbox entirely or rethink its purpose.You have two options:
- Remove the checkbox entirely and assume all alerts from this screen are emergencies
- Keep the checkbox but change its purpose to something more meaningful, like selecting the alert severity level
Option 1:
-CheckboxListTile( - value: isEmergency, - onChanged: (value) => ref - .read(isEmergencyProvider.notifier) - .state = value ?? true, - title: Text( - 'This is an emergency', - style: TextStyle(color: textColor), - ), - controlAffinity: ListTileControlAffinity.leading, -), +Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.red), + ), + child: Row( + children: [ + const Icon(Icons.warning_amber_rounded, color: Colors.red), + const SizedBox(width: 12), + Expanded( + child: Text( + 'This will send an emergency alert to all group members', + style: TextStyle(color: textColor), + ), + ), + ], + ), +),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
ios/Podfile.lockis excluded by!**/*.lock
📒 Files selected for processing (6)
CHANGELOG.md(1 hunks)lib/features/emergency_alert/emergency_alert_controller.dart(1 hunks)lib/features/emergency_alert/emergency_alert_screen.dart(1 hunks)lib/provider/note_provider.dart(1 hunks)lib/router/group/group_detail_widget.dart(2 hunks)pubspec.yaml(1 hunks)
🔇 Additional comments (5)
lib/provider/note_provider.dart (2)
1-7: Good imports with focused dependencies.The imports are clean and focused on the necessary components for the note provider functionality.
8-8: Well-defined provider using Flutter Riverpod.Good use of Riverpod's Provider to make the NoteProvider available throughout the application.
pubspec.yaml (1)
19-19: Version increment is appropriate.Incrementing the patch version from 0.0.6 to 0.0.7 correctly follows semantic versioning for a feature addition that doesn't break backward compatibility.
CHANGELOG.md (1)
13-17: Clear and concise changelog entry.The changelog entry appropriately documents the new emergency alert feature under "Release Notes" following the project's established format.
lib/router/group/group_detail_widget.dart (1)
21-21: Appropriate import for the new EmergencyAlertScreen.Good addition of the import needed for the emergency alert functionality.
| Future<Event> sendEmergencyAlert(String message, String groupId) async { | ||
| final event = Event( | ||
| nostr!.publicKey, | ||
| EventKind.groupNote, | ||
| [ | ||
| ["h", groupId], | ||
| ], | ||
| message, | ||
| ); | ||
|
|
||
| final sentEvent = await nostr! | ||
| .sendEvent(event, targetRelays: [groupId], tempRelays: [groupId]); | ||
| if (sentEvent == null) { | ||
| throw Exception('Failed to send emergency alert'); | ||
| } | ||
| return sentEvent; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add null safety checks and improve error handling
The use of nostr! with the non-null assertion operator could lead to runtime exceptions if nostr is null. Consider adding proper null checks and more descriptive error messages.
Future<Event> sendEmergencyAlert(String message, String groupId) async {
+ if (nostr == null) {
+ throw Exception('Nostr SDK is not initialized');
+ }
+
final event = Event(
nostr!.publicKey,
EventKind.groupNote,
[
["h", groupId],
],
message,
);
final sentEvent = await nostr!
.sendEvent(event, targetRelays: [groupId], tempRelays: [groupId]);
if (sentEvent == null) {
- throw Exception('Failed to send emergency alert');
+ throw Exception('Failed to send emergency alert. Please check your connection and try again.');
}
return sentEvent;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Future<Event> sendEmergencyAlert(String message, String groupId) async { | |
| final event = Event( | |
| nostr!.publicKey, | |
| EventKind.groupNote, | |
| [ | |
| ["h", groupId], | |
| ], | |
| message, | |
| ); | |
| final sentEvent = await nostr! | |
| .sendEvent(event, targetRelays: [groupId], tempRelays: [groupId]); | |
| if (sentEvent == null) { | |
| throw Exception('Failed to send emergency alert'); | |
| } | |
| return sentEvent; | |
| } | |
| Future<Event> sendEmergencyAlert(String message, String groupId) async { | |
| if (nostr == null) { | |
| throw Exception('Nostr SDK is not initialized'); | |
| } | |
| final event = Event( | |
| nostr!.publicKey, | |
| EventKind.groupNote, | |
| [ | |
| ["h", groupId], | |
| ], | |
| message, | |
| ); | |
| final sentEvent = await nostr! | |
| .sendEvent(event, targetRelays: [groupId], tempRelays: [groupId]); | |
| if (sentEvent == null) { | |
| throw Exception( | |
| 'Failed to send emergency alert. Please check your connection and try again.'); | |
| } | |
| return sentEvent; | |
| } |
| final sentEvent = await nostr! | ||
| .sendEvent(event, targetRelays: [groupId], tempRelays: [groupId]); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Review relay parameters usage
Both targetRelays and tempRelays are set to the same value. This redundancy might cause confusion. Consider using only one parameter if that's sufficient, or document why both are needed.
🌐 Web query:
What's the difference between targetRelays and tempRelays in Nostr SDK?
💡 Result:
Difference Between targetRelays and tempRelays in Nostr SDK
There is no direct reference to targetRelays or tempRelays in the official documentation or widely referenced Nostr SDK resources in the current search results. However, based on standard naming conventions in SDKs and common Nostr client design patterns, the distinctions can be reasonably inferred:
Target Relays
- Typically refers to the list of persistent relays that a client aims to connect with for regular reading and publishing activities.
- These relays are usually configured by the user or set as defaults within the app. They form the backbone of a client’s ongoing communication and data synchronization with the Nostr network.
- Events published to targetRelays are expected to be generally available for the user and their followers to discover and fetch as needed[6][7][9].
Temp Relays (Temporary Relays)
- Usually denotes relays that are connected on a short-term or ad-hoc basis, often to fulfill a particular request or to fetch very specific data.
- These might be used, for example, to retrieve an event that is only known to be on a particular relay, or to briefly connect to community or topic-focused relays for special queries or actions.
- After the data is fetched, the client typically disconnects from these tempRelays to conserve resources and reduce unnecessary network traffic[6][7].
Summary Table
| Purpose | targetRelays | tempRelays |
|---|---|---|
| Connection Type | Persistent/long-term | Temporary/ad-hoc |
| Usage | Regular event reading/publishing | One-off fetches or special operations |
| Example | Feed updates, note publishing | Fetching a specific event from a known relay |
Additional Context
Nostr clients are designed for flexibility—users can define which relays they interact with, and clients may connect to any number of relays, swapping in or out as needed[6][7][9]. While the search results did not define these terms explicitly, this distinction is common in developer tools and protocols with relay architectures similar to Nostr.
If you need SDK-specific implementation details for these terms (e.g., exact classes or method signatures), consulting the [Nostr SDK documentation][1] or searching within the relevant SDK source code would provide the most precise answer.
(References are based on inferred relay concepts in the sources, as the specific terms were not documented in the provided results.)
Citations:
- 1: https://nostr-sdk.github.io/nostr-sdk-ios/documentation/nostrsdk/
- 2: https://github.com/aljazceru/awesome-nostr
- 3: https://nostr.net
- 4: https://erlangpunch.substack.com/p/implementating-nostr-client-in-erlang
- 5: A method for transferring HTTP communication over Nostr direct-messages nostr-protocol/nips#1276
- 6: https://nickmonad.blog/2023/building-nostr-client-index-1/
- 7: https://github.com/nostr-protocol/nostr
- 8: https://github.com/arkin0x/cyberspace
- 9: https://nostr.com/naddr1qqxnzde5xyersd33xscrwwfhqyqqygyhcu9ygdn2v56uz3dnx0uh865xmlwz675emfsccsxxguz6mx8rygpsgqqqw4rslrdh06
- 10: https://pypi.org/project/nostr-sdk/
Review relay parameter usage: drop redundant tempRelays
In lib/features/emergency_alert/emergency_alert_controller.dart (line 25), you’re passing the same relay ID to both targetRelays (persistent) and tempRelays (ad-hoc). Since sendEvent will publish to all specified relays, having both lists identical is unnecessary and may confuse future maintainers. Choose one of these patterns:
- For regular publishing, keep only
targetRelays:final sentEvent = await nostr! .sendEvent(event, targetRelays: [groupId]);
- If you truly need a one-off connection, drop
targetRelaysand use onlytempRelays:final sentEvent = await nostr! .sendEvent(event, tempRelays: [groupId]);
If there’s a special reason to send via both persistent and temporary relay lists, please document it in a comment.
| BotToast.showText( | ||
| text: 'Failed to send alert: ${e.toString()}'); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Improve error message presentation
The current error handling shows the raw exception as a toast, which might include technical details that aren't helpful to users and could be confusing.
} catch (e) {
BotToast.showText(
- text: 'Failed to send alert: ${e.toString()}');
+ text: 'Failed to send emergency alert. Please try again later.',
+ duration: const Duration(seconds: 3),
);
+ // Log the actual error for debugging
+ debugPrint('Emergency alert error: ${e.toString()}');
}| onPressed: () async { | ||
| final message = messageController.text; | ||
| if (message.isEmpty) { | ||
| BotToast.showText(text: 'Please enter a message'); | ||
| return; | ||
| } | ||
|
|
||
| if (!isEmergency) { | ||
| BotToast.showText( | ||
| text: | ||
| 'If this is not an emergency please go back and post a normal announcement using the + button.', | ||
| duration: const Duration(seconds: 4), | ||
| ); | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add confirmation dialog before sending emergency alerts
Currently, there's no confirmation step before sending an emergency alert. Since emergency alerts are high-impact messages that may trigger immediate responses, consider adding a confirmation dialog to prevent accidental sends.
onPressed: () async {
final message = messageController.text;
if (message.isEmpty) {
BotToast.showText(text: 'Please enter a message');
return;
}
if (!isEmergency) {
BotToast.showText(
text:
'If this is not an emergency please go back and post a normal announcement using the + button.',
duration: const Duration(seconds: 4),
);
return;
}
+ // Show confirmation dialog
+ final confirm = await showDialog<bool>(
+ context: context,
+ builder: (context) => AlertDialog(
+ title: const Text('Send Emergency Alert?'),
+ content: const Text(
+ 'This will send an emergency alert to all members of this group. '
+ 'Please only use this for genuine emergencies.',
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(false),
+ child: const Text('Cancel'),
+ ),
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(true),
+ style: TextButton.styleFrom(foregroundColor: Colors.red),
+ child: const Text('Send Alert'),
+ ),
+ ],
+ ),
+ );
+
+ if (confirm != true) return;
try {
// Rest of the implementation...There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
lib/data/group_repository.dart (1)
22-25: Consider updating the method documentation to mention broadcast capability.The documentation for
createGroupshould be updated to mention that created groups will support emergency alerts/broadcast capability, since this is now a key feature of groups./// Creates a new group with the specified [groupId]. /// /// Sends an event to the relay to create a private closed group. Returns /// a [GroupIdentifier] if the group creation succeeds, otherwise returns `null`. +/// The created group will support emergency alerts via the broadcast capability.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
lib/data/group_repository.dart(1 hunks)lib/features/emergency_alert/emergency_alert_controller.dart(1 hunks)lib/features/emergency_alert/emergency_alert_screen.dart(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- lib/features/emergency_alert/emergency_alert_controller.dart
- lib/features/emergency_alert/emergency_alert_screen.dart
🔇 Additional comments (2)
lib/data/group_repository.dart (2)
31-32: LGTM! The "broadcast" tag addition enables emergency alerts.This change properly adds the "broadcast" tag to newly created groups, enabling the emergency alert functionality that's being implemented in this PR. This tag will allow the Nostr protocol to handle broadcast events for these groups.
26-58: Verify that existing groups will support emergency alerts.This change ensures that newly created groups support emergency alerts, but it's worth verifying how this affects existing groups that were created before this change (without the "broadcast" tag).
Some questions to consider:
- Will existing groups automatically support emergency alerts?
- Is there a migration needed for existing groups?
- Does the emergency alert controller check for this tag before allowing alerts to be sent?
Description
Adds basic UI to send an emergency alert.
How to test
The second device should get a push notification with the alert.
Screenshots/Video
Summary by CodeRabbit
New Features
Bug Fixes
Chores