NIP-04 encryption for sending DMs is now deprecated. All new messages
are sent using NIP-17 (gift-wrapped sealed messages). Reading NIP-04
messages remains supported for backward compatibility.
When a recipient lacks both a DM relay list (kind 10050) and NIP-65
inbox relays, the send button is disabled and a warning is shown
explaining that messages cannot be delivered.
Changes:
- ChatNewMessageState: Remove nip17/requiresNip17 toggles, add
recipientsMissingDmRelays state, always send via NIP-17
- ChatNewMessageViewModel: Always use NIP-17, remove NIP-04 send
paths, add recipient relay status checking
- ToggleNip17Button -> Nip17Indicator: Replace toggle with static
NIP-17 indicator (always on)
- PrivateMessageEditFieldRow: Show warning when recipients lack
DM relay lists, hide message input
- ChatFileSender: sendAll() always uses NIP-17
- Desktop ChatPane: Remove NIP-17 toggle, show relay warning
- ChatroomView: Reactively check recipient DM relay availability
https://claude.ai/code/session_01T7QhUW9cZogk4DxDXbbbJJ
Add full send + receive encrypted media support in desktop DM chat:
- Paperclip attach button and drag-and-drop in ChatPane (NIP-17 mode only)
- AES-GCM encryption before upload to Blossom server
- ChatMessageEncryptedFileHeaderEvent (kind 15) wrapped in GiftWrap
- sendNip17EncryptedFile() added to IAccount interface and implementations
- DesktopUploadOrchestrator.uploadEncrypted() with proper encrypted hash
- DesktopBlossomClient ByteArray upload overload for encrypted blobs
- LRU cache in EncryptedMediaService to avoid re-downloading
- Error handling: retry on failure, disable send during upload
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code reuse
- Extracted remuxTracks() helper to deduplicate video/audio remux loop (~40 lines)
- Extracted stripAfterCompression() to deduplicate identical blocks in upload()/uploadEncrypted()
- Replaced 8 identical showStrippingFailureDialog() copy-pastes with shared SuspendableConfirmation utility
Privacy
- 4 metadata ViewModels now error on strip failure instead of silently uploading unstripped media
Efficiency
- Eliminated temp input file copy for video/audio — MediaExtractor reads URIs directly
Bug fixes
- Fixed Long.toInt() overflow for MP3 files >2GB
- Fixed MP3 temp file leak on exception and null InputStream paths
Cleanup
- Mutex field and wrapping the suspendCancellableCoroutine in mutex.withLock. Concurrent callers now queued
- Converted MetadataStripper from class to object (stateless)
- Moved StrippingFailureState from service layer to UI as generic ConfirmationCallbacks
already handles metadata stripping)
- Pass stripMetadata=false when toggle is hidden for compressed video
- Hide compression quality for audio and other non-compressible media types
- refactor