Skip to content

Conversation

@erskingardner
Copy link
Member

@erskingardner erskingardner commented Aug 30, 2025

Collection of a few bug fixes:

Fixes #550
Fixes #553
Fixes #409

Summary by CodeRabbit

  • Refactor

    • Network Relays settings now shows a always-visible list with a header, Add action, and optional help; improved loading and error states.
  • Style

    • Standardized copy-to-clipboard toasts across the app: “Public key copied”, “Private key copied”, and “Copied lightning/bitcoin address” on the Donate screen.
  • Chores

    • Android builds now generate APKs prefixed with “whitenoise” for clearer artifact naming.
    • Build scripts/commands updated to align with new Android artifact names and flow.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 30, 2025

Walkthrough

Renames Android APK base name to "whitenoise" and updates build scripts/paths accordingly. Normalizes several copy-to-clipboard toast messages across UI. Adjusts donate screen copy helper to include address type. Replaces collapsible relay ExpansionTile with a static header and always-visible content, adding explicit info and add actions.

Changes

Cohort / File(s) Summary
Android build artifact renaming
android/app/build.gradle.kts, scripts/build.sh, justfile
Set archivesBaseName to "whitenoise"; updated script artifact paths from app-... to whitenoise-...; changed Just recipe to call ./scripts/build.sh --full --android.
Clipboard toast message normalization
lib/ui/chat/chat_info/dm_chat_info.dart, lib/ui/chat/chat_info/widgets/group_member_bottom_sheet.dart, lib/ui/contact_list/widgets/user_profile.dart, lib/ui/settings/profile/share_profile_screen.dart, lib/ui/settings/profile_keys/profile_keys_screen.dart
Standardized toasts to "Public key copied"/"Private key copied" (consistent casing, no punctuation); removed "to clipboard".
Donate copy helper update
lib/ui/settings/donate/donate_screen.dart
_copyToClipboard now (WidgetRef ref, String type, String text) and toasts read "Copied {type} address"; call sites pass lightning or bitcoin.
Relay list non-collapsible header
lib/ui/settings/network/widgets/relay_expansion_tile.dart
Replaced ExpansionTile with static Column and header Row; added optional info icon and explicit Add action; list content always visible with loading/error states preserved.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant UI as RelayExpansionTile (UI)
  participant VM as RelayState/Notifiers

  rect rgba(200,220,255,0.25)
    note over UI: Non-collapsible header
    User->>UI: Tap title
    note over UI: No expand/collapse occurs
  end

  User->>UI: Tap info icon (if shown)
  UI-->>User: Show info/tooltip (onInfoTap)

  User->>UI: Tap "Add" action
  UI->>VM: _showAddRelayBottomSheet / _addRelay
  VM-->>UI: Success or Error
  alt Success
    UI-->>User: Relay added, list updates
  else Error
    UI-->>User: Show error toast
  end
Loading
sequenceDiagram
  actor User
  participant UI as DonateScreen
  participant Utils as ClipboardUtils

  User->>UI: Tap copy Lightning address
  UI->>Utils: copyWithToast(text=lnAddr, success="Copied lightning address")
  Utils-->>User: Toast shown

  User->>UI: Tap copy Bitcoin address
  UI->>Utils: copyWithToast(text=btcAddr, success="Copied bitcoin address")
  Utils-->>User: Toast shown
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Standardize copy toasts across app: QR code screen, keys settings, donate page (#550)
Prevent relay category header taps from collapsing list (#553)
Provide app signing certificate SHA-256 hash and document it (#409) No addition of cert hash generation or README/documentation changes found.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Rename APK base via setProperty("archivesBaseName", "whitenoise") (android/app/build.gradle.kts, in android block before compileOptions) Not referenced in linked issues; unrelated to toasts, relay UI behavior, or cert hash documentation.
Update build artifact paths from app-... to whitenoise-... and defer OUTPUT_DIR creation (scripts/build.sh, sections defining APK paths and output setup) Artifact naming and directory timing changes are not part of the specified objectives.
Change Just recipe arg from --versioned to --android (justfile, android-build recipe) Build invocation flag change not tied to the listed issues’ requirements.

Suggested reviewers

  • codeswot
  • untreu2

Poem

In burrows of code I hop and peep,
Toasts now neat, no dots to keep.
Relays stand tall, no sneak-collapse,
Lightning, bitcoin—copy perhaps!
APKs renamed, a brighter night—
Whitenoise whispers, build paths light.
Thump-thump—ship it, ears upright! 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3ae6269 and c5e96b2.

📒 Files selected for processing (1)
  • lib/ui/settings/network/widgets/relay_expansion_tile.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/ui/settings/network/widgets/relay_expansion_tile.dart
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bug-hunting

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
lib/ui/settings/profile_keys/profile_keys_screen.dart (3)

65-68: Leak: _publicKeyController not disposed

Dispose both controllers to avoid leaks.

Apply:

   @override
   void dispose() {
-    _privateKeyController.dispose();
+    _publicKeyController.dispose();
+    _privateKeyController.dispose();
     super.dispose();
   }

38-45: Guard null/empty npub before copying

If npub isn’t loaded yet, passing null may throw or show a bad toast. Early-return.

   void _copyPublicKey() {
-    final npub = ref.read(nostrKeysProvider).npub;
-    ClipboardUtils.copyWithToast(
+    final String? npub = ref.read(nostrKeysProvider).npub;
+    if (npub == null || npub.isEmpty) {
+      return;
+    }
+    ClipboardUtils.copyWithToast(
       ref: ref,
       textToCopy: npub,
       successMessage: 'Public key copied',
     );
   }

16-21: Add SHA-256 checksum and verification steps to docs
The README/docs lack the APK’s SHA-256 hash and instructions for verifying with apksigner or AppVerifier.

scripts/build.sh (1)

352-356: Do not put signing-certificate SHA-256 into .sha256 files; compute file hash separately

Current flow conflates the APK signing cert fingerprint (from apksigner) with the file hash and writes that value into “.apk.sha256”. That misleads verification and breaks tooling that expects a file checksum.

Fix:

  • Keep using apksigner output as the “signing cert SHA-256 fingerprint”.
  • Always compute the file’s SHA-256 for .apk.sha256.
  • When capturing function output, strip log lines.

Minimal diffs (ARM64 shown; mirror for ARMv7/x86_64 and debug):

-        ARM64_HASH=""
+        ARM64_HASH=""            # signing cert SHA-256 (fingerprint)
+        ARM64_FILE_HASH=""       # file SHA-256
         ARMV7_HASH=""
         X86_64_HASH=""
+        ARMV7_FILE_HASH=""
+        X86_64_FILE_HASH=""
-        if [ -f "$ARM64_APK_PATH" ]; then
-            ARM64_HASH=$(generate_apk_hash "$ARM64_APK_PATH" "arm64-v8a")
-        fi
+        if [ -f "$ARM64_APK_PATH" ]; then
+            ARM64_HASH=$(generate_apk_hash "$ARM64_APK_PATH" "arm64-v8a" | tail -n1)
+            ARM64_FILE_HASH=$(shasum -a 256 "$ARM64_APK_PATH" | cut -d' ' -f1)
+        fi
-                if [ -n "$ARM64_HASH" ]; then
-                    print_info "  SHA-256: $ARM64_HASH"
-                    # Create hash file
-                    echo "$ARM64_HASH  whitenoise-${VERSION_NAME}-arm64-v8a.apk" > "$OUTPUT_DIR/whitenoise-${VERSION_NAME}-arm64-v8a.apk.sha256"
+                if [ -n "$ARM64_FILE_HASH" ]; then
+                    print_info "  File SHA-256: $ARM64_FILE_HASH"
+                    echo "$ARM64_FILE_HASH  whitenoise-${VERSION_NAME}-arm64-v8a.apk" > "$OUTPUT_DIR/whitenoise-${VERSION_NAME}-arm64-v8a.apk.sha256"
+                fi
+                if [ -n "$ARM64_HASH" ]; then
+                    print_info "  Signing cert SHA-256: $ARM64_HASH"
                 fi

Repeat analogous replacements for ARMv7/x86_64 blocks and for the debug section (use ARM64_DEBUG_FILE_HASH, etc.). Also update build_info.txt population to list both the file SHA-256 (for .sha256) and the signing cert SHA-256 separately.

Also applies to: 369-380, 384-396, 400-406

lib/ui/chat/chat_info/widgets/group_member_bottom_sheet.dart (1)

48-56: Align toast messages with new conventions and publish signing certificate hash

  • lib/utils/clipboard_utils.dart (defaults at lines 35, 40, 70, 78) & test/utils/clipboard_utils_test.dart (lines 59, 199): replace legacy defaults (‘Copied to clipboard’, ‘Failed to copy to clipboard’) with the new pattern (e.g. ‘… copied’, ‘No text to copy’).
  • lib/ui/settings/wallet/wallet_screen.dart:83 & lib/ui/chat/services/chat_dialog_service.dart:130: remove “to clipboard” suffix (e.g. change ‘Connection secret copied to clipboard’ → ‘Connection secret copied’).
  • Donation flows: no occurrences of ‘Copied bitcoin address’ or ‘Copied lightning address’ in lib—add the missing implementations and/or tests.
  • README.md/docs: there’s no published SHA-256 app signing certificate hash (only code in scripts/build.sh). Add the final hash per #409.
lib/ui/chat/chat_info/dm_chat_info.dart (1)

111-119: Standardize copy-to-clipboard toast messages
Non-standard toasts remain; all copy-to-clipboard messages must start with “Copied”, drop the “to clipboard” suffix, and have no trailing period.

• lib/ui/settings/profile_keys/profile_keys_screen.dart (line 43)
• lib/ui/settings/profile/share_profile_screen.dart (line 52)
• lib/ui/contact_list/widgets/user_profile.dart (line 32)
• lib/ui/chat/chat_info/dm_chat_info.dart (line 116)
• lib/ui/chat/chat_info/widgets/group_member_bottom_sheet.dart (line 53)
– Change
successMessage: 'Public key copied'

successMessage: 'Copied public key'

• lib/ui/settings/wallet/wallet_screen.dart (line 83)
– Change
successMessage: 'Connection secret copied to clipboard'

successMessage: 'Copied connection secret'

• lib/ui/chat/services/chat_dialog_service.dart (line 130)
– Change
Text('Message copied to clipboard')

Text('Copied message')

Review and update the fallback in lib/utils/clipboard_utils.dart (default successMessage from 'Copied to clipboard''Copied').

lib/src/rust/api/error.freezed.dart (1)

1-1636: Document app signing certificate SHA-256 fingerprint
Add the app signing certificate’s SHA-256 fingerprint (as output by apksigner verify --print-certs) to README.md (or docs/release.md) under the Release section, separate from the APK file checksums.

🧹 Nitpick comments (11)
lib/ui/settings/profile/share_profile_screen.dart (1)

48-54: Remove unused context param or use it for i18n.

context is not used in _copyToClipboard. Either remove it or leverage it for AppLocalizations. Minimal removal:

-  void _copyToClipboard(BuildContext context, String text) {
+  void _copyToClipboard(String text) {
     ClipboardUtils.copyWithToast(
       ref: ref,
       textToCopy: text,
       successMessage: 'Public key copied',
     );
   }
...
-                                  InkWell(
-                                    onTap: () => _copyToClipboard(context, npub),
+                                  InkWell(
+                                    onTap: () => _copyToClipboard(npub),

Also applies to: 142-144

lib/ui/settings/profile_keys/profile_keys_screen.dart (1)

106-114: Localize hardcoded strings

Move UI text to AppLocalizations to follow project guidelines and enable translations.

Also applies to: 128-136, 165-171, 189-195

lib/ui/settings/donate/donate_screen.dart (1)

18-24: Clipboard toast copy updates match #550; reduce magic strings and prep for i18n

  • Messages conform to the spec (“Copied lightning address” / “Copied bitcoin address”). Good.
  • To avoid typos and ease reuse, avoid raw 'lightning'/'bitcoin' literals; define constants (or an enum) and reference them at call sites. Consider routing the success text through AppLocalizations next.

Apply within the shown ranges and add the constants near the top of the class/file:

+  static const String _lightningType = 'lightning';
+  static const String _bitcoinType = 'bitcoin';
-  void _copyToClipboard(WidgetRef ref, String type, String text) {
+  void _copyToClipboard(WidgetRef ref, String type, String text) {
     ClipboardUtils.copyWithToast(
       ref: ref,
       textToCopy: text,
-      successMessage: 'Copied $type address',
+      successMessage: 'Copied $type address',
     );
   }
- onTap: () => _copyToClipboard(ref, 'lightning', kLightningAddress),
+ onTap: () => _copyToClipboard(ref, _lightningType, kLightningAddress),
- 'bitcoin',
+ _bitcoinType,

Optional (outside ranges): if localization is available, derive the message via AppLocalizations for consistent translations.

Also applies to: 108-113, 137-146

scripts/build.sh (3)

53-56: sort -V is not portable on macOS; add fallback

macOS BSD sort lacks -V. Use gsort if present, or ls -1v as a fallback.

-                LATEST_BUILD_TOOLS=$(ls "$BUILD_TOOLS_DIR" | sort -V | tail -n 1)
+                if command -v gsort >/dev/null 2>&1; then
+                  LATEST_BUILD_TOOLS=$(ls "$BUILD_TOOLS_DIR" | gsort -V | tail -n 1)
+                else
+                  LATEST_BUILD_TOOLS=$(ls -1v "$BUILD_TOOLS_DIR" | tail -n 1)
+                fi

49-56: Support ANDROID_SDK_ROOT in addition to ANDROID_HOME

Modern setups often export ANDROID_SDK_ROOT. Use either.

-        if [ -n "$ANDROID_HOME" ]; then
+        if [ -n "$ANDROID_HOME" ] || [ -n "$ANDROID_SDK_ROOT" ]; then
-            BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools"
+            SDK_DIR="${ANDROID_HOME:-$ANDROID_SDK_ROOT}"
+            BUILD_TOOLS_DIR="$SDK_DIR/build-tools"

Also applies to: 66-71


539-614: Surface signing cert hash per #409 in docs

You now emit fingerprints and .sha256 files in versioned output. Add a short README snippet explaining:

  • how to verify the APK file hash (.sha256)
  • how to verify the signing cert fingerprint via apksigner/AppVerifier
  • where the “expected” cert SHA-256 is published
justfile (1)

211-211: android-build no longer produces versioned artifacts; confirm consumers

Switching to “./scripts/build.sh --full --android” stops producing OUTPUT_DIR, .sha256, and build_info for this recipe. Ensure any downstream steps (manual QA, distribution to testers) don’t rely on versioned outputs. If they do, consider adding an android-build-versioned target.

lib/ui/settings/network/widgets/relay_expansion_tile.dart (2)

117-118: Localize the error string and avoid hard-coded English

Move "Error Loading Relays" into AppLocalizations and drop const since it uses context.

-        else if (widget.relayState.error != null)
-          const Center(child: Text('Error Loading Relays'))
+        else if (widget.relayState.error != null)
+          Center(child: Text(AppLocalizations.of(context)!.errorLoadingRelays))

Add import (top of file):

+import 'package:flutter_gen/gen_l10n/app_localizations.dart';

41-56: Reduce runtime type checks with Dart 3 pattern matching (cleaner, safer)

Replace chained is/cast blocks with exhaustive type-pattern switch. It’s clearer and avoids accidental wrong casts. Add a default to catch unsupported notifier types.

   Future<void> _addRelay(String url) async {
     try {
-      // Call the appropriate notifier's addRelay method
-      if (widget.relayNotifier is NormalRelaysNotifier) {
-        return await (widget.relayNotifier as NormalRelaysNotifier).addRelay(url);
-      }
-      if (widget.relayNotifier is InboxRelaysNotifier) {
-        return await (widget.relayNotifier as InboxRelaysNotifier).addRelay(url);
-      }
-      if (widget.relayNotifier is KeyPackageRelaysNotifier) {
-        return await (widget.relayNotifier as KeyPackageRelaysNotifier).addRelay(url);
-      }
+      switch (widget.relayNotifier) {
+        case NormalRelaysNotifier n:
+          await n.addRelay(url);
+        case InboxRelaysNotifier n:
+          await n.addRelay(url);
+        case KeyPackageRelaysNotifier n:
+          await n.addRelay(url);
+        default:
+          ref.showErrorToast('Unknown relay notifier');
+      }
     } catch (e) {
       ref.showErrorToast('Failed to add relay: $e');
     }
   }
 
   Future<void> _deleteRelay(String url) async {
     try {
-      // Call the appropriate notifier's deleteRelay method
-      if (widget.relayNotifier is NormalRelaysNotifier) {
-        return await (widget.relayNotifier as NormalRelaysNotifier).deleteRelay(url);
-      }
-      if (widget.relayNotifier is InboxRelaysNotifier) {
-        return await (widget.relayNotifier as InboxRelaysNotifier).deleteRelay(url);
-      }
-      if (widget.relayNotifier is KeyPackageRelaysNotifier) {
-        return await (widget.relayNotifier as KeyPackageRelaysNotifier).deleteRelay(url);
-      }
+      switch (widget.relayNotifier) {
+        case NormalRelaysNotifier n:
+          await n.deleteRelay(url);
+        case InboxRelaysNotifier n:
+          await n.deleteRelay(url);
+        case KeyPackageRelaysNotifier n:
+          await n.deleteRelay(url);
+        default:
+          ref.showErrorToast('Unknown relay notifier');
+      }
     } catch (e) {
       ref.showErrorToast('Failed to delete relay: $e');
     }
   }

Also applies to: 58-73

lib/ui/chat/chat_info/widgets/group_member_bottom_sheet.dart (1)

49-55: Toast text now matches #550 style guide

"Public key copied" (lowercase k, no period) adheres to the new convention. Consider localizing both success and noText messages via AppLocalizations for consistency.

lib/ui/chat/chat_info/dm_chat_info.dart (1)

116-118: Avoid hard-coded user-facing strings; use AppLocalizations or central constants.

To keep translations consistent and avoid drift across screens, reference a localized key or a shared constant instead of a raw string.

       textToCopy: npub,
-      successMessage: 'Public key copied',
+      successMessage: ToastMessages.publicKeyCopied,
       noTextMessage: 'No public key to copy',

Example constant (place in a shared constants file and import here):

// lib/config/constants/toast_messages.dart
class ToastMessages {
  static const String publicKeyCopied = 'Public key copied';
  static const String noPublicKeyToCopy = 'No public key to copy';
}

Prefer AppLocalizations if keys exist (e.g., context.l10n.publicKeyCopied).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1cad807 and 3ae6269.

⛔ Files ignored due to path filters (2)
  • ios/Podfile.lock is excluded by !**/*.lock
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (29)
  • android/app/build.gradle.kts (1 hunks)
  • justfile (1 hunks)
  • lib/config/states/auth_state.freezed.dart (6 hunks)
  • lib/config/states/chat_search_state.freezed.dart (13 hunks)
  • lib/config/states/chat_state.freezed.dart (7 hunks)
  • lib/config/states/group_state.freezed.dart (6 hunks)
  • lib/config/states/nostr_keys_state.freezed.dart (3 hunks)
  • lib/config/states/profile_state.freezed.dart (3 hunks)
  • lib/config/states/theme_state.freezed.dart (6 hunks)
  • lib/config/states/toast_state.freezed.dart (12 hunks)
  • lib/config/states/welcome_state.freezed.dart (3 hunks)
  • lib/src/rust/api.dart (1 hunks)
  • lib/src/rust/api/accounts.dart (2 hunks)
  • lib/src/rust/api/error.dart (1 hunks)
  • lib/src/rust/api/error.freezed.dart (18 hunks)
  • lib/src/rust/api/groups.dart (2 hunks)
  • lib/src/rust/api/relays.dart (1 hunks)
  • lib/src/rust/api/users.dart (1 hunks)
  • lib/src/rust/api/utils.dart (1 hunks)
  • lib/src/rust/frb_generated.dart (104 hunks)
  • lib/src/rust/frb_generated.io.dart (15 hunks)
  • lib/ui/chat/chat_info/dm_chat_info.dart (1 hunks)
  • lib/ui/chat/chat_info/widgets/group_member_bottom_sheet.dart (1 hunks)
  • lib/ui/contact_list/widgets/user_profile.dart (1 hunks)
  • lib/ui/settings/donate/donate_screen.dart (3 hunks)
  • lib/ui/settings/network/widgets/relay_expansion_tile.dart (1 hunks)
  • lib/ui/settings/profile/share_profile_screen.dart (1 hunks)
  • lib/ui/settings/profile_keys/profile_keys_screen.dart (2 hunks)
  • scripts/build.sh (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.dart

📄 CodeRabbit inference engine (.cursor/rules/flutter.mdc)

**/*.dart: Always declare the type of each variable and function (parameters and return value). Avoid using 'any'. Create necessary types.
Don't leave blank lines within a function.
One export per file.
Use PascalCase for classes.
Use camelCase for variables, functions, and methods.
Use underscores_case for file and directory names.
Use UPPERCASE for environment variables. Avoid magic numbers and define constants.
Start each function with a verb.
Use verbs for boolean variables. Example: isLoading, hasError, canDelete, etc.
Use complete words instead of abbreviations and correct spelling, except for standard and well-known abbreviations (API, URL, i, j, err, ctx, req, res, next).
Write short functions with a single purpose. Less than 20 instructions.
Name functions with a verb and something else. If it returns a boolean, use isX or hasX, canX, etc. If it doesn't return anything, use executeX or saveX, etc.
Avoid nesting blocks by early checks and returns, or extraction to utility functions.
Use higher-order functions (map, filter, reduce, etc.) to avoid function nesting. Use arrow functions for simple functions (less than 3 instructions). Use named functions for non-simple functions.
Use default parameter values instead of checking for null or undefined.
Reduce function parameters using RO-RO: use an object to pass multiple parameters and to return results. Declare necessary types for input arguments and output.
Use a single level of abstraction in functions.
Don't abuse primitive types and encapsulate data in composite types.
Avoid data validations in functions and use classes with internal validation.
Prefer immutability for data. Use readonly for data that doesn't change. Use 'as const' for literals that don't change.
Declare interfaces to define contracts.
Write small classes with a single purpose. Less than 200 instructions, less than 10 public methods, less than 10 properties.
Use exceptions to handle errors you don't expect. If you catch an exception, it sh...

Files:

  • lib/src/rust/api/relays.dart
  • lib/src/rust/api/error.dart
  • lib/ui/contact_list/widgets/user_profile.dart
  • lib/ui/settings/profile/share_profile_screen.dart
  • lib/ui/chat/chat_info/dm_chat_info.dart
  • lib/ui/chat/chat_info/widgets/group_member_bottom_sheet.dart
  • lib/ui/settings/profile_keys/profile_keys_screen.dart
  • lib/config/states/profile_state.freezed.dart
  • lib/src/rust/api/users.dart
  • lib/config/states/auth_state.freezed.dart
  • lib/src/rust/api.dart
  • lib/src/rust/api/accounts.dart
  • lib/config/states/toast_state.freezed.dart
  • lib/config/states/chat_search_state.freezed.dart
  • lib/config/states/group_state.freezed.dart
  • lib/ui/settings/donate/donate_screen.dart
  • lib/config/states/welcome_state.freezed.dart
  • lib/src/rust/api/utils.dart
  • lib/config/states/nostr_keys_state.freezed.dart
  • lib/src/rust/frb_generated.io.dart
  • lib/src/rust/api/error.freezed.dart
  • lib/config/states/chat_state.freezed.dart
  • lib/ui/settings/network/widgets/relay_expansion_tile.dart
  • lib/config/states/theme_state.freezed.dart
  • lib/src/rust/frb_generated.dart
  • lib/src/rust/api/groups.dart
🧠 Learnings (5)
📚 Learning: 2025-08-08T13:39:00.500Z
Learnt from: CR
PR: parres-hq/whitenoise_flutter#0
File: .cursor/rules/flutter.mdc:0-0
Timestamp: 2025-08-08T13:39:00.500Z
Learning: Applies to **/*.dart : Use freezed to manage UI states.

Applied to files:

  • lib/config/states/profile_state.freezed.dart
  • lib/config/states/auth_state.freezed.dart
  • lib/config/states/toast_state.freezed.dart
  • lib/config/states/chat_search_state.freezed.dart
  • lib/config/states/group_state.freezed.dart
  • lib/config/states/welcome_state.freezed.dart
  • lib/config/states/nostr_keys_state.freezed.dart
  • lib/config/states/chat_state.freezed.dart
  • lib/config/states/theme_state.freezed.dart
📚 Learning: 2025-08-29T09:11:07.251Z
Learnt from: erskingardner
PR: parres-hq/whitenoise_flutter#530
File: rust/src/api/utils.rs:23-26
Timestamp: 2025-08-29T09:11:07.251Z
Learning: In the nostr-sdk Rust library, PublicKey::parse method accepts both hex and bech32 (npub) encoded strings, so there's no need to use format-specific methods like PublicKey::from_bech32 when parsing public keys from string input.

Applied to files:

  • lib/src/rust/api/utils.dart
📚 Learning: 2025-08-12T11:21:53.640Z
Learnt from: josefinalliende
PR: parres-hq/whitenoise_flutter#455
File: lib/ui/settings/profile/switch_profile_bottom_sheet.dart:0-0
Timestamp: 2025-08-12T11:21:53.640Z
Learning: In the whitenoise_flutter codebase, ContactModel.publicKey can be stored in either npub format (from metadata cache) or hex format (from account storage). The activeAccountProvider may return either format depending on how the account was originally stored, so normalization to hex format is required when comparing with other hex-normalized keys in sorting logic.

Applied to files:

  • lib/src/rust/api/utils.dart
  • lib/src/rust/frb_generated.dart
📚 Learning: 2025-08-08T13:39:00.500Z
Learnt from: CR
PR: parres-hq/whitenoise_flutter#0
File: .cursor/rules/flutter.mdc:0-0
Timestamp: 2025-08-08T13:39:00.500Z
Learning: Applies to **/*.dart : Use the whitenoise rust crate (via flutter_rust_bridge) as the source of all data and only way to trigger changes in our data model.

Applied to files:

  • lib/src/rust/frb_generated.io.dart
  • lib/src/rust/frb_generated.dart
📚 Learning: 2025-08-08T13:39:00.500Z
Learnt from: CR
PR: parres-hq/whitenoise_flutter#0
File: .cursor/rules/flutter.mdc:0-0
Timestamp: 2025-08-08T13:39:00.500Z
Learning: Applies to **/*.dart : Use flutter_rust_bridge to access core functionality of the app.

Applied to files:

  • lib/src/rust/frb_generated.dart
🔇 Additional comments (38)
lib/ui/contact_list/widgets/user_profile.dart (1)

32-33: Toast text standardized correctly.

Matches the spec: "Public key copied" (sentence case, no period). LGTM.

lib/ui/settings/profile/share_profile_screen.dart (1)

52-53: Toast text standardized correctly.

"Public key copied" aligns with #550. LGTM.

lib/config/states/auth_state.freezed.dart (1)

29-31: Generated code reflow only; no behavioral changes.

Changes are formatting/line-wrapping to copyWith and equality; signatures and defaults unchanged. Safe to keep.

Also applies to: 47-49, 95-101, 112-118, 192-194, 239-241

android/app/build.gradle.kts (1)

13-15: archivesBaseName correctly prefixes APK outputs and matches build.sh. No further action needed.

lib/config/states/group_state.freezed.dart (1)

21-23: Generated code reflow only; no behavioral changes.

Formatting changes to getters, copyWith accessors, and equality. No API or logic impact. LGTM.

Also applies to: 37-39, 293-295, 349-351, 409-411

lib/config/states/profile_state.freezed.dart (3)

33-34: Generated code reflow only — OK

Formatting-only change to copyWith getter; no behavioral impact.


148-170: Generated copyWith interface wrapping — OK

Pure indentation/line-wrap adjustments; signatures unchanged.


289-303: Equality check rewrapped — OK

Line breaks only; semantics unchanged.

lib/ui/settings/profile_keys/profile_keys_screen.dart (2)

43-44: Toast text matches #550 spec

“Public key copied” uses lowercase “key” and no period.


53-54: Toast text matches #550 spec

“Private key copied” standardized correctly.

lib/config/states/toast_state.freezed.dart (1)

30-31: Freezed reformat only — OK

All hunks are whitespace/line-wrap changes to generated copyWith/equality; no semantic changes.

Also applies to: 112-128, 229-233, 298-300, 366-380, 456-467, 510-512, 522-524, 539-541, 597-599, 695-697

lib/config/states/nostr_keys_state.freezed.dart (1)

28-30: Generated file wrapping only — OK

CopyWith getter/interface and equality rewrapped; behavior unchanged.

Also applies to: 91-101, 180-182

lib/config/states/theme_state.freezed.dart (1)

25-27: Generated file formatting — OK

Line-wrap/indentation only; no logic changes.

Also applies to: 40-42, 68-77, 80-83, 124-126, 150-152

lib/config/states/welcome_state.freezed.dart (1)

28-29: Formatting-only Freezed changes look fine

No functional impact detected. Safe to keep in sync with generated output.

Also applies to: 96-101, 210-212

scripts/build.sh (2)

228-236: Creating OUTPUT_DIR after cleaning is correct

Prevents it from being wiped by flutter clean. Nice.

Also applies to: 275-279


345-348: Renamed APK paths depend on Gradle archivesBaseName; verify CI alignment

If android/app/build.gradle(.kts) sets archivesBaseName = "whitenoise", these paths are correct; otherwise, the script will miss artifacts. Confirm CI/build cache uses the same setting.

Also applies to: 441-443

lib/config/states/chat_search_state.freezed.dart (1)

29-31: Generated formatting only; no behavioral changes

Looks consistent with other Freezed outputs.

Also applies to: 104-110, 222-224, 286-288, 354-368, 514-516, 527-535, 569-577, 648-650, 682-684

lib/ui/settings/network/widgets/relay_expansion_tile.dart (1)

77-113: Non-collapsible header solves #553

Switching from ExpansionTile to a static Column/Row removes the accidental collapse behavior when tapping the title. The help icon is explicitly the only actionable in the header, and the Add button is clearly isolated. Looks good.

lib/src/rust/api/users.dart (1)

47-51: Formatting-only change to hashCode; OK

No behavioral impact; consistent with the multi-line formatting style used elsewhere.

lib/src/rust/api/utils.dart (1)

21-24: Formatting-only change; OK

Line-wrapping the return expression is fine. No semantic changes.

lib/src/rust/api.dart (1)

45-46: Formatting-only change; OK

Multi-line return for getAppSettings is consistent with the rest of the FRB wrappers.

lib/ui/chat/chat_info/widgets/group_member_bottom_sheet.dart (1)

185-192: Remove normalization suggestion—npub comparison is already normalized

Both admin.publicKey and currentUserNpub are converted to npub (via npubFromHexPubkey in the provider and .toNpub() in the bottom sheet), so they’re already in the same format and the equality check won’t misidentify admins. No additional normalization needed.

Likely an incorrect or invalid review comment.

lib/ui/chat/chat_info/dm_chat_info.dart (1)

116-116: Toast text matches the standard.

"Public key copied" follows the capitalization and no-period rule from #550.

lib/src/rust/api/relays.dart (2)

14-16: Formatting-only FRB wrapper changes look good.

Arrow funcs wrapped to next line without semantic change; safe in generated code.

Also applies to: 17-19, 20-21


14-21: Titles are static Text widgets without tap handlers—no UX changes needed. RelayExpansionTile renders the title as a plain Text (no GestureDetector/InkWell), with only the help and add icons wrapped for taps.

Likely an incorrect or invalid review comment.

lib/src/rust/api/groups.dart (1)

102-106: Generated wrapper reflow only; no behavior change.

Method bodies just wrap to multiple lines; API surface identical.

Also applies to: 107-111, 112-116, 162-165

lib/config/states/chat_state.freezed.dart (1)

1-444: Freezed-generated formatting changes are fine.

No semantic diffs detected. Continues to align with prior guidance to use Freezed for UI states.

lib/src/rust/api/error.freezed.dart (1)

1-1636: Freezed error unions reflowed only; safe to merge.

Formatting/copyWith wrapping changes only; constructors and union variants unchanged.

lib/src/rust/frb_generated.dart (6)

22-23: Conditional import rewrap is fine

No behavioral change. Keeps the standard FRB conditional import pattern.


84-89: Verify FFI loader stem/path match produced artifacts

Confirm that the stem “rust_lib_whitenoise” and ioDirectory/webPrefix align with actual library names/locations for all targets (Android/iOS/desktop/web). A mismatch will cause runtime load failures.


64-69: Getter wrapping only

Pure formatting of apiImplConstructor/wireConstructor; no semantic changes.


386-391: TaskConstMeta getters: formatting-only

All these “TaskConstMeta get … => const TaskConstMeta(…)” changes are line-wraps only.

Also applies to: 417-421, 449-454, 482-487, 520-525, 590-595, 627-632, 658-663, 689-694, 771-775, 806-811, 841-845, 901-905, 936-941, 969-974, 1004-1009, 1039-1043, 1069-1072, 1098-1103, 1129-1133, 1158-1163, 1191-1196, 1224-1229, 1255-1259, 1319-1324, 1351-1355, 1385-1389, 1416-1421, 1447-1451, 1482-1485, 1512-1516, 1548-1552, 1581-1586, 1612-1616, 1642-1646, 1672-1676, 1708-1712, 1741-1745, 1773-1777, 1803-1807, 1834-1838, 1866-1870, 1906-1910, 1943-1947, 1987-1991, 2021-2025, 2053-2056, 2087-2091, 2122-2126, 2156-2160, 2194-2198, 2225-2229, 2256-2260


2368-2374: Decode helpers reflowed

DCO/SSE decode wrappers rewrapped; signatures and behavior unchanged.

Also applies to: 3071-3079, 3118-3127, 3194-3203, 3231-3239, 3267-3275


3961-3971: Encode helpers reflowed

SSE encode helpers line-wrapped only; no logic change.

Also applies to: 4000-4010, 4013-4023, 4099-4109, 4112-4119, 4122-4129, 4145-4155, 4158-4165, 4178-4188

lib/src/rust/api/error.dart (1)

46-50: Multiline returns are OK

Pure formatting; calls remain identical to the FRB API.

Also applies to: 52-55

lib/src/rust/frb_generated.io.dart (2)

32-34: Getter wrapping is fine

Formatting-only for CrossPlatformFinalizerArg getter.


1034-1040: Constructor/_lookup reflow

Only layout changed; symbol lookup remains the same.

lib/src/rust/api/accounts.dart (1)

16-18: Return-expression rewraps are fine

All three are formatting-only; behavior unchanged.

Also applies to: 22-24, 25-28

Comment on lines 130 to 133
pubkey.hashCode ^
lastSyncedAt.hashCode ^
createdAt.hashCode ^
updatedAt.hashCode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Null-safety bug in hashCode (nullable lastSyncedAt)

Accessing lastSyncedAt.hashCode when lastSyncedAt is null will fail static analysis and can NPE. Use Object.hash (null-safe) or null-aware operator.

Apply this diff:

-  int get hashCode =>
-      pubkey.hashCode ^
-      lastSyncedAt.hashCode ^
-      createdAt.hashCode ^
-      updatedAt.hashCode;
+  int get hashCode =>
+      Object.hash(pubkey, lastSyncedAt, createdAt, updatedAt);
📝 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.

Suggested change
pubkey.hashCode ^
lastSyncedAt.hashCode ^
createdAt.hashCode ^
updatedAt.hashCode;
int get hashCode =>
Object.hash(pubkey, lastSyncedAt, createdAt, updatedAt);
🤖 Prompt for AI Agents
In lib/src/rust/api/accounts.dart around lines 130 to 133, the current hashCode
expression accesses lastSyncedAt.hashCode directly which breaks null-safety when
lastSyncedAt can be null; replace the combined XOR expression with a null-safe
approach—either use Object.hash(pubkey, lastSyncedAt, createdAt, updatedAt) or
change lastSyncedAt.hashCode to (lastSyncedAt?.hashCode ?? 0) so the hashCode
computation is null-safe and static-analysis clean.

@josefinalliende josefinalliende self-requested a review August 30, 2025 17:02
Copy link
Contributor

@josefinalliende josefinalliende left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM! Coudn't check the build command, but the toast messages and fixes in relays screen work fine in my ios emulator! Approved! 🚀

@josefinalliende josefinalliende merged commit 3935c42 into master Aug 30, 2025
2 checks passed
@josefinalliende josefinalliende deleted the bug-hunting branch August 30, 2025 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tapping relay category title should not collapse list Update toast messages Provide App Signing Certificate SHA-256 Hash

5 participants