Commit Graph

10702 Commits

Author SHA1 Message Date
Claude a6c1909602 fix: filter out logged-in user from call member pictures in all call states
The Offering, Ended, Connected, and PipConnected call states were showing
the logged-in user's picture alongside other call members. IncomingCall and
Connecting already filtered correctly. Now all states consistently exclude
the current user via `- accountViewModel.account.signer.pubKey`.

https://claude.ai/code/session_01PFzKU8Y3nUQXhshA3MT5EM
2026-04-03 17:20:53 +00:00
Vitor Pamplona 31448ad1f6 Makes sure relays and tor are available if just the call activity starts. 2026-04-03 12:59:34 -04:00
Vitor Pamplona 858f13d60f Merge pull request #2103 from vitorpamplona/claude/debug-webrtc-connecting-Q7Jo8
Refactor CallController for multi-peer group calls with mesh topology
2026-04-03 12:58:14 -04:00
Claude fcecb5241c feat: implement full-mesh WebRTC group calls with per-peer PeerConnections
Replaces the single-PeerConnection architecture with full mesh topology
where each participant maintains one PeerConnection per peer, as
specified by NIP-AC.

WebRtcCallSession: Refactored to a pure PeerConnection wrapper that
accepts a shared PeerConnectionFactory. No longer manages media sources,
tracks, or camera — those are now shared across all peer sessions.

CallController: Manages per-peer sessions via ConcurrentHashMap. Shared
resources (PeerConnectionFactory, EglBase, audio/video sources, camera)
are initialized once and reused. Each peer gets its own WebRtcCallSession
with per-peer ICE candidate routing. Supports callee-to-callee mesh
connections with pubkey-based tie-breaking to avoid ofer glare.

CallManager: New methods for per-peer offer/answer publishing
(publishOfferToPeer, publishAnswerToPeer, beginOffering). Forwards ALL
answers to CallController (not just the first). New callbacks
onNewPeerInGroupCall and onMidCallOfferReceived for mesh setup. Handles
mid-call offers (same call-id) when already in Connecting/Connected state.

AccountViewModel: Updated callback wiring to pass peer pubkey with
answer events and register new group call callbacks.

https://claude.ai/code/session_01J5fJx9YbSBx1BsBMctiAm8
2026-04-03 16:54:00 +00:00
Vitor Pamplona 425f8463ec Merge pull request #2102 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-03 12:42:04 -04:00
Crowdin Bot 7342b6c066 New Crowdin translations by GitHub Action 2026-04-03 16:05:11 +00:00
davotoula f78e152d35 update translations: CZ, DE, PT, SE 2026-04-03 17:55:44 +02:00
Vitor Pamplona 57a505c877 Merge pull request #2101 from vitorpamplona/claude/marmot-encryption-group-state-3hXJ4
Add Marmot protocol support with MIP-00, MIP-02, MIP-03, MIP-05 implementations
2026-04-03 11:38:41 -04:00
Claude 0488245200 fix: send ICE candidates to all peers and exclude self from peer set
Two bugs caused WebRTC group calls to get stuck in "Connecting":

1. CallController.onLocalIceCandidate() used currentPeerPubKey() which
   returns only the first peer via firstOrNull(). ICE candidates were
   gift-wrapped to only one peer; the others never received them.
   Fixed by iterating over all currentPeerPubKeys().

2. CallManager.acceptCall() set Connecting.peerPubKeys to groupMembers
   which includes the local user's own pubkey. This caused
   currentPeerPubKey() to potentially return self, sending ICE
   candidates to oneself instead of the caller.
   Fixed by filtering out signer.pubKey from the peer set.

https://claude.ai/code/session_01J5fJx9YbSBx1BsBMctiAm8
2026-04-03 15:36:50 +00:00
Claude c4096dbd1a test: add comprehensive tests for Marmot Phase 2 components
38 test cases covering:
- ChaCha20Poly1305: RFC 8439 §2.8.2 test vector, round-trip, tamper
  detection, wrong key, invalid nonce/key length, large messages
- GroupEventEncryption: round-trip, empty/large messages, format
  validation, random nonce uniqueness, tamper detection, integration
  with GroupEvent.build()
- CommitOrdering: deterministic winner selection (lowest created_at,
  smallest id tiebreak), comparator sorting, EpochCommitTracker
  lifecycle (add/resolve/clear)
- KeyPackageUtils: validation (encoding, content, ref), selection
  policy (prefer non-last-resort, newest), migration kind detection
- MarmotFilters: all filter builders produce correct kinds, tags,
  authors, since parameters; no empty filters

https://claude.ai/code/session_01Ee5wmBAXwN46AJYUGYA9RQ
2026-04-03 15:33:00 +00:00
Claude 6cbd56c53d feat: add Marmot Phase 2 — encryption, group state, and lifecycle helpers
Implements encryption/decryption, gift-wrap flows, and utility logic for
the Marmot MLS-over-Nostr protocol, building on the Phase 1 event defs.

New files:
- ChaCha20Poly1305: standard AEAD (12-byte nonce) for MIP-03 outer layer
- GroupEventEncryption: encrypt/decrypt GroupEvent content with MLS key
- WelcomeGiftWrap: NIP-59 gift-wrap pipeline for Welcome messages
- KeyPackageUtils: selection policy, rotation, migration helpers
- CommitOrdering: deterministic conflict resolution for MLS commits
- MarmotFilters: relay subscription filter builders for all Marmot events
- TokenEncryption: ECDH + HKDF + ChaCha20 token encryption for MIP-05

https://claude.ai/code/session_01Ee5wmBAXwN46AJYUGYA9RQ
2026-04-03 15:23:31 +00:00
Vitor Pamplona 7eaa01f197 Merge pull request #2100 from vitorpamplona/claude/fix-call-ui-dark-mode-3Unot
Fix call UI to exclude current user from peer list
2026-04-03 11:19:49 -04:00
Claude 7210776113 fix: filter self from connecting call UI and fix dark mode text color
1. Filter out the logged-in user from the Connecting state UI, matching
   the existing behavior in IncomingCall state.
2. Change GroupCallNames default textColor from Color.Unspecified to
   MaterialTheme.colorScheme.onSurface so names are visible in dark mode
   (Box+background doesn't set LocalContentColor like Surface does).

https://claude.ai/code/session_016sDH1SL7P8aXhcyFaFtzYu
2026-04-03 15:18:50 +00:00
Vitor Pamplona e22ecf2825 Merge pull request #2099 from vitorpamplona/claude/fix-webrtc-disconnect-issue-1HDAl
Fix group call handling for answer/reject events
2026-04-03 10:40:49 -04:00
Claude 4a529a304c fix: prevent group call participants from disconnecting when another member answers/rejects
In group calls (e.g. Alice calls Bob and Charlie), when Bob accepts the
call, a CallAnswerEvent is gift-wrapped to all group members including
Charlie. Charlie's CallManager, still in IncomingCall state, was treating
ANY CallAnswerEvent as "answered elsewhere" (intended for multi-device
scenarios) and ending the call prematurely.

The fix checks whether the answering/rejecting peer is actually the
current user (signer.pubKey) before treating it as an "answered/rejected
elsewhere" event. If it's a different group member, we simply ignore it
and keep ringing.

https://claude.ai/code/session_01EYzWB93PZRqw15QuQadCf4
2026-04-03 14:37:29 +00:00
Vitor Pamplona d7eabf0f80 Merge pull request #2098 from vitorpamplona/claude/mls-nostr-integration-nFDom
Add Marmot protocol support (MIP-00 through MIP-05)
2026-04-03 10:31:16 -04:00
Vitor Pamplona cd7850aa73 Merge pull request #2097 from vitorpamplona/claude/filter-video-call-display-uQAXE
Exclude current user from group call member displays
2026-04-03 10:25:14 -04:00
Claude 33ae066bdc feat: add Marmot Protocol event definitions for MLS-over-Nostr
Implements Nostr event kinds for the Marmot Protocol (MIP-00 through MIP-05),
which provides end-to-end encrypted group messaging using MLS (RFC 9420) with
Nostr as the identity and transport layer.

Event kinds added:
- kind 30443: KeyPackage (addressable, MIP-00) — MLS KeyPackage publication
- kind 10051: KeyPackage Relay List (replaceable, MIP-00) — KeyPackage discovery
- kind 444: Welcome (MIP-02) — gift-wrapped group onboarding
- kind 445: Group Event (MIP-03) — encrypted group messages and control
- kind 446: Notification Request (MIP-05) — push notification trigger
- kind 447: Token Request (MIP-05) — push token gossip
- kind 448: Token List (MIP-05) — push token sync response
- kind 449: Token Removal (MIP-05) — push token cleanup

Also includes:
- MarmotGroupData model (MIP-01) — 0xF2EE extension data structure
- MlsCiphersuite enum — RFC 9420 ciphersuite registry
- Full tag definitions following existing quartz patterns

https://claude.ai/code/session_017jmJgCAXnaiVh1HsbC8CrW
2026-04-03 14:23:55 +00:00
Claude 6b6adf81f2 fix: filter logged-in user from incoming video call display
Remove the current user's picture and name from the incoming call
screen so only the other participants are shown.

https://claude.ai/code/session_01F4LLE1PZ84oNURN8RQwNuc
2026-04-03 14:20:47 +00:00
Vitor Pamplona 090da72d35 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-03 10:10:10 -04:00
Vitor Pamplona f35fafec92 Fixes missing imports 2026-04-03 10:03:43 -04:00
Vitor Pamplona 64bdfd0231 Merge pull request #2095 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-03 09:57:10 -04:00
Crowdin Bot cedb9cba76 New Crowdin translations by GitHub Action 2026-04-03 13:35:02 +00:00
Vitor Pamplona 2f7fd94d28 Merge pull request #2096 from vitorpamplona/claude/debug-webrtc-connecting-h4M8P
Add comprehensive logging to WebRTC call session and controller
2026-04-03 09:33:41 -04:00
Claude 76477373d9 debug: add comprehensive WebRTC call flow logging to diagnose "stuck on Connecting"
Adds debug logging across CallManager, CallController, and WebRtcCallSession
to trace the full call lifecycle: SDP offer/answer creation, remote description
set success/failure, ICE candidate exchange, ICE connection state transitions,
and signaling state changes. The noOpSdpObserver is replaced with a logging
observer so setRemoteDescription success/failure is visible in logcat.

Filter logcat with: CallManager|CallController|WebRtcCallSession

https://claude.ai/code/session_01M3yyyRyu8KWJZ5PjNt4V2H
2026-04-03 13:30:50 +00:00
Vitor Pamplona 501e028567 Merge pull request #2093 from greenart7c3/claude/consolidate-poll-screen-KVUg2
Simplify poll creation UI and consolidate poll types
2026-04-03 09:28:56 -04:00
Vitor Pamplona 7eab27fada Merge pull request #2094 from vitorpamplona/claude/fix-hardware-bitmap-performance-U8bmz
Disable hardware acceleration for profile picture image loading
2026-04-03 09:26:14 -04:00
Claude 423b9ac4aa fix: disable hardware bitmaps for notification icons to avoid StrictMode violation
Hardware bitmaps cannot be efficiently read for pixels, which is required
when parceling notifications. Adding allowHardware(false) to all image
requests used for notification bitmaps prevents the slow pixel readback.

https://claude.ai/code/session_018y75qshU48REuHXxLH3Tmb
2026-04-03 13:18:55 +00:00
Vitor Pamplona 316290ac32 Merge pull request #2092 from vitorpamplona/claude/add-hashtag-limit-filter-N8sMZ
Add maximum hashtag limit security filter
2026-04-03 08:55:13 -04:00
Vitor Pamplona db494d2ac7 Merge pull request #2091 from vitorpamplona/claude/add-members-to-ptags-O8lvL
Add group call support with multi-member p-tags and per-peer SDP
2026-04-03 08:55:00 -04:00
Claude 1bc94f5a29 refactor: remove P2P vs group branching in CallManager
The group factory methods work correctly with any number of members,
including single-peer calls. Remove the if/else branches that
duplicated P2P vs group logic in acceptCall, rejectCall, hangup,
sendRenegotiation, and sendRenegotiationAnswer.

https://claude.ai/code/session_013h2E7spwHDgSjunqumsYgp
2026-04-03 12:35:53 +00:00
Vitor Pamplona 16acce729a Merge pull request #2090 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-03 08:35:45 -04:00
Claude 2a24b711cd feat: add configurable max hashtag limit filter for spam prevention
Adds a security setting to hide posts with more than X hashtags (t tags),
defaulting to 5. Applied at the feed filter level via FilterByListParams.match()
and Account.isAcceptable() to cover all feeds. Configurable in Security Filters
settings, with 0 to disable.

https://claude.ai/code/session_01NMVypgGSU9EjQA7hZ9uvjD
2026-04-03 12:34:32 +00:00
Claude 23e8ef6169 feat: hide call buttons in chats with more than 5 members
Full-mesh WebRTC group calls degrade at 5+ participants (each
participant uploads N-1 streams). Hide the voice and video call
buttons in the chat header when the room has more than 5 members.

https://claude.ai/code/session_013h2E7spwHDgSjunqumsYgp
2026-04-03 12:25:47 +00:00
Claude 0dc38de839 feat: add recipientPubKeys()/groupMembers() helpers and multi-device docs
- Add recipientPubKeys() and groupMembers() helper methods to
  CallAnswerEvent, CallHangupEvent, CallRejectEvent, and
  CallRenegotiateEvent (matching the existing pattern in CallOfferEvent)
- Document in NIP-AC spec that group calls handle multi-device
  self-notification implicitly by including the sender's own pubkey
  in the gift-wrap recipient set

https://claude.ai/code/session_013h2E7spwHDgSjunqumsYgp
2026-04-03 12:01:34 +00:00
Claude 7020eb003c fix: per-peer SDP handling and invitePeer group context
SDP payloads (offer, answer, renegotiate) are specific to individual
PeerConnections and cannot be shared across peers. This commit:

- Adds group-context overloads to createCallOffer, createCallAnswer,
  and createRenegotiate that include all member p-tags but wrap to
  a single target peer
- Removes createGroupRenegotiate (renegotiation is always per-peer)
- Updates sendRenegotiation/sendRenegotiationAnswer to take explicit
  peerPubKey and use per-peer wrapping with group p-tags
- Updates invitePeer to include all existing group members + invitee
  in p-tags so the new peer sees the full group composition
- Documents SDP per-peer vs sign-once distinction in NIP-AC spec

https://claude.ai/code/session_013h2E7spwHDgSjunqumsYgp
2026-04-03 11:57:32 +00:00
Crowdin Bot 052a4d4c58 New Crowdin translations by GitHub Action 2026-04-03 11:40:54 +00:00
davotoula be6a6346ae update translations: CZ, DE, PT, SE 2026-04-03 13:38:44 +02:00
Claude b2584e6705 feat: include all group members in p-tags of inner ephemeral events
All signaling event types in group calls (answer, hangup, reject,
renegotiate) now include p-tags for every group member, matching the
pattern already used by CallOfferEvent. This allows signing the inner
event once and gift-wrapping it separately for each recipient.

- Add Set<HexKey> build() overloads to CallAnswerEvent, CallHangupEvent,
  CallRejectEvent, and CallRenegotiateEvent
- Add createGroupCallAnswer, createGroupRenegotiate factory methods
- Fix createGroupHangup to sign once instead of per-peer
- Update createGroupReject to accept full member set
- Update CallManager to use group methods when in group call
- Document group call p-tag convention in NIP-AC spec
- Add tests for all group build overloads

https://claude.ai/code/session_013h2E7spwHDgSjunqumsYgp
2026-04-03 04:53:17 +00:00
Vitor Pamplona 2fa045370d Merge pull request #2089 from vitorpamplona/claude/review-webrtc-calls-sAQqe
Improve call handling: busy rejection, peer tracking, and ICE state
2026-04-02 23:01:46 -04:00
Claude ea65dd76e7 fix: correct group call hangup signaling and connection resilience
- Include pending peers in hangup notification so ringing users get
  immediate feedback instead of waiting for the 60s timeout
- Sign individual hangup events per peer with correct p-tag instead
  of reusing a single event with the first peer's tag
- Stop treating ICE DISCONNECTED as terminal; only hang up on FAILED
  since DISCONNECTED is often transient (network switch, packet loss)
- Send "busy" reject when receiving a call while already in one

https://claude.ai/code/session_01HpowdWMK77pA35ABn9XWpD
2026-04-03 02:49:37 +00:00
Vitor Pamplona 532d794f40 Merge pull request #2088 from vitorpamplona/claude/review-call-permissions-utwfD
Add group call support with participant management
2026-04-02 22:14:35 -04:00
Claude 9433505064 fix: move ringtone and audio mode setup off the main thread
RingtoneManager.getRingtone() and audio mode switching do disk I/O,
causing StrictMode DiskWriteViolation when called from the state
collector on the main thread. Wrap startRinging() and
switchToCallAudioMode() in withContext(Dispatchers.IO).

https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
2026-04-03 02:05:36 +00:00
Claude a65e192f03 feat: add participant to existing call + comprehensive call previews
Add ability to invite new users into an ongoing call:
- CallManager.invitePeer() sends a call offer to a new peer using
  the current callId, adding them to pendingPeerPubKeys
- CallController.invitePeer() exposes this to the UI layer
- Add PersonAdd button to connected call controls
- Add AddParticipantDialog with user search (reuses UserSuggestionState)
- New strings: call_add_participant, call_search_users

Rewrite CallScreenPreviews to showcase all call states:
1. Offering P2P call (Calling...)
2. Offering group call (3 avatars)
3. Connecting
4. Incoming voice call
5. Incoming video call
6. Incoming group call (4 avatars)
7. Connected P2P voice call with controls
8. Connected muted + video + speaker
9. Connected group call with pending peers
10. Connected with Bluetooth audio
11. Call ended
12. Large group (5+ members, +N badge)
13. PiP calling
14. PiP connected

https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
2026-04-03 01:07:14 +00:00
Claude 0c7aa67dd8 feat: start group call on first answer, show pending peers
The call now starts immediately when the first peer answers instead
of waiting for all participants. Remaining peers are tracked as
pending and shown in the UI with a "Waiting for others to join..."
indicator. When a pending peer answers, they move to the connected
set. If a pending peer rejects or hangs up, they're silently removed.

Changes:
- Add pendingPeerPubKeys to Connecting and Connected states
- Add allPeerPubKeys helper on Connected for UI rendering
- Split peers into connected/pending on first answer in onCallAnswered
- Handle subsequent answers in Connecting and Connected states
- Carry pending peers through Connecting -> Connected transition
- Show all peers (connected + pending) in call UI avatars
- Display "Waiting for others to join..." when pending peers exist

https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
2026-04-03 00:58:50 +00:00
Vitor Pamplona 5c6772b7cc Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-02 20:36:56 -04:00
Claude 3962f75583 feat: keep group call alive when a peer leaves until only 2 remain
When a peer hangs up or rejects in a group call, remove them from the
participant set instead of ending the entire call. The call only ends
when no peers remain (all others left). Similarly, if a peer rejects
during the Offering phase of a group call, remove them but continue
ringing remaining peers.

For incoming group calls, if the original caller hangs up the call
ends immediately. If another group member leaves, the call continues
as long as at least 2 members (including self) remain.

https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
2026-04-03 00:29:24 +00:00
Claude 4e2143641f feat: render group member pictures in call UI, capped at 4 + remaining count
- Add GroupCallPictures composable showing up to 4 user avatars in
  a grid layout (same pattern as NonClickableUserPictures in chat)
- When >4 members, show 3 avatars + a "+N" badge in the 4th slot
- Add GroupCallNames composable showing up to 2 names + "+N" remaining
- Update CallInProgressUI, IncomingCallUI, ConnectedCallUI, PipCallUI,
  and PipConnectedCallUI to pass full peerPubKeys/groupMembers sets
  instead of only .first()

https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
2026-04-03 00:22:37 +00:00
Claude 6946a5a263 fix: add missing call permissions for all supported Android versions
- Add MODIFY_AUDIO_SETTINGS for audio routing (speaker, earpiece, BT SCO)
- Add BLUETOOTH permission for API 26-30 (maxSdkVersion=30)
- Request BLUETOOTH_CONNECT at runtime on API 31+ for BT audio
- Add FOREGROUND_SERVICE_CAMERA for video calls on API 34+
- Update foreground service type to microphone|camera for video calls
- Guard Bluetooth operations with permission check in CallAudioManager
- Add uses-feature for microphone with required=false

https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
2026-04-03 00:13:55 +00:00
Vitor Pamplona 70b205326c Merge pull request #2087 from vitorpamplona/claude/enable-group-calls-hv7v8
Add group call support to WebRTC call system
2026-04-02 20:00:26 -04:00