feat(chats): always show attach button, auto-force NIP-17 with snackbar

- Attach button always visible in DM input (not gated on NIP-17 mode)
- Drag-and-drop always enabled
- When files are attached in NIP-04 mode, auto-switches to NIP-17
  with snackbar: "Switched to NIP-17 — file attachments require
  encrypted messaging"
- NIP-17 toggle remains for manual control

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
nrobi144
2026-03-19 09:02:05 +02:00
parent 9187bf448f
commit d9848b868b
@@ -50,6 +50,8 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@@ -154,6 +156,20 @@ fun ChatPane(
// File attachment state
val attachedFiles = remember { mutableStateListOf<File>() }
var isUploading by remember { mutableStateOf(false) }
val snackbarHostState = remember { SnackbarHostState() }
// Helper: attach files and auto-force NIP-17 if needed
fun attachFiles(files: List<File>) {
val mediaFiles = files.filter { it.extension.lowercase() in MEDIA_EXTENSIONS }
if (mediaFiles.isEmpty()) return
attachedFiles.addAll(mediaFiles)
if (!isNip17) {
messageState.toggleNip17()
scope.launch {
snackbarHostState.showSnackbar("Switched to NIP-17 — file attachments require encrypted messaging")
}
}
}
// Drag-and-drop target for file attachments (NIP-17 only)
var isDragOver by remember { mutableStateOf(false) }
@@ -168,7 +184,7 @@ fun ChatPane(
if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
@Suppress("UNCHECKED_CAST")
val files = transferable.getTransferData(DataFlavor.javaFileListFlavor) as List<File>
attachedFiles.addAll(files.filter { it.extension.lowercase() in MEDIA_EXTENSIONS })
attachFiles(files)
dropEvent.dropComplete(true)
return true
}
@@ -195,12 +211,13 @@ fun ChatPane(
messageState.load(roomKey)
}
Box(modifier = modifier.fillMaxSize()) {
Column(
modifier =
modifier
Modifier
.fillMaxSize()
.dragAndDropTarget(
shouldStartDragAndDrop = { isNip17 },
shouldStartDragAndDrop = { true },
target = dropTarget,
).then(
if (isDragOver) {
@@ -316,14 +333,11 @@ fun ChatPane(
HorizontalDivider()
// File attachment row (only when NIP-17 and files attached)
if (isNip17 && attachedFiles.isNotEmpty()) {
if (attachedFiles.isNotEmpty()) {
MediaAttachmentRow(
attachedFiles = attachedFiles,
isUploading = isUploading,
onAttach = {
val files = DesktopFilePicker.pickMediaFiles()
attachedFiles.addAll(files)
},
onAttach = { attachFiles(DesktopFilePicker.pickMediaFiles()) },
onPaste = {},
onRemove = { attachedFiles.remove(it) },
)
@@ -339,10 +353,7 @@ fun ChatPane(
hasAttachments = attachedFiles.isNotEmpty(),
onMessageChange = { messageState.updateMessage(messageText.copy(text = it)) },
onToggleNip17 = { messageState.toggleNip17() },
onAttach = {
val files = DesktopFilePicker.pickMediaFiles()
attachedFiles.addAll(files)
},
onAttach = { attachFiles(DesktopFilePicker.pickMediaFiles()) },
onSend = {
scope.launch {
if (attachedFiles.isNotEmpty()) {
@@ -373,7 +384,13 @@ fun ChatPane(
}
},
)
}
} // end Column
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier.align(Alignment.BottomCenter).padding(bottom = 80.dp),
)
} // end Box
}
/**
@@ -674,8 +691,7 @@ private fun MessageInput(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
// Paperclip attach button (NIP-17 only)
if (isNip17) {
// Paperclip attach button — always visible, auto-switches to NIP-17 on send
IconButton(
onClick = onAttach,
enabled = !isUploading,
@@ -692,7 +708,6 @@ private fun MessageInput(
},
)
}
}
OutlinedTextField(
value = messageText,