234 Commits
Author SHA1 Message Date
Mubarak AuwalandGitHub 1ce17a275d feat(blocked-users): complete block/unblock UX (#676)
* feat(blocked-users): complete block/unblock UX

* docs(changelog): link block/unblock UX entry to PR #676

* fix(l10n): reuse profile key instead of duplicate blockedUserProfileTitle

* fix(blocked-user): disable unblock button while action is in flight

* fix(blocked-users): address pepina review

* refactor(blocked-users): morph in place on unblock and polish notice card

* fix(blocked-users): restore bottom padding only when unblocked panel is shown

* style(blocked-users): apply dart format

* Cover unblocked action panel buttons in BlockedUserScreen tests

* Consolidate blocked-user and start-chat screens into UserProfileScreen

* Cover topAligned mode, blocked notice transitions, and UserProfileScreen.show in tests

* Cover topAligned invite share tap and failure paths

* Fall back to bech32 npub for the blocked user tile display name and drop stale start-chat route stub

* Drop the WnSystemNotice elevatedCard variant and use fillSecondary on the blocked user notice

* Address pepi's review on the blocked notice and open user profiles top-aligned from start-chat entries
2026-05-21 18:42:38 +02:00
Danny MandGitHub a67863aaff fix(mentions): use accent contentSecondary (Color/500) so mentions stay legible on both bubble backgrounds (#692)
Mentions were rendered with AvatarColorSet.content, wired to accent.contentPrimary
(Color/900 in light, Color/50 in dark). That palette is for text on the tinted
avatar fill — not text on a chat bubble. The 50 step is near-white, so on a
white outgoing bubble in dark mode mentions disappeared (see #ux report).

Per design guidance, mention text should use Accent/{color}/Content Secondary
(Color/500) in both themes — a mid-saturation step legible on light and dark
bubbles alike.

- Add contentSecondary to AvatarColorSet, wired to accent.contentSecondary
  (and fillContentTertiary for the neutral bucket).
- Switch the npub mention text style in MarkdownText to .contentSecondary.
- Add a widget test asserting the mention color equals contentSecondary
  and is not contentPrimary (regression guard).

Sender-name and reply-author labels still use .content; they were not in
scope for this fix.
2026-05-21 12:43:03 +02:00
Danny MandGitHub cebf3a1239 fix: route user-tag taps to the group member screen for members (#686) 2026-05-21 11:06:20 +02:00
Danny MandGitHub 21917327cf Share media and messages via long-press chooser (#687) 2026-05-20 21:50:22 +02:00
PepiandGitHub 942521fafc Show notifications settings screen on ios (#688)
* feat: show notifications settings on ios

* docs: update changelog
2026-05-20 15:08:54 -04:00
Danny MandGitHub d6f405fa6f Add 'Add members' button to group info (#679)
* Add 'Add members' button to group info screen

Admin-only button in group info opens a user picker that filters out
existing members and submits via addMembersToGroup. Reuses the same
search + selection hooks as the new-group flow.

* Fetch group members live in add-members screen

Replaces the existingMemberPubkeys constructor arg + go_router extra
plumbing with useGroupMembers fetched inside the screen using the
groupId already in the route. This filter survives deep links and
process restoration, where extra would have come back null and
allowed re-adding existing members.

Per Jeff's review on #679.

* Extract shared UserPickerScreen widget

Both the new-group flow (UserSelectionScreen) and the new add-members
flow (AddGroupMembersScreen) were rendering nearly identical UI: slate
header, search field, selected-user bubbles, candidate list with
checkboxes and fade overlays, footer button. Pull that shared
structure into a reusable UserPickerScreen widget parameterised on
title, submit text/icon/callback, optional candidate filter, extra
loading state, and an optional caller-managed system notice.

Net effect: both consumers shrink to thin wrappers (~30 / ~70 lines
vs ~180 / ~220 before), and any future visual change to the picker
is now a single edit.

Per Jeff's review on #679.

* Prefer submit-error notice over caller-supplied notice

In UserPickerScreen, additionalNotice (e.g. a sticky member-fetch
error) was masking the submit-error notice triggered by onSubmit
throwing. If the user took an action and it failed, they got no
feedback. Flip the precedence so submit errors win; the
caller-supplied notice reappears once the user dismisses the submit
error.

Regression test pins the order in add_group_members_screen_test.dart.

* Address Pepi's review: use hook for add, localize error keys, move picker

- Use membersState.addMembers from useGroupMembers instead of calling
  groups_api.addMembersToGroup directly. Avoids reimplementing the
  hook's add-and-update-local-state path.
- Map membersState.error (a key, not a message) through a localizer
  so add failures show 'Failed to add members.' and fetch failures
  show 'Failed to load group members.'. Previously both were always
  labelled as fetch failures.
- Move UserPickerScreen from widgets/ to screens/ since it is a full
  Scaffold-based screen used by two thin-wrapper consumers. Added a
  doc comment naming both consumers.
- Drop the now-unused submitErrorMessage parameter and the
  caller-vs-internal notice precedence logic; both consumers route
  errors through additionalNotice now.

Per Pepi's review on #679.

* Collapse UserSelectionScreen into UserPickerScreen

The thin UserSelectionScreen wrapper just configured UserPickerScreen
for the new-group flow. Pull that config into a private helper
function inside routes.dart and delete the class. Now UserPickerScreen
is the single picker screen; AddGroupMembersScreen and the new-group
route both compose it directly.

Renames the user_selection_screen test file to
user_picker_screen_test.dart since it exercises UserPickerScreen via
the new-group route. routes_test.dart now asserts find.byType(
UserPickerScreen) for the empty-selectedUsers fallback.

Per Jeff's follow-up on #679.

* Remove stale UserPickerScreen doc comment
2026-05-20 14:47:46 +02:00
JeffGandGitHub d57a883e53 Implement iOS NSE notifications and Android push token plumbing (#673)
* Add remote push notification plumbing

* Checkpoint notification reliability cleanup

* Prepare notification reliability PR

* Fix notification PR review findings

* Address notification review follow-ups

* Clean up failed iOS data migration copies

* Address app resume and migration cleanup review

* Restore coverage for notification review fixes

* Fix App Group migration guard

* Address notification PR review feedback

* Cover notification review edge cases

* Address notification test review feedback

* Address NSE migration and startup retry review

* Use foreground catch-up for notification refresh

* Update whitenoise-rs foreground catch-up dependency

* Cover notification catch-up error paths

* Fix notification review edge cases
2026-05-20 14:47:09 +02:00
JeffGandGitHub 30ab65da28 [codex] Add key package developer controls (#685)
* Add key package developer controls

* Refactor bulk key package deletion hook

* Localize key package d-tag label

* Preserve bulk delete action on refresh failure
2026-05-20 12:21:45 +02:00
Mubarak AuwalandGitHub bc32a14ed9 fix: reset Whitenoise lifecycle after deleting data (#662)
* fix: reset Whitenoise lifecycle after deleting data

* fix: preserve global instance on failed delete

* docs: clarify delete reset recovery

* fix: await delete all data completion

* fix: ignore stale dm group ids

* fix: repair delete data test assertion

* fix: serialize whitenoise initialization

* fix: reset Whitenoise lifecycle after deleting data

* fix: satisfy delete reset CI checks

* test(reset_marker): cover default FlutterSecureStorage.deleteAll fallback

* fix: keep start dm errors visible
2026-05-19 17:29:15 +01:00
9b2d034d2f Add markdown rendering for chat messages (#665)
* Bridge whitenoise-markdown AST through flutter-rust-bridge

Bumps the whitenoise dep to 53fbbdd, which replaces the
`Vec<SerializableToken>` content representation with a
`whitenoise_markdown::Document` (CommonMark + GFM + nostr-extension
AST). Field names `tokens` / `content_tokens` are retained for FFI
continuity as the upstream author signaled.

- rust/src/api/markdown.rs: new module mirroring every AST node
  (Document, Block, Inline, ListItem, ListKind, Alignment,
  CodeBlockKind, AutolinkKind, NostrEntity, NostrHrp, TableCell) as
  FRB-friendly types, with From impls and 24 unit tests.
- rust/src/api/messages.rs: SerializableToken removed; tokens and
  content_tokens retyped to MarkdownDocument.
- rust/src/api/groups.rs: add disappearing_message_secs: None to
  NostrGroupConfigData / NostrGroupDataUpdate (incidental upstream
  change pulled in by the rev bump).
- Regenerated FRB bindings — recursive AST bridged cleanly as Dart
  sealed classes.
- lib/screens/chat_raw_debug_screen.dart: rewrote the token-dump
  section as a recursive AST pretty-printer.
- 12 test fixtures: tokens/contentTokens fixtures retyped.

No new rendering yet — content is still rendered as plain text by
WnMessageBubble; the parsed AST sits unused on ChatMessage until the
renderer lands in the next commit.

* Render markdown in chat message bubbles

Wires the parsed whitenoise_markdown::Document AST through to a new
WnMarkdownText widget that renders inside ChatMessageBubble.

Renderer (lib/widgets/wn_markdown_text.dart, 480 lines):
- Faithful AST walk: every Block (Paragraph, Heading, ThematicBreak,
  CodeBlock, BlockQuote, List, Table, MathBlock) and every Inline
  (Text, SoftBreak, HardBreak, Code, Emph, Strong, Strikethrough,
  Link, Image, Autolink, Math, NostrMention, NostrUri) renders.
- Task-list checkboxes for ListItem.checked = Some(...).
- Table with per-column alignment, scrolls horizontally on overflow.
- Inline images render as tappable '[image: alt]' text — no
  in-bubble network image loads.
- Math: source rendered as italic monospace (no LaTeX engine).
- URL scheme allow-list: http, https, mailto, nostr, tel. Anything
  else (javascript:, data:, file:, ...) gets no tap recognizer.
- Highlight-query support: per-inline substring matching merged with
  AST rendering, so search results show both markdown formatting and
  highlight backgrounds in the same view.
- HookWidget manages TapGestureRecognizer lifetimes via useRef +
  useEffect cleanup (no StatefulWidget).

Integration (lib/widgets/wn_message_bubble.dart, chat_message_bubble.dart):
- New `document` parameter on WnMessageBubble; `onLinkTap` /
  `onNostrTap` callbacks.
- Plain-text fast path: when the document is empty, or is a single
  Paragraph whose inlines are only Text/SoftBreak/HardBreak, the
  existing Text widget is used — plain messages render byte-identically
  to before, preserving the inline-timestamp layout in _TextWithTimestamp.
- Formatted messages route through WnMarkdownText; timestamp moves to
  its own row below the content.
- Deleted messages ignore the document.
- ChatMessageBubble provides default tap handlers using url_launcher
  (LaunchMode.externalApplication), guarded by isSafeMarkdownUrl.

Tests:
- test/widgets/wn_markdown_text_test.dart (51 widget tests) covers
  every Block and Inline variant, URL safety, highlight merging,
  maxLines truncation, empty / edge cases.
- test/widgets/wn_message_bubble_test.dart: 8 new tests for the
  plain-text / markdown routing and callback wiring.
- test/widgets/chat_message_bubble_test.dart: 3 new tests verifying
  url_launcher integration (safe URL launches externally, javascript:
  is silently dropped, nostr: URIs launch).
- All 4487 tests pass. Coverage: 99.04%.

Known limitations (worth surfacing in QA):
- Chat-list previews, reply quotes, and notifications still render
  the raw message string — markdown syntax characters will appear
  as literal text in those surfaces. This matches what most chat
  apps do and Whitenoise gives us plain content there anyway.
- Multi-block markdown messages move the timestamp to its own row;
  plain-text and single-formatted-paragraph messages keep the inline
  timestamp behavior.

* Render SoftBreak as newline so user-typed line breaks survive

The CommonMark default for SoftBreak is a space, which is fine on the
web where authors hard-wrap source for readability and expect rendering
to reflow. In a chat composer every Enter press produces an in-paragraph
newline, which the parser emits as SoftBreak — so the default would
collapse multi-line messages into one long visual line.

This matches what every chat app does (Slack, Telegram, Discord,
iMessage all map SoftBreak → newline). HardBreak already mapped to
newline; the two now share an arm.

The image-alt flattening helper (_flattenInlines) still emits a space
for SoftBreak — alt text shouldn't carry hard line breaks.

* Render @npub mentions as underlined display name

Per Vlad's design call: Nostr npub mentions should appear as the
mentioned user's display name with an underline, not as a chip and not
in link-blue. Other Nostr HRPs (note/nevent/nprofile/naddr/nrelay)
still render with the link-style fallback since they point to events,
not people.

- WnMarkdownText: new `mentionDisplayName: String? Function(String hexPubkey)?`
  callback. For each npub mention/URI the renderer decodes the bech32
  to a hex pubkey via hexFromNpub() and asks the caller for a display
  name. If resolved → '@DisplayName'. If unresolved or callback absent
  → '@npub1abcdefgh…wxyz' truncated fallback. Style is the surrounding
  text's color with TextDecoration.underline — mentions inherit the
  bubble text color rather than the link color.
- Plumbed through WnMessageBubble and ChatMessageBubble as a single
  optional param at each layer; ChatScreen wires it to its existing
  `presentName(getAuthorMetadata(hex))` cache. Mentions of message
  authors resolve immediately; mentions of users not yet in the
  metadata cache fall back to truncation until metadata loads (a
  later follow-up can subscribe to mentioned pubkeys).
- 3 new widget tests: resolved name, empty-name fallback, no-callback
  fallback. 55 markdown tests pass. Coverage 99.03%.

* Plumb mentionDisplayName into the long-press action menu

When a user long-presses a message, the action menu re-renders the
bubble inside MessageActionsModal — a separate widget tree from the
chat list. The mentionDisplayName callback was not being forwarded to
that re-render, so @mentions fell back to truncated npubs in the
preview while showing resolved names in the main chat. Wire it through
MessageActionsScreen \u2192 MessageActionsModal \u2192 ChatMessageBubble.

* Bump whitenoise-rs to 7577289f (bare-npub parsing)

Picks up two commits on the markdown PR since 53fbbdd:

  c4ef3d99  address review feedback
              \u2014 cosmetic-only: drops `#[cfg(feature = "serde")]`
              gating (serde derives now unconditional), removes doc
              references to deleted PLAN.md, internal scanner refactor.
              Database path switches from re-deriving the AST on read
              to reading from a persisted `content_tokens` column \u2014
              same observable behavior at the FFI boundary.

  7577289f  bare npubs should also parse
              \u2014 the parser now recognizes bare `npub1\u2026` strings as
              `Inline::NostrMention` (previously only `@npub1\u2026` and
              `nostr:npub1\u2026` worked). Restricted to the `npub` HRP
              to avoid false positives on prose starting with
              `note1\u2026` / `nevent1\u2026`. No AST shape change.

No bridge or renderer changes needed: our existing NostrMention/Npub
path with the mentionDisplayName callback handles the new shape
identically.

* Bump whitenoise-rs to f421a8ad (fix nested-blockquote misparse)

Single fast-forward commit on the markdown PR:

  f421a8ad  fix bug with inner quotes inside quotes
              \u2014 trailing paragraphs after a blank \`> >\` line inside
              a nested blockquote were escaping to the document root.
              Verified by re-running the local AST dump on the user's
              repro input: 'Trailing paragraph in inner quote.' now
              sits as the third child of the inner BlockQuote, not as
              document.blocks[0].

No FFI shape changes; bridge regen is format-noise only (handled by
dart format).

* Tapping an @npub mention opens the user's profile in a shade

Per Vlad's design call. Previously, tapping a mention launched a
nostr:<bech32> URI externally; that bounced users out to the OS and
felt out of place inside a chat.

New flow:
- chat_message_bubble: when the renderer reports an Npub tap, decode
  bech32 to hex and open UserProfileShade in-app. Non-npub Nostr URIs
  (note/nevent/nprofile/naddr/nrelay) keep the external launchUrl
  behavior \u2014 those point to events, not people.
- lib/screens/user_profile_shade.dart: new HookConsumerWidget pushed
  via PageRouteBuilder with an opaque:false barrier. Wraps
  WnUserProfileCard inside a WnSlate. Header navigates back with
  Navigator.pop. For non-self users, a 'Start chat' button pops the
  shade and routes to StartChatScreen. Copy actions surface as
  SnackBars.
- The shade drops input focus before opening (mirrors what chat_screen
  does before MessageActionsScreen.show). Without this the chat list
  snaps to the bottom on dismiss because the keyboard reappears and
  resizes the viewport.
- 7 new shade tests cover: card rendered, button shown/hidden by
  isSelf, copy and copy-error snackbars, header dismiss, and the
  start-chat button popping then routing via a stub GoRouter.
- Updated chat_message_bubble tests: the npub tap test now asserts
  launchUrl is NOT called; a new test exercises the non-npub
  external-launch fallback.

4498 tests pass. Coverage 99.03%.

* Bump whitenoise-rs to 935683af (bare-URL autolinks + longer bech32)

Two fast-forward commits from f421a8ad:

  dbd595b3  nostr bech32 are not limited to 90ch
              \u2014 lifts the bech32 length cap from BIP-173's 90 to 1024
              per NIP-19, which explicitly waives 90 for TLV-encoded
              nevent / naddr / nprofile entities with multiple relay hints.
              Pure parser change.

  935683af  add better bare-url parsing for whitenoise, http, https, tel, mailto
              \u2014 bare URLs now parse as Inline::Autolink for the schemes
              http://, https://, mailto:, tel:, whitenoise://, and
              whitenoise-staging://. Trailing punctuation excluded.
              The opaque form 'whitenoise:foo' (no //) stays literal.

No AST shape change. Bare URLs flow through our existing autolink path.
Bridge regen is format-noise only.

Note: my URL allow-list in wn_markdown_text.dart includes http, https,
mailto, nostr, tel but not whitenoise(-staging). Bare whitenoise://
links will render styled but tap is a no-op until that's extended.

* Restyle @npub mentions: user color, bold, no @ prefix

Per the latest design call (Vlad). Mentions now render with three
properties:

- No '@' prefix.
- Bold (FontWeight.w700).
- Color seeded from the pubkey via AvatarColor.fromPubkey() \u2014 same
  palette the bubble already uses for sender names, so a mention of
  someone reads in the same hue as their own messages in a group.

Underline is dropped \u2014 color + bold is the new mention affordance.

When hexFromNpub fails to decode (malformed bech32), the mention falls
back to its surrounding text color so it stays bold but doesn't pretend
to identify someone.

Tests updated: drop '@' from expected strings, replace underline
assertions with FontWeight.w700 + per-pubkey color.

* Bump whitenoise-rs to bb717d1c (content_tokens backfill migration)

Two fast-forward commits from 935683af:

  6fae0178  fix stale comment
  bb717d1c  backfill migration to reparse content_tokens with markdown parser

The migration adds m0044_reparse_content_tokens.rs in the whitenoise
crate. It runs once at app init and reparses any persisted
content_tokens rows whose stored AST is older than the current parser
(necessary now that the AST is persisted instead of re-derived on each
read). All internal to whitenoise; no FFI surface change.

The only types.rs touch is a doc-comment update on
ChatMessage::content_tokens, removing the now-stale 'never persisted'
sentence.

* bump + just regenerate

* Address review: rename MarkdownText, honor list 'tight' flag, stable test keys

Three findings from PR review:

1. Drop the 'Wn' prefix on MarkdownText.
   Per CLAUDE.md, the 'Wn'/'wn_' prefix is for design-system widgets
   that are presentational only and make no Rust API calls. This
   widget calls hexFromNpub (Rust) for mention decoding, so it doesn't
   belong in the design-system namespace.
   - Renamed class WnMarkdownText -> MarkdownText.
   - Renamed file lib/widgets/wn_markdown_text.dart -> markdown_text.dart.
   - Renamed test file accordingly.
   - Updated imports in chat_message_bubble, wn_message_bubble, and
     both test files (66 references in total).

2. Honor MarkdownBlock_List.tight.
   The renderer previously destructured only kind and items and used a
   fixed 4.h gap between list items, ignoring the tight flag the
   parser emits. Now forwards tight to _buildList and picks a smaller
   inter-item gap (2.h) for tight lists vs a larger one (8.h) for
   loose lists, matching CommonMark semantics.

3. Stable keys on task-list checkbox icons.
   The two task-list rendering tests were keying off Icons.check_box
   and Icons.check_box_outline_blank via find.byIcon, which is fragile
   if the underlying Icon ever changes. Added Key('check_box') and
   Key('check_box_outline_blank') to the renderer's checkbox Icon and
   switched the tests to find.byKey.

All 4536 Flutter + 75 Rust tests pass. Coverage 99.05%.

* Tap-to-open whitenoise:// links; unify mention/profile UI under Start Chat shade

- Allow whitenoise:// and whitenoise-staging:// in markdown_text safe schemes
  so taps on the Rust-emitted autolinks dispatch to the bubble handler.
- whitenoise://chat/<id> routes through GoRouter; whitenoise://user/<npub>
  and bare @npub mentions now open the Start New Chat menu as a shade.
- Show a localized error dialog when a whitenoise:// URL fails to parse.
- Make StartChatScreen shade-capable (transparent scaffold, scrollable
  content) and remove the now-redundant UserProfileShade.

Co-authored-by: nvk <797193+nvk@users.noreply.github.com>

* Add @mention picker with rich input and npub display resolution

- Typing `@` in a group chat opens a member picker driven by useGroupMembers;
  selecting an entry inserts a styled `@DisplayName` token in the input.
- MentionTextEditingController tracks mention spans, renders them in the
  primary color via buildTextSpan, and exposes `messageText` so the wire
  representation is the bare `@npub1...` form regardless of how it's
  displayed.
- Bare `@npub1...` strings typed or pasted into the input are auto-tracked:
  if a name resolver is wired, they show as `@DisplayName`; otherwise they
  truncate to `@npub1abcdefgh…wxyz` matching the receiver bubble.
- chat_screen wires the resolver from getAuthorMetadata so mentions of users
  outside the current group still display by name when we know them, while
  the picker stays group-only.
- useChatInput now uses MentionTextEditingController and saves
  controller.messageText to drafts.

Adapted from PR #659 (rich message rendering / mentions); the receiver-side
markdown path on this branch already handles NostrMention inlines so no
bubble changes were needed.

Co-authored-by: erskingardner <202880+erskingardner@users.noreply.github.com>

* fix jumpy cursor bug

* coderabbit

* strikethrough

* render in reply content block thing

* fmt

* carrots for our rabbit, including THE GOLDEN CARROT

* tests: cover mention-controller branches and bubble truncation path

Restores coverage above the 99% gate after the markdown/mention/timestamp
fixes. New cases:

- insertMention shifts a later tracked mention via _TrackedMention.shift
- buildTextSpan emits a leading text segment before a mention span
- setMentionTargets snaps the cursor when it sat mid-URI
- setMentionTargets handles a known URI that sits past existing text
- _shiftMentions shifts a mention past a prefix insertion
- _replaceBareNpubs preserves and remaps a later mention via
  _shiftPastReplacements when a bare npub is inserted before it
- IME composing-at-mention-boundary preservation + the non-composing
  drop control case
- A tight finite-height truncation path for _TextWithTimestamp

* fix: synchronous recognizer disposal + permissive mention boundaries

Markdown link/nostr recognizers were being torn down via
addPostFrameCallback. If the widget was removed before the next frame
(Navigator.pop during scroll, hot reload, fast list rebuild), the
callback still ran but no longer had a clean owner. Switch to disposing
the previous frame's recognizers synchronously at the start of build,
before assigning the new list. Unmount cleanup via useEffect stays.

Add a testWidgets that mounts a MarkdownText with a link, tears the
subtree down, pumps a follow-up frame, and asserts no exception is
thrown. Use Flutter's experimentalLeakTesting + leak_tracker so any
leaked TapGestureRecognizer surfaces a test failure.

Also:
- _isMentionBoundary now accepts sentence punctuation (. , ; : ! ? ) ])
  so '@npub1...!' / '@npub1...,' auto-truncate the way the Rust parser
  already permits.
- markdown_text's display-name resolver trims the returned name before
  the empty-check, matching the controller's behavior and dropping
  whitespace-only resolutions back to the truncated fallback.

* fix: use theme color for markdown highlight fallback; move mention picker below images

Addresses two review notes:

1. r3261917942 ("Use colors set in theme") — markdown_text.dart:145.
   The default highlightColor was Colors.yellow, a raw Material color
   inconsistent with the rest of the codebase. Fall back to
   context.colors.intentionInfoContent, which matches how
   wn_message_bubble.dart already builds its highlight color when the
   caller doesn't specify one.

2. Mention picker UX — chat_screen.dart. When media attachments are
   present the @-picker was being rendered above the entire input box,
   pushing the image preview down. It now rides along inside the
   attachment area, sitting below the image previews but still above
   the text field. Without media it still appears above the input as
   before. Two new chat-screen tests pin both branches in place,
   asserting the picker's position relative to the media preview and
   the input via getTopLeft / getBottomLeft.

---------

Co-authored-by: nvk <797193+nvk@users.noreply.github.com>
Co-authored-by: erskingardner <202880+erskingardner@users.noreply.github.com>
2026-05-19 17:40:59 +02:00
PepiandGitHub dba52c10dc Enable leave group (#675)
* chore: remove feature flag from use leave group hook

* chore: remove feature flag from chat list tile

* chore: remove feature flag provider

* docs: update changelog
2026-05-19 10:17:33 -04:00
Javier G. Montoya S.andGitHub 418fca8beb test(integration): add integration test support with basic messaging flow test (#666)
* test(integration): add integration test support with basic messaging flow test

* chore(): add docker-compose file with needed relays for integration tests
2026-05-18 12:03:00 -04:00
PepiandGitHub 406e5cee74 Fix show author name in last message of pending invites in chat list (#668)
* fix: show author name in last message of pending invites in chat list

* docs: update changelog
2026-05-18 10:34:04 -04:00
PepiandGitHub 672a975d3a Add archive option in chat removed warning and change wording for leave case (#657)
* refactor: add toggle method to use chat archive hook

* feat: add archive in removed warning and change wording for leave case

* docs: update changelog
2026-05-15 23:29:43 -04:00
PepiandGitHub 594f58aa78 Leave group from chatlist for non-last admins (#638)
* feat: add self demote to bridge

* feat: add leave warnings translations

* feat: add admin demote logic to leave hook

* feat: handle admin leave from chat list

* docs: update changelog
2026-05-15 11:38:51 -04:00
JeffGandGitHub 7c543c32a0 Add native deep links for users, chats, and settings (#661)
* Add copy deep link action to chat debug screen

* Address deep link review feedback

* Address deep link PR review follow-ups

* Scale profile QR code size
2026-05-15 12:34:58 +02:00
PepiandGitHub b8b9e05345 Fix QR scanner not showing camera on first attempt after giving permission (#654)
* fix: qr scanner happy path

* docs: update changelog
2026-05-14 11:33:57 -04:00
08d5c44a9b Add Chinese localizations (#647)
* Add Chinese localizations

* Fix Chinese locale selection

* Revert "Fix Chinese locale selection"

This reverts commit fa59dc2df05e90551334622d72b600ccc8f6816e.

* Use zh fallback for Simplified Chinese

* Fix script-specific locale equality

* fix: persist Chinese locale settings

Bump whitenoise-rs to the current ref with Chinese language variants and map zh/zh_Hant through the Rust language bridge.

Co-authored-by: stupidloud <klever1988@protonmail.com>

* fix: preserve system locale scripts

Preserve supported system locale script codes when resolving language settings and rendering the system language label.

---------

Co-authored-by: Jeff Gardner <202880+erskingardner@users.noreply.github.com>
2026-05-13 10:14:57 -04:00
Danny MandGitHub aa57640bd3 fix: close bug report screen with success snackbar after submission (#639) 2026-05-12 12:26:07 +02:00
Danny MandGitHub 5da4153da1 Dismiss chat list search on swipe-back instead of closing app (#640) 2026-05-11 17:22:07 +02:00
JeffGandGitHub e480f49e9e fix(android): external signer notification recovery (#635)
* Reconcile external signer callbacks on auth lifecycle

* Fix headless external signer notification recovery

* Point whitenoise-rs dependency at merged recovery fix

* Address external signer notification review feedback

* Address notification review follow-ups

* Cover external signer registry fallback paths

* Use developer log in foreground task
2026-05-11 17:21:50 +02:00
JeffGandGitHub 22bd445a85 Fix system notice dismiss after unmount (#646) 2026-05-11 08:52:22 +02:00
JeffGandGitHub a4fec570d9 Make group info screen scrollable (#648) 2026-05-10 14:35:10 +02:00
JeffGandGitHub 5a246d1ced fix: create android release checksums (#634) 2026-05-07 15:52:04 +02:00
bed6630303 Filter blocked users across Flutter messaging surfaces (#623)
* Filter blocked users and fix widgetbook analysis

* Filter blocked users across messaging surfaces

* Guard blocked-user loading windows

* Remove chat list subscription from chat screen and use chat summary  (#633)

* chore: update rust crate

* refactor: move chat summary type to its own rust bridge file

* feat: add get chat summary to rust bridge

* refactor: add use chat summary hook

* feat: add util for shared logig on chat summary dispplay across screens

* refactor: remove chat list suscription from chat screen and replace use chat profile with chat summary

* refactor: use chat summary instead of chat profile in chat info screen

* refactor: use chat summary instead of chat profile in chat invite screen

* refactor: remove use chat profile hook

* Address blocked chat review feedback

* Preserve unread count for blocked chat updates

---------

Co-authored-by: Pepi <mariajosefinaalliende@gmail.com>
2026-05-07 11:07:31 +02:00
PepiandGitHub 61f3e6bc93 Save to gallery (#619)
* feat: add gal depency to save images in gallery

* feat: add save to gallery translations

* feat: add save to gallery hook

* refactor: extract aspect ratio from dim to utils

* refactor: use wn icon button for deleting media in previews

* feat: add overlay to video to then have download button

* feat: save to gallery in media modal

* docs: update changelog

* test: improve coverage of chat message media
2026-05-07 08:43:44 +02:00
PepiandGitHub 85a42c26cb fix: chat list filter pills spacing issue (#632)
* refactor: expose method in wn search field to get height

* fix: chat list filter pills spacing issue

* docs: update changelog
2026-05-07 08:29:59 +02:00
PepiandGitHub 0bd3e3d2ea fix: scanner camera permission loop (#620) 2026-05-06 09:59:04 -04:00
DatawavandGitHub d11c10d60b Update donate Lightning address to whitenoise@donate.ipf.dev (#628) 2026-05-06 14:55:23 +02:00
JeffGandGitHub 931cec376a [codex] Bump whitenoise-rs master and refresh Android assets (#617)
* Bump whitenoise-rs master and refresh Android assets

* Update version to 2026.5.4

* Use shared group chat flag in chat screen
2026-05-05 17:46:22 +02:00
PepiandGitHub bf273c95e2 Refactor: apply review feedback from #586 (#618)
* refactor: remove unnecessary offline guards in add profile screen

* refactor: use system notice hook in edit profile screen

* test: split test case with double expect in two in wn auth buttons container

* test: groups test case in switch profile screen to improve readability
2026-05-05 10:21:57 -04:00
PepiandGitHub 9836cd4ece Leave group from chatlist UI (#604)
* fix: system notice positioning in chat list

* feat: add leave option on chat list tile menu

* docs: update changelog
2026-05-04 09:29:30 -04:00
PepiandGitHub 64d999e161 Improve chat list filters ux (#603)
* refactor: rename search and filters widget and move back filters to it

* fix: in chats show filters only with search and in archive treat them separately

* docs: update changelog
2026-04-30 11:28:11 -04:00
Nihar PatilandGitHub d889d8be50 Rename contact actions to Follow and Unfollow across locales (#595) 2026-04-30 10:13:30 -04:00
PepiandGitHub cb332db90a Fix notification icon for graphene os (#605)
* fix: notification icon for graphene os

* docs: update changelog
2026-04-30 10:11:13 -04:00
JeffGandGitHub b100fc1ec5 Add Fastlane release scaffolding (#601)
* Add Fastlane release scaffolding

* chore: document release flow and tag guard

* fix: address release automation review comments

* fix: harden release version parsing

* fix: resolve release review follow-ups

* fix: restore Android staging application id
2026-04-29 22:34:19 +02:00
PepiandGitHub 2300885c67 Use group capabilities in leave group hook (#600)
* feat: add feature flags provider for leave group

* feat: update leave group locales

* feat: add self remove to chat list chat summary

* feat: adjust use leave group hook to show it only when allowed

* chore: update podfile.lock
2026-04-29 10:08:16 -04:00
92b7e9b205 fix(android): enable boot auto-restart for foreground service (#577)
* fix(android): enable boot auto-restart for foreground service

The foreground service was not auto-restarting after device reboot,
so notifications only started working once the user reopened the
app. This was caused by two stacked misconfigurations:

1. `AndroidManifest.xml` explicitly disabled the plugin's
   `RebootReceiver` and `RestartReceiver` via `tools:node="replace"`.
   This was added in c3b4a1909 as defense-in-depth against an
   upgrade-path crash caused by a `foregroundServiceType 0x201`
   bitmask being persisted in native SharedPreferences. The
   primary fix for that crash — a one-time prefs cleanup in
   `MainActivity.onCreate()` — has been shipped for ~2 months,
   so the defensive disable is no longer load-bearing.

2. `ForegroundTaskOptions` did not set `autoRunOnBoot` or
   `autoRunOnMyPackageReplaced`, which default to `false`. Even
   with the receivers enabled, the plugin's `RebootReceiver`
   early-returns unless these flags are `true`.

Both fixes are required: the receiver is the *only* mechanism
the plugin provides for boot restart, and it only fires when
the Dart-side flags opt in.

Particularly impactful for GrapheneOS users, who typically run
without sandboxed Google Play Services and therefore cannot rely
on FCM wake-ups — the foreground service is their only path to
notifications.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: add headless-isolate spike to foreground task handler

Temporary instrumentation to verify the background isolate can reach
Rust (via flutter_rust_bridge), platform channels (path_provider), and
persisted data (accounts) when the foreground service starts.

Each step is logged with a `[SPIKE]` prefix so logcat can be filtered
with `adb logcat | grep SPIKE`. Also installs a log listener in the
task isolate's `_startCallback` — without one, log output never reaches
logcat because the main isolate's listener is scoped to its own isolate.

This will run on every foreground-service start, including:
- Normal app launch (main isolate already initialized whitenoise; spike
  will log "already initialized" for that step)
- RebootReceiver auto-start (fresh isolate; spike should pass every step)

Comparing logcat between the two paths tells us whether a real headless
task handler is viable. To be removed when follow-up lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(android): initialize keyring context in Application.onCreate

The Rust keyring crate panics with "android context was not
initialized" when invoked from the foreground service after a
headless boot restart. Root cause: Keyring.initializeNdkContext was
called from MainActivity.configureFlutterEngine, which only runs
when an Activity is launched. When RebootReceiver starts the
foreground service directly, no Activity launches, so the JNI
Context pointer never gets handed to Rust.

Move the init to a new WhitenoiseApplication.onCreate. Application
subclasses run on every process start regardless of entry point
(Activity, Service, or Receiver), so the Context is ready for any
component that touches Rust.

Confirmed via device spike: all spike steps passed pre-reboot, and
initializeWhitenoise specifically failed post-reboot with this exact
panic. Re-running the spike after this change should show a full
"[SPIKE] DONE" line on the post-reboot path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(spike): log WhitenoiseApplication.onCreate to prove it runs

Spike still panics post-install with "android context was not
initialized" despite the WhitenoiseApplication fix. Two possibilities:

1. WhitenoiseApplication.onCreate isn't actually running in the
   headless process (class-resolution or build-cache issue).
2. It is running, but Keyring.initializeNdkContext only sets up
   keyring-internal state, not the global ndk_context that other
   crates (rustls-platform-verifier-android, etc.) need.

Add an Android Log call so we can tell which case we're in. Also
wrap the Keyring call in try/catch so any failure there is visible
instead of silently allowing later crates to panic with a confusing
message.

After rebuilding with `flutter clean && flutter build apk --profile
--flavor staging --dart-define=WHITENOISE_ENABLE_SPIKE_LOGS=true`
and installing, logcat should include:

  adb logcat | grep -E 'SPIKE|WhitenoiseApp'

If WhitenoiseApp lines appear → Application subclass loaded; problem
is a broader ndk_context init (H2). If they don't → class isn't being
loaded on this path (H1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: extract NotificationSubscription for isolate-agnostic reuse

Previously the notification subscription lived inside
notification_provider.dart's _initializeAndListen and was gated on a
Riverpod Ref (so it could only run from the widget tree). This
prevents the foreground-task background isolate from reusing the same
subscription logic, which is what PR B needs.

Extract the subscription into lib/services/notification_subscription.dart
as a plain class. It takes dependencies via callbacks rather than Ref:

  - ActiveChatGetter: String? Function()
  - LocaleGetter: Locale Function()

The main-isolate provider wires these with ref.read(...). The upcoming
task handler will wire them with isolate-local defaults (no active
chat, locale from SharedPreferences).

Side effects of the extraction:
- formatNotification moves to the new file (still @visibleForTesting)
- handleNotificationUpdate becomes NotificationSubscription.handleUpdate
  (tests migrate accordingly)
- notification_provider.dart shrinks; its coverage:ignore block now
  wraps a smaller amount of genuinely-untestable Ref-wiring code

All ~40 existing tests for formatting + update handling migrate to
test/services/notification_subscription_test.dart. Coverage holds at
99%+. No behavioral change — this is a pure extraction.

Foundation for the next commit, which wires the new subscription into
the foreground-task handler for headless post-reboot delivery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(android): headless notification handler + isolate coordination

Replace the diagnostic SPIKE instrumentation with a real
_NotificationTaskHandler that runs inside the foreground-task
background isolate and owns the notification subscription when the
main UI isolate isn't running.

Behavior:

- On TaskStarter.system (boot / MY_PACKAGE_REPLACED, main isolate
  not running): task isolate bootstraps Flutter bindings + Rust FFI
  + whitenoise, loads locale from persisted app settings, and starts
  its own NotificationSubscription. Notifications arrive without
  the user opening the app.

- On TaskStarter.developer (main isolate called foregroundService.
  start()): task isolate bootstraps but does NOT start a subscription;
  main owns it. Task awaits coordination via sendDataToTask.

- Coordination protocol (via FlutterForegroundTask.sendDataToTask):
    {'event': 'main_started'}  -> task stops its subscription
    {'event': 'main_stopped'}  -> task starts its subscription
  The main-isolate caller triggers these via ForegroundService.
  notifyMainStarted() / notifyMainStopped(). Wiring into the main
  isolate's lifecycle observer lands in the next commit.

- onDestroy cancels the headless subscription so it doesn't leak
  across service restarts.

- Headless notification tap currently just launches the app via
  FlutterForegroundTask.launchApp(). Deep-link routing to the
  specific chat/invite is tracked in #488.

Cleanup:

- Remove _runHeadlessSpike, _spikeShowTestNotification, and the
  WHITENOISE_ENABLE_SPIKE_LOGS dart-define. Their job is done —
  device testing confirmed all headless steps work.

- Remove SPIKE-reference comments from WhitenoiseApplication.kt.
  Keep the try/catch + Log.e defensive around Keyring init.

- Logcat forwarding for ForegroundService + NotificationSubscription
  loggers remains, gated on kDebugMode, for development visibility.

Tests:

- Existing ForegroundService tests remain green; new tests cover
  notifyMainStarted/notifyMainStopped (sends correct event, no-op
  when service isn't running, no-op when disabled).

- _NotificationTaskHandler itself runs in a separate isolate and
  can't be unit-tested; its work (NotificationSubscription) is
  fully covered in test/services/notification_subscription_test.dart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(android): send lifecycle signals from main to task handler

Wire _WnAppState as a WidgetsBindingObserver so the main isolate can
tell the task handler whether it owns the notification subscription:

  - AppLifecycleState.resumed -> foregroundService.notifyMainStarted()
    (task handler yields)
  - paused/inactive/hidden/detached -> notifyMainStopped()
    (task handler takes over notification delivery)

This closes the loop with the _NotificationTaskHandler coordination
protocol from the previous commit. Combined with the TaskStarter-based
initial decision, the notification channel is always owned by exactly
one isolate:

  Boot/package-replaced (headless)    -> task isolate owns it
  Main isolate starts, pushes resumed -> main isolate owns it
  Main isolate pauses/detaches        -> task isolate takes over again

Edge cases accepted for this PR (tracked as follow-ups):
- If Android kills the main isolate without firing detached
  (rare under a foreground service, but possible), the task handler
  may remain idle. User reopen re-establishes ownership.
- Briefly during pause -> resume transitions, both isolates may
  deliver the same update. NotificationService uses a group-id-
  derived notification ID so the Android plugin de-duplicates
  visually.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(android): deep-link handoff for headless notification taps

When the task isolate fires a notification and the user taps it, the
task handler now persists the payload (groupId, isInvite,
receiverPubkey) via FlutterForegroundTask.saveData before calling
launchApp(). The main isolate consumes the payload on startup /
resume and routes to the correct chat or invite screen, switching
the active account first if the tap targeted a different one.

Changes:

- foreground_service.dart:
  - _persistTapAndLaunch: the task isolate's notification tap handler.
    Stashes the payload, then launches the app.
  - consumePendingNotificationTap(): public helper that reads and
    clears the stashed payload. Returns a typed PendingNotificationTap
    or null.

- notification_provider.dart:
  - Rename _onNotificationTap -> handleNotificationTap (public).
  - Refactor its signature to take plain values + a switchToProfile
    callback instead of a Riverpod Ref, so it's callable from both
    Provider (Ref) and ConsumerState (WidgetRef) contexts.

- main.dart:
  - _WnAppState consumes pending taps in two places:
    (a) post-frame callback from initState (covers first launch)
    (b) AppLifecycleState.resumed (covers returning from background)
  - Both paths guard against missing context / unmounted state.

Edge cases and trade-offs:
- In-app taps (main isolate active) still go through the synchronous
  NotificationService callback; no SharedPreferences round-trip.
- If the user taps a headless notification but the router isn't yet
  ready when we try to navigate, the logger warns ("No navigator
  context") and the payload is dropped — next tap works. Acceptable
  for first iteration; a deferred-navigation queue is a possible
  follow-up if it surfaces in testing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: add unit tests for handleNotificationTap profile-switch logic

Covers three cases using plain-value callbacks (no Riverpod Ref
needed):
- Switches profile when the active pubkey differs from the tap's
  target receiver
- Does NOT switch when they match
- Switches when there's no active pubkey (first-tap case)

TestWidgetsFlutterBinding.ensureInitialized() is required because
handleNotificationTap reaches Routes.navigatorKey.currentContext,
which touches the widget binding. The binding returns null in tests
(no router mounted), which the function tolerates via a warn log —
we're specifically testing the pre-navigation logic here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: restore 99.36% coverage baseline

CI flagged a coverage regression (99.36% → 99.07%) from the
headless-notification landings. Close the gap:

1. NotificationSubscription
   - Add `enabled` parameter (like NotificationService) so tests can
     bypass the Platform.isAndroid guard.
   - Add tests covering start(), stop(), double-start idempotency,
     stream event dispatch, stream errors, stream close, outer catch
     on initialize failure, and default-enabled fallback.
   - Narrow coverage:ignore on the defensive `if (_stopped)` branch
     after stream.listen — structurally unreachable (no await between
     the earlier _stopped check and this one) but kept as a guard in
     case future edits introduce one.

2. ForegroundService
   - Extract lifecycle switch into handleAppLifecycleChange method
     so tests can drive each AppLifecycleState branch directly.
   - Add tests for all five lifecycle states (resumed, paused,
     inactive, hidden, detached) plus the disabled-service no-op case.
   - main.dart's didChangeAppLifecycleState becomes a one-line call to
     this method, drastically shrinking untestable widget-glue.

3. routePendingTap
   - Extract the pending-tap routing logic from _WnAppState into a
     top-level function in notification_provider.dart. Takes plain
     values (bool isMounted, callbacks) — no Ref, no ConsumerState
     dependency.
   - Add tests covering null payload, unmounted guard, and the full
     route-through case.
   - main.dart now delegates to routePendingTap with an inlined bool
     for mounted — no more uncovered lambdas.

4. Widget test for WnApp lifecycle
   - Add a widget test that pumps WnApp and cycles through all
     AppLifecycleState values. Verifies the observer chain wires
     without throwing and exercises the main.dart switch body.

5. Re-export PendingNotificationTap + consumePendingNotificationTap
   from notification_provider.dart so main.dart imports from a single
   place.

Coverage: 99.07% → 99.36% (matches master baseline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(android): claim subscription ownership + detect failed headless start

Two reviewer-flagged correctness gaps in the headless notification
delivery path:

1. Main isolate didn't claim ownership when opening after a system-
   started service. ForegroundService.start() returns early when the
   service is already running (post-reboot path), and
   WidgetsBindingObserver doesn't replay the current lifecycle state
   at registration — so notifyMainStarted() never fired before the
   main isolate's NotificationSubscription started. Result: brief
   double-subscription window where the task isolate (with
   getActiveChatId: () => null) could surface notifications for the
   currently-open chat.

   Send notifyMainStarted() explicitly during _startForegroundAndSubscribe,
   right after the service is known to be running, before the main
   subscription starts.

2. Failed headless subscription start was recorded as success.
   NotificationSubscription.start() catches its own startup failures
   internally; _NotificationTaskHandler stored the wrapper before
   awaiting start, then logged success regardless. A transient boot-
   time failure left _subscription non-null forever, so future
   coordination signals early-returned and the user got perma-silence
   until service destruction.

   Move the _subscription assignment after the await, check
   sub.isRunning, and only persist the wrapper on actual attachment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 02:32:57 +02:00
PepiandGitHub 1f26b38edb Show offline notice in auth flows (#586)
* feat: show no internet notice in add profile screen

* feat: show no internet notice in switch profile screen

* feat: show no internet notice in login screen

* feat: show no internet notice in sign up screen

* feat: show no internet notice in edit profile screen

* docs: update changelog
2026-04-24 14:24:18 -04:00
Lee SalminenandGitHub 757cd9553c feat: add video media attachment support (#574) 2026-04-22 12:53:43 -04:00
PepiandGitHub ca85425958 Replace delete all key packages with delete legacy key packages (#585)
* refactor: rename debug info pill to wn pill

* feat: update rust crate and replace delete all kp with delete legacy kp

* docs: update changelog
2026-04-22 10:28:19 -04:00
PepiandGitHub 6cb653d12b Show offline notice in settings related screens (#576)
* refactor: wrap offline notice translations

* feat: show offline notice in network screen

* feat: show no internet notice in settings screen

* feat: show no internet notice in bug report screen

* feat: show no internet notice in key packages screen

* feat: show no internet notice in relay control screen

* docs: update changelog
2026-04-20 09:46:44 -04:00
emir yorulmazandGitHub 6838bf63fc Implement block/unblock UI (#578) 2026-04-17 23:13:21 +03:00
PepiandGitHub f67b27dc83 Leave group hook (#571)
* feat: add leave group translations

* feat: add leave group to bridge

* feat: add hook to leave group
2026-04-16 15:13:57 -04:00
emir yorulmazandGitHub 1514cc22a3 Add block/unblock hook (#573)
* Add block/unblock hook

* Prevent stale updates in hook

* Reset isBlocked when userPubkey is null

* rm block/unblock from public API
2026-04-16 21:26:15 +03:00
PepiandGitHub 4e36baa258 No internet notice in chat related screens (#569)
* feat: show no internet notice in chat invite screen

* feat: show no internet notice in chat screen

* feat: show no internet notice in chat support screen
2026-04-15 13:08:11 -04:00
PepiandGitHub 11de8fbfc5 Detect offline state and show notice in chat list (#565)
* chore: add connectivity plus

* feat: add provider to detect offline state

* test: improve coverage of add logs screen

* feat: add waiting for internet translations

* feat: show no internet notice in chat list

* docs: update changelog
2026-04-14 10:05:59 -04:00
Mubarak AuwalandGitHub 2cd588272d feat: add block/unblock user Flutter bridge (#554)
* feat: add UserBlockChanged to ChatListUpdateTrigger bridge

* feat: add block/unblock user bridge functions

* chore: regenerate flutter_rust_bridge bindings for mute_list API

* test: enforce exact order in userBlockChanged chat list assertions

* chore: bump whitenoise-rs rev and regenerate bridge bindings

* chore: format generated dart bridge files

* chore: rustfmt generated bridge file

* chore: rebase with master and regenerate bridge bindings

* fix: remove duplicate test function in chat_list
2026-04-14 11:32:43 +01:00
emir yorulmazandGitHub 0b53847034 UI Polish: Donate Screen (#553)
* UI Polish: Donate Screen

* Update CHANGELOG.md

* add widget keys to copyable fields

* find.textContaining for sp address

* 'Contribution acknowledgment' callout
2026-04-14 09:57:14 +03:00
PepiandGitHub 594d817bc6 Refactor default relays urls (#564)
* refactor: move default relays to its own file to avoid repetition

* test: add mock for default relays
2026-04-13 18:58:33 -04:00