Commit Graph

10620 Commits

Author SHA1 Message Date
Claude 0f533ea43a fix: guard against stale SDP answers in wrong signaling state
Our own renegotiation answers can echo back through the relay and
get processed as if from the peer. Check that the peer connection
is in HAVE_LOCAL_OFFER state before applying a remote answer SDP,
preventing the "Called in wrong state: stable" error.

https://claude.ai/code/session_014espsysob7MrE8X8e75jFX
2026-04-02 18:42:43 +00:00
Claude ea96dc9fb9 feat: disable outgoing video when phone is near ear during calls
Uses the proximity sensor to detect when the phone is held to the ear
and automatically pauses the camera/video track. Video resumes when the
phone is moved away, but only if the user had video enabled.

https://claude.ai/code/session_014espsysob7MrE8X8e75jFX
2026-04-02 18:35:59 +00:00
Vitor Pamplona 2cb88185e2 Merge pull request #2077 from vitorpamplona/claude/arti-proxy-port-change-fiNca
Implement Tor SOCKS port retry logic with dynamic port allocation
2026-04-02 14:16:49 -04:00
Claude 58df705be6 fix: change Arti SOCKS proxy default port to 17392 and retry on busy port
Change default port from 19050 to 17392 to avoid conflicts with other
Tor-using apps. When binding fails (address in use), increment the port
and retry up to 10 times before giving up.

https://claude.ai/code/session_01JEzjceY3ZaCHz5kL4DsKic
2026-04-02 18:13:18 +00:00
Vitor Pamplona 1d4778b4e0 Merge pull request #2076 from vitorpamplona/claude/fix-multi-device-call-ringing-m7FYV
Add multi-device call state synchronization
2026-04-02 14:11:34 -04:00
Claude 500928f1b3 fix: stop ringing on other devices when call is answered or rejected
When a user is logged in on multiple devices, all devices ring on an
incoming call. Previously, accepting or rejecting on one device left the
others still ringing.

Now, acceptCall() and rejectCall() also publish a self-addressed
gift-wrapped event (to the user's own pubkey) so other devices receive
it and transition out of the IncomingCall state. Added ANSWERED_ELSEWHERE
end reason and handling in onCallAnswered/onCallRejected for IncomingCall
state.

https://claude.ai/code/session_01X9juzyPYWqxRMCgDWEw8R1
2026-04-02 18:09:33 +00:00
Vitor Pamplona 13de5d5eee Merge pull request #2075 from vitorpamplona/claude/fix-video-freeze-on-share-stop-V8BX3
Add remote video activity monitoring to detect peer disconnections
2026-04-02 13:49:59 -04:00
Claude fe1a009fb4 fix: wire SDP renegotiation so video toggle works for remote peer
When toggling video mid-call, addVideoTrack triggers
onRenegotiationNeeded but the callback was empty — the remote peer
never received the updated SDP, so video never appeared on their side.

This wires up the full renegotiation flow using the existing
CallRenegotiateEvent (kind 25055):
- WebRtcCallSession: forward onRenegotiationNeeded to a callback
- CallController: create and send renegotiation offers, handle
  incoming renegotiation offers by creating answers
- CallManager: route CallRenegotiateEvent, allow CallAnswerEvent
  during Connected state for renegotiation answers

https://claude.ai/code/session_01WdmqktFBjXKB6PYRZ5b4FD
2026-04-02 17:05:10 +00:00
Vitor Pamplona 3d91fb75d4 Merge pull request #2074 from vitorpamplona/claude/add-ephemeral-giftwrap-event-PiIXY
Add support for NIP-59 Ephemeral Gift Wrap events (kind 21059)
2026-04-02 12:00:24 -04:00
Claude ec99373ae3 fix: show voice call UI when remote peer stops sharing video
When a peer disables their camera, the remote side was showing a frozen
last frame because the VideoTrack reference remained non-null. This adds
a VideoSink-based frame monitor that detects when frames stop arriving
and transitions the UI back to the voice call avatar view.

https://claude.ai/code/session_01WdmqktFBjXKB6PYRZ5b4FD
2026-04-02 15:58:03 +00:00
Vitor Pamplona 3ac271bf7b Merge pull request #2073 from vitorpamplona/claude/improve-markdown-typography-VZDUg
Improve markdown rendering with enhanced typography and styling
2026-04-02 11:55:46 -04:00
Claude 6116f45888 feat: add EphemeralGiftWrapEvent (kind 21059)
Adds an ephemeral variant of GiftWrap for transient encrypted messages
that relays don't need to persist. EphemeralGiftWrapEvent extends
GiftWrapEvent so all existing processing paths (decryption, routing,
notifications, call signaling) automatically handle it via type
hierarchy. Relay subscription filters are updated to request both kinds.

https://claude.ai/code/session_0157X96G6HLTzYxkdX9pyTSJ
2026-04-02 15:55:44 +00:00
Vitor Pamplona e33a1b600d Merge pull request #2072 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-02 11:31:25 -04:00
Crowdin Bot 94fc8dc38e New Crowdin translations by GitHub Action 2026-04-02 15:30:15 +00:00
Vitor Pamplona 90288a1c78 Merge pull request #2068 from vitorpamplona/claude/add-webrtc-calls-4kBSR
Add WebRTC-based peer-to-peer voice and video calling via NIP-AC
2026-04-02 11:28:23 -04:00
Claude a1b2c785aa fix: hide local video PiP when camera is disabled
When the user disables video, the SurfaceViewRenderer kept showing
the last captured frame (frozen). Now the local video PiP is only
rendered when isVideoEnabled is true — when camera is off, the PiP
disappears entirely revealing the black background.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 14:55:36 +00:00
Vitor Pamplona b631c6b148 Merge pull request #2071 from vitorpamplona/claude/redesign-article-screen-GwS0M
Enhance long-form article editor with banner image and tags UI
2026-04-02 10:54:04 -04:00
Claude eb19fc69ae fix: disable slug field when editing an existing article
The d-tag is the article's identity in Nostr addressable events.
Changing it on edit would create a new article instead of updating
the existing one. The slug field is now disabled when isEditing is true.

https://claude.ai/code/session_011JuQbdA12WdPxC7aGvUBqJ
2026-04-02 14:50:51 +00:00
Claude 31a3c06544 fix: complete overhaul of video toggle state and camera lifecycle
Four bugs fixed:

1. _isVideoEnabled defaulted to true — voice calls showed the
   camera icon as "active" even though no video was being sent.
   Changed default to false; only set true when video track is
   actually created (video call initiation or acceptance).

2. cleanup() reset _isVideoEnabled to true — next call started
   with wrong state. Changed to false.

3. stopCamera() disposed the capturer but toggleVideo re-enable
   path only called setVideoEnabled(true) without restarting the
   camera. No frames were captured. Now re-enable path calls
   startCamera() to create a fresh capturer.

4. startCamera() made public on WebRtcCallSession with guard
   against double-start (returns early if cameraCapturer != null).

State flow is now:
- Voice call: _isVideoEnabled=false, icon shows "camera off"
- Tap camera: creates track + starts camera, _isVideoEnabled=true
- Tap camera again: stops camera + disables track, =false
- Tap camera again: restarts camera + enables track, =true
- Video call: _isVideoEnabled=true from start, camera running

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 14:41:50 +00:00
Claude 10bcbdcd8c feat: redesign article creation screen with banner, tags, and slug
Redesigns the LongFormPostScreen to match the reference UI with:
- Visual banner image placeholder with dashed border (clickable to upload)
- Borderless large title field with placeholder styling
- Labeled Summary section
- URL Slug field mapped to the d-tag for user-defined article identifiers
- Tag/hashtag chip input with add button and removable chips
- Tags are included as hashtags in the published event

Adds tags and slug state to LongFormPostViewModel, wires them into
createTemplate() for event publishing, and loads them from drafts.

https://claude.ai/code/session_011JuQbdA12WdPxC7aGvUBqJ
2026-04-02 14:29:37 +00:00
Vitor Pamplona 0d4e2e06de Merge pull request #2070 from vitorpamplona/claude/add-post-action-buttons-KsoZk
Add floating action buttons for media and poll creation
2026-04-02 10:27:01 -04:00
Claude 1f5b39a9e4 fix: enable video toggle during voice calls
When a call starts as voice-only, no video track exists. Tapping
the camera toggle was calling setVideoEnabled(true) on a null
track, doing nothing.

Now toggleVideo() checks if a video track exists. If not, it
creates one on-demand (addVideoTrack + startCamera), upgrading
the voice call to include video. If toggling off, it stops the
camera to save battery.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 14:24:13 +00:00
Claude 6e695f4801 fix: pass Activity context to CallController for camera orientation
WebRTC's Camera2Session uses WindowManager to get device orientation
for frame rotation. WindowManager requires a visual (Activity)
context — using Application context throws IllegalAccessException.

Changed initCallController to pass the Activity context directly
instead of context.applicationContext.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 14:05:10 +00:00
Vitor Pamplona 7e23267cc5 Merge pull request #2069 from vitorpamplona/claude/nip-53-compliance-mh5pD
Add support for NIP-53 Meeting Spaces and Meeting Rooms
2026-04-02 10:04:00 -04:00
Vitor Pamplona 217389ecb7 Merge pull request #2067 from vitorpamplona/claude/implement-nip51-kinds-53nfc
Add NIP-51 list event types for content curation and management
2026-04-02 09:55:08 -04:00
Claude 3fbbfd0224 fix: request CAMERA permission for video calls
Video calls were failing with SecurityException because only
RECORD_AUDIO was requested at runtime. CAMERA permission is also
required for Camera2Capturer to open the front-facing camera.

Changes:
- CallPermissions now uses RequestMultiplePermissions instead of
  RequestPermission, with isVideo parameter to request CAMERA
  alongside RECORD_AUDIO for video calls
- ChatroomScreen passes isVideo=true for the video call button
- CallScreen passes isVideo based on call type when accepting
  incoming video calls

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 13:35:08 +00:00
Claude 8ee71e94e8 fix: use onAddTrack instead of onAddStream for remote video
With Unified Plan SDP semantics (which we set in RTCConfiguration),
onAddStream is deprecated and doesn't fire. Remote media tracks
arrive via onAddTrack(RtpReceiver, MediaStream[]) instead.

Changed WebRtcCallSession:
- onRemoteStream callback renamed to onRemoteVideoTrack(VideoTrack)
- onAddTrack now extracts VideoTrack from RtpReceiver.track()
- onAddStream kept as fallback for Plan B compatibility

This was why video never appeared after connecting — the remote
video track was never captured and _remoteVideoTrack stayed null.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 13:18:57 +00:00
Claude ccbaeacbd9 fix: prevent call screen from closing before state transitions
When initiateCall() runs async (Dispatchers.IO), navigation to
the ActiveCall screen happens before CallManager state changes
from Idle to Offering. The CallScreen sees Idle and immediately
calls onCallEnded() → popBack(), causing the screen to flash
and disappear.

Fix: CallScreen now waits 500ms on Idle before popping back.
If the state transitions to Offering within that window (normal
case), the screen stays. If it's still Idle after 500ms (no call
in progress), it pops back as before.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 13:02:15 +00:00
Claude 117cb68b9d docs: update NIP-AC with full implementation guidance
Major additions to the NIP specification:

- Gift wrap expiration: documented that outer gift wraps MUST
  include expiration tags so relays can garbage-collect signaling
- Event structures: added full JSON examples with all required
  fields (pubkey, id, sig, expiration, alt tags)
- ICE candidate buffering: documented that candidates MUST be
  buffered while ringing and NOT cleared on accept
- Staleness and deduplication: documented 20s staleness check
  and event ID dedup as spam prevention requirements
- NAT traversal: added TURN server guidance for same-WiFi and
  restrictive network fallback (~20% of cases)
- Audio routing: documented MODE_IN_COMMUNICATION, earpiece/
  speaker/Bluetooth SCO cycling, ringback tone, ringtone
- Platform integration: foreground service (microphone type),
  proximity wake lock, PiP mode, runtime permissions
- Error handling: SDP creation failures, ICE_CONNECTION_FAILED,
  session creation errors
- JSON escaping: noted that ICE candidate SDP strings MUST be
  properly escaped in the content JSON

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 12:59:59 +00:00
Claude b891bc7bb9 feat: add 20s expiration to call signaling gift wraps
All GiftWrapEvent.create() calls in WebRtcCallFactory now pass
expirationDelta=20 so relays can garbage-collect the wraps after
the signaling data is no longer relevant. The actual expiration
timestamp is createdAt + 20s + 2 days (to account for the
randomized createdAt in NIP-59 gift wraps).

Previously only the inner events had expiration tags — the outer
gift wraps persisted indefinitely on relays.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 12:45:48 +00:00
Claude 662e870f2a feat: implement all missing NIP-51 list event kinds
Add 14 missing NIP-51 event kinds with full CRUD support:

Replaceable lists:
- 10009 SimpleGroupListEvent (NIP-29 group membership)
- 10017 GitAuthorListEvent (NIP-34 code follows)
- 10018 GitRepositoryListEvent (NIP-34 repo follows)
- 10020 MediaFollowListEvent (multimedia follows)
- 10101 GoodWikiAuthorListEvent (NIP-54 wiki authors)
- 10102 GoodWikiRelayListEvent (NIP-54 wiki relays)

Addressable sets:
- 30004 ArticleCurationSetEvent (curated articles)
- 30005 VideoCurationSetEvent (curated videos)
- 30006 PictureCurationSetEvent (curated pictures)
- 30007 KindMuteSetEvent (kind-specific mutes)
- 30015 InterestSetEvent (hashtag interest groups)
- 30063 ReleaseArtifactSetEvent (software release artifacts)
- 30267 AppCurationSetEvent (software app curation)
- 39092 MediaStarterPackEvent (media starter packs)

All events follow existing patterns with NIP-44 private tag
encryption, ALT tags, hint providers, and TagArrayBuilder extensions.
Registered all new kinds in EventFactory.

https://claude.ai/code/session_01QrYyt6KQepNGtRMDcCr5zM
2026-04-02 04:01:03 +00:00
Claude a524890a36 feat: NIP-53 full compliance - render all live activity kinds
- Add case-insensitive role matching in ParticipantTag (accepts both
  "host" and "Host" from other clients per NIP-53 spec)
- Add PARTICIPANT role to ROLE enum per NIP-53 spec
- Add MeetingSpaceEvent (30312), MeetingRoomEvent (30313), and
  MeetingRoomPresenceEvent (10312) consumption in LocalCache
- Create rendering composables for MeetingSpaceEvent and MeetingRoomEvent
  with status flags (OPEN/PRIVATE/CLOSED for spaces, LIVE/PLANNED/ENDED
  for rooms) and participant display
- Add meeting kinds to all discovery feed relay filters (global, authors,
  hashtag, geohash, community)
- Add meeting kinds to ChannelCardCompose and LiveActivityCard thumb
  rendering
- Add meeting kinds to KindRegistry search aliases

https://claude.ai/code/session_01CJ6xfY9gRA1c5r4T1SrUuv
2026-04-02 03:42:31 +00:00
Claude 3f82bb5ff6 fix: address critical audit findings
#3 JSON escaping: CallIceCandidateEvent.serializeCandidate() now
escapes backslashes and quotes in SDP/sdpMid strings before
interpolation. Prevents malformed JSON when SDP contains special
characters.

#4 Thread safety: remoteDescriptionSet changed from Boolean to
AtomicBoolean to prevent race conditions between WebRTC callback
threads and main thread accessing the flag simultaneously.

#5 Async cleanup: hangup() no longer calls cleanup() synchronously
after launching the coroutine. Cleanup is now triggered by the
CallManager state collector when Ended state is reached, avoiding
resource disposal during active WebRTC callbacks.

#7 EglBase leak: createWebRtcSession() now wraps initialize() and
createPeerConnection() in try-catch that disposes the session on
failure, preventing EGL context leaks from failed initialization.

#10 SDP failure: createOffer/createAnswer onCreateFailure() now
calls onError() to surface SDP creation failures to the user
instead of silently logging them.

#11 Notification age: EventNotificationConsumer now uses
CallOfferEvent.EXPIRATION_SECONDS (20s) instead of hardcoded 30s
to align with the actual event expiration.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 03:25:11 +00:00
Claude 0e31afbb25 feat: add Bluetooth headset audio routing for calls
Audio route cycling: the speaker button now cycles through
Earpiece → Bluetooth → Speaker (or Earpiece → Speaker if no
Bluetooth device is connected).

CallAudioManager:
- Detects Bluetooth SCO/A2DP/BLE headset devices via AudioManager
- Manages Bluetooth SCO connection (startBluetoothSco/stopBluetoothSco)
- BroadcastReceiver monitors SCO state changes — auto-falls back
  to earpiece if Bluetooth disconnects during call
- Exposes audioRoute and isBluetoothAvailable as StateFlows

CallController:
- Replaced toggleSpeaker() with cycleAudioRoute() which delegates
  to CallAudioManager.cycleAudioRoute()
- Exposes audioRoute and isBluetoothAvailable flows to UI

CallScreen:
- Audio route button shows context-aware icon:
  VolumeOff (earpiece), VolumeUp (speaker), BluetoothAudio
- Blue tint for Bluetooth, cyan for speaker, white for earpiece

Manifest: BLUETOOTH_CONNECT permission added.
Strings: call_bluetooth string resource added.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 03:15:22 +00:00
Claude 6e92e8153d feat: add default TURN servers for same-network call connectivity
Add OpenRelay (metered.ca) free TURN servers to IceServerConfig
as defaults alongside STUN. This fixes calls between devices on
the same WiFi network where hairpin NAT isn't supported — the
~20% case where STUN-only fails.

TURN servers on ports 80, 443, and 443/TCP to bypass corporate
firewalls. Uses openrelayproject public credentials (20GB/month
free tier).

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 03:08:17 +00:00
Claude 0bd26bcacd feat: localized strings, PiP support, and ICE candidate fix
Localized strings:
- Added 28 string resources for all call UI text in strings.xml
- Replaced hardcoded strings in CallScreen, CallForegroundService,
  NotificationUtils, and RenderRoomTopBar with stringRes() calls
- Technical error messages in CallController remain English (no
  composable context available)

Picture-in-Picture:
- EnterPipOnLeave composable observes lifecycle and enters PiP
  mode when app goes to background during active call
- MainActivity manifest updated with supportsPictureInPicture and
  smallestScreenSize configChange
- 9:16 aspect ratio for portrait PiP window

ICE candidate fix (from logcat diagnosis):
- acceptIncomingCall() no longer clears pendingIceCandidates at
  start — candidates buffered while ringing were being wiped
  before flushPendingIceCandidates() could apply them
- This was causing "Flushing 0 buffered ICE candidates" on callee

StrictMode fix:
- initiateCall/acceptIncomingCall now run WebRTC session creation
  on Dispatchers.IO to avoid DiskReadViolation from native lib
  loading on main thread

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 02:57:15 +00:00
Claude 24d7fb1a92 refactor: fix race conditions, null safety, and dead code
Critical fixes:
- CallController.initiateCall/acceptIncomingCall now validate
  WebRTC session creation with try/catch and null check before
  proceeding. Errors shown to user via errorMessage flow.
- AccountViewModel.initCallController() is now @Synchronized to
  prevent double initialization. EventProcessor callManager wired
  before creating CallController. Callbacks set before exposing
  controller to avoid timing races.
- Removed duplicate state (currentCallId/currentPeerPubKey) from
  CallController — uses callManager.currentCallId()/
  currentPeerPubKey() as single source of truth.
- Removed dead network callback code (was only logging).

Code quality:
- CallScreen null-safety patterns now use remember{} for fallback
  StateFlow instances instead of creating new ones per recomposition.
- Removed dead rememberCallPermissionLauncher from CallPermissions.
- CallController cleaned up from 363 to 304 lines.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 02:45:36 +00:00
Claude 44d6345e16 refactor: improve separation of concerns in call feature
1. Move UI toggle state out of CallState.Connected:
   - Removed isAudioMuted/isVideoEnabled/isSpeakerOn from domain
     CallState. These are UI concerns, not call state.
   - Added StateFlows for toggles in CallController (isAudioMuted,
     isVideoEnabled, isSpeakerOn) with proper toggle methods.
   - CallScreen reads toggle state from CallController flows.
   - Removed toggleAudioMute/toggleVideo/toggleSpeaker from
     CallManager (no longer manages UI state).

2. Move ICE candidate parsing to quartz layer:
   - Added candidateSdp(), sdpMid(), sdpMLineIndex() parsers and
     serializeCandidate() to CallIceCandidateEvent in quartz.
   - Removed companion object with parseIceCandidate/
     serializeIceCandidate from CallController.
   - CallController now uses quartz-layer parsing directly.
   - Updated IceCandidateSerializationTest accordingly.

3. Add helper methods to CallManager:
   - currentCallId() and currentPeerPubKey() extract from any
     active state, reducing repeated when expressions.

4. Fix empty callId bug in ChatroomScreen:
   - Navigation to ActiveCall now uses callManager.currentCallId()
     instead of hardcoded empty string.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 02:39:41 +00:00
Claude f344f00e9d test: add previews and unit tests for call feature
Previews (CallScreenPreviews.kt):
- PreviewCallingScreen: outgoing call "Calling..." state
- PreviewConnectingScreen: WebRTC connecting state
- PreviewCallEndedScreen: call ended state
- PreviewIncomingCallScreen: incoming call with accept/reject
- PreviewConnectedCallScreen: active call with controls
- PreviewConnectedCallMuted: muted mic + speaker on state
All use ThemeComparisonColumn for dark/light side-by-side

Unit tests - quartz (CallTagsTest.kt):
- CallIdTag parse/assemble/roundtrip, edge cases (empty, wrong name)
- CallTypeTag parse voice/video, unknown types, roundtrip
- CallType.fromString validation

Unit tests - quartz (CallEventsTest.kt):
- All 6 event kinds have correct constants (25050-25055)
- CallOfferEvent.build includes call-id, call-type, p, expiration tags
- Expiration is 20 seconds
- Answer/ICE/Hangup/Reject/Renegotiate template content verification

Unit tests - amethyst (IceCandidateSerializationTest.kt):
- Parse ICE candidate JSON with all fields
- Parse with different sdpMLineIndex
- Parse with missing fields (defaults)
- Serialize produces valid JSON
- Roundtrip serialization preserves all fields

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 02:27:23 +00:00
Claude 8354bcd616 debug: add logging to trace call signaling flow
Temporary diagnostic logging to identify why calls hang at
Connecting. Logs added to:
- CallManager.onSignalingEvent: event kind, age, state
- CallController: answer received, ICE candidates (buffered vs
  direct), flush count, local ICE generation

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 02:12:37 +00:00
Claude ca21a8489f feat: complete call UI with video, audio routing, error handling
1. Audio mode: AudioManager switches to MODE_IN_COMMUNICATION on
   Connecting, restores to MODE_NORMAL on call end. Audio routes
   through earpiece by default during calls.

2. Notification actions: Accept/Reject buttons added to incoming
   call notification for quick action without opening the app.

3. Background handling: Full-screen intent brings app to foreground.
   CallManager state persists in ViewModel so the call screen
   picks up current state when navigated to via notification.

4. Call screen icons: Replaced text labels with Material icons -
   Mic/MicOff for mute, Videocam/VideocamOff for camera,
   VolumeUp/VolumeOff for speaker. Color-coded toggle states.

5. Video rendering: SurfaceViewRenderer composable wraps WebRTC
   video tracks. Remote video displays full-screen, local video
   as a small PIP in the top-right corner. Falls back to avatar
   display when no video tracks are active.

6. Ringback tone: ToneGenerator plays TONE_SUP_RINGTONE during
   Offering state (caller hears ringing). Stops on connect/end.

7. Error handling: CallController catches WebRTC session creation
   failures, SDP errors, and ICE failures. Errors exposed via
   errorMessage StateFlow and displayed as Snackbar on call screen.

8. Network handling: ConnectivityManager.NetworkCallback registered
   during active calls to detect WiFi/cellular changes.
   Unregistered on call end.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 02:08:56 +00:00
Claude b01ddc8958 fix: use microphone foreground service type instead of phoneCall
phoneCall type requires MANAGE_OWN_CALLS on SDK 34+, which needs
the app to be a default dialer. microphone type only requires
RECORD_AUDIO which is already granted via runtime permission.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 01:58:21 +00:00
Vitor Pamplona 7643fc05f1 Merge branch 'claude/add-webrtc-calls-4kBSR' of https://github.com/vitorpamplona/amethyst into claude/add-webrtc-calls-4kBSR
# Conflicts:
#	amethyst/src/main/AndroidManifest.xml
#	amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallForegroundService.kt
#	commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallManager.kt
#	quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipACWebRtcCalls/NIP-AC.md
#	quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipACWebRtcCalls/events/CallAnswerEvent.kt
#	quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipACWebRtcCalls/events/CallHangupEvent.kt
#	quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipACWebRtcCalls/events/CallIceCandidateEvent.kt
#	quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipACWebRtcCalls/events/CallOfferEvent.kt
#	quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipACWebRtcCalls/events/CallRejectEvent.kt
#	quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipACWebRtcCalls/events/CallRenegotiateEvent.kt
2026-04-01 21:52:02 -04:00
Claude 1016e46d50 fix: reduce event expiration to 20s and fix foreground service crash
- Change EXPIRATION_SECONDS from 300 to 20 for all call signaling
  events (offer, answer, ICE, hangup, reject, renegotiate)
- Change MAX_EVENT_AGE_SECONDS from 30 to 20 to match
- Update NIP-AC doc accordingly
- Fix SecurityException crash on SDK 36: phoneCall foreground service
  type requires MANAGE_OWN_CALLS permission. Switch to microphone
  type which only needs RECORD_AUDIO (already granted).

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 01:28:00 +00:00
Claude d93b384f72 fix: route call events via EventProcessor and add video call button
Fix call connection stuck at "Connecting":
- Inner events from unwrapped GiftWraps are dispatched through
  EventProcessor.consumeEvent(), not LocalCache.newEventBundles.
  The previous approach using newEventBundles observer never saw
  the inner call events because they're created internally during
  GiftWrap processing, not from relay arrivals.
- Restored callManager routing in EventProcessor.consumeEvent()
  and wired it via account.newNotesPreProcessor.callManager in
  AccountViewModel.initCallController()

Video call button:
- Added Videocam icon button in DM chat header (RenderRoomTopBar)
  next to the voice call button
- onVideoCallClick initiates a VIDEO type call with camera capturer

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 01:20:53 +00:00
Claude b0f3943e69 feat: add ringtone, vibration, proximity sensor, camera, and lock screen call UI
Ringtone & vibration:
- CallAudioManager plays default ringtone (looping) and vibrates
  in a 1s-on/1s-off pattern when IncomingCall state is active
- Automatically stops on accept/reject/hangup via cleanup()

Proximity sensor:
- PROXIMITY_SCREEN_OFF_WAKE_LOCK acquired during Connecting and
  Connected states, turns off screen when held to ear
- Released on call end

Camera for video calls:
- Camera2Enumerator finds front-facing camera
- CameraVideoCapturer attached to VideoSource at 640x480@30fps
- Camera stopped and disposed on call end

Full-screen intent on lock screen:
- Incoming call notification uses setFullScreenIntent() to show
  the call screen even when device is locked
- USE_FULL_SCREEN_INTENT permission added to manifest
- Notification has VISIBILITY_PUBLIC for lock screen display

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 01:20:53 +00:00
Claude 3f0d1aa60f fix: add event dedup, back button handling, and wake lock for calls
- Deduplicate signaling events via processedEventIds set in
  CallManager to prevent duplicate processing from multiple relays
- BackHandler on CallScreen calls hangup() before navigating back
- KeepScreenOn composable adds FLAG_KEEP_SCREEN_ON during calls
  and clears it on dispose

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 01:20:53 +00:00
Claude 8114739166 feat: add runtime permissions, audio routing, and call notifications
Runtime permissions:
- RECORD_AUDIO permission prompted before initiating or accepting calls
- rememberCallWithPermission() composable wraps call actions with
  Android runtime permission flow via accompanist

Audio routing:
- CallController.setAudioMuted/setVideoEnabled/setSpeakerOn now
  control WebRtcCallSession and Android AudioManager
- Mute/speaker/video toggles in ConnectedCallUI wired to actual
  hardware controls

Incoming call notifications:
- EventNotificationConsumer handles CallOfferEvent with follow-gate
  and 30s staleness check
- New CALL_CHANNEL notification channel (IMPORTANCE_HIGH)
- NotificationUtils.sendCallNotification shows caller name with
  auto-dismiss after 60 seconds
- Call notification cancelled on cleanup (accept/reject/hangup)

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 01:20:53 +00:00
Claude 0688a46604 fix: buffer ICE candidates and discard stale signaling events
Two fixes for call connectivity:

1. ICE candidate buffering: Candidates arriving before the WebRTC
   session exists (callee ringing) or before remote description is
   set (caller waiting for answer) are now queued in
   pendingIceCandidates and flushed once setRemoteDescription is
   called. This was the root cause of calls getting stuck at
   "Connecting" — ICE candidates were silently dropped.

2. Stale event filter: All signaling events older than 30 seconds
   are discarded in CallManager.onSignalingEvent() to prevent
   old cached events from triggering phantom calls.

Also: removed cleanup() from WebRTC onDisconnected callback to
avoid double-cleanup race with the CallManager state observer.

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 01:20:52 +00:00
Claude 8ddec3a0d7 fix: auto-reset CallManager state after call ends
CallManager now auto-resets from Ended to Idle after 2 seconds via
transitionToEnded(). Previously reset() was called from a
LaunchedEffect in CallScreen which could be cancelled when the
composable was disposed via popBack(), leaving the state stuck at
Ended and silently dropping subsequent incoming calls.

Also: CallController now observes CallManager state and auto-cleans
up the WebRTC session when a call ends (handles peer hangup case).

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
2026-04-02 01:20:52 +00:00