mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 01:07:46 +00:00
feat: optional subject/title field in the ShortNotePostScreen composer
Add a subject line toggled from the composer's bottom row. On a kind-1 note it becomes a NIP-14 `subject` tag; on a NIP-29 kind-11 group thread it becomes the `title` (superseding the first-line-as-title heuristic — that stays as the fallback when the field is left empty). The field is auto-shown for group threads and labelled "Title" there, "Subject" otherwise. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B5MLY4hq5LXJ2D5WeLRyXj
This commit is contained in:
+40
@@ -45,6 +45,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Switch
|
||||
@@ -350,6 +351,26 @@ private fun NewPostScreenBody(
|
||||
)
|
||||
}
|
||||
|
||||
if (postViewModel.wantsSubject) {
|
||||
OutlinedTextField(
|
||||
value = postViewModel.subjectText,
|
||||
onValueChange = { postViewModel.subjectText = it },
|
||||
singleLine = true,
|
||||
label = {
|
||||
Text(
|
||||
stringRes(
|
||||
if (postViewModel.groupThreadTarget != null) {
|
||||
R.string.relay_group_thread_title_label
|
||||
} else {
|
||||
R.string.messages_new_message_subject
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = Size10dp),
|
||||
)
|
||||
}
|
||||
|
||||
// Only show text input if no voice message is being posted
|
||||
if (postViewModel.voiceMetadata == null && postViewModel.voiceRecording == null) {
|
||||
Row(
|
||||
@@ -788,6 +809,10 @@ private fun BottomRowActions(
|
||||
}
|
||||
}
|
||||
|
||||
AddSubjectButton(postViewModel.wantsSubject) {
|
||||
postViewModel.toggleSubject()
|
||||
}
|
||||
|
||||
MarkAsSensitiveButton(postViewModel.wantsToMarkAsSensitive) {
|
||||
postViewModel.toggleMarkAsSensitive()
|
||||
}
|
||||
@@ -857,6 +882,21 @@ private fun AddPrivateNoteButton(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddSubjectButton(
|
||||
isActive: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.AutoMirrored.Article,
|
||||
contentDescription = stringRes(R.string.messages_new_message_subject),
|
||||
modifier = Modifier.height(22.dp),
|
||||
tint = if (isActive) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddPollButton(
|
||||
isPollActive: Boolean,
|
||||
|
||||
+24
-4
@@ -116,6 +116,7 @@ import com.vitorpamplona.quartz.nip10Notes.content.findURLs
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.markedETags
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.notify
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.prepareETagsAsReplyTo
|
||||
import com.vitorpamplona.quartz.nip14Subject.subject
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.quotes
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.taggedQuoteIds
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
@@ -310,6 +311,11 @@ open class ShortNotePostViewModel :
|
||||
override var forwardZapTo = mutableStateOf<SplitBuilder<User>>(SplitBuilder())
|
||||
override val forwardZapToEditting = TextFieldState()
|
||||
|
||||
// Optional subject line: a NIP-14 `subject` tag on a kind-1 note, or the `title` of a NIP-29
|
||||
// kind-11 group thread. Toggled from the bottom row; auto-on for group threads.
|
||||
var wantsSubject by mutableStateOf(false)
|
||||
var subjectText by mutableStateOf("")
|
||||
|
||||
// NSFW, Sensitive
|
||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||
var contentWarningDescription by mutableStateOf("")
|
||||
@@ -504,6 +510,8 @@ open class ShortNotePostViewModel :
|
||||
) {
|
||||
val relay = relayUrl?.let { RelayUrlNormalizer.normalizeOrNull(it) }
|
||||
groupThreadTarget = if (groupId != null && relay != null) GroupThreadTarget(groupId, listOf(relay)) else null
|
||||
// A thread wants a title, so surface the field by default when composing one.
|
||||
if (groupThreadTarget != null) wantsSubject = true
|
||||
}
|
||||
|
||||
open fun load(
|
||||
@@ -1114,12 +1122,14 @@ open class ShortNotePostViewModel :
|
||||
|
||||
val threadTarget = groupThreadTarget
|
||||
return if (threadTarget != null) {
|
||||
// NIP-29 kind-11 group thread: title = first line, body = the rest (or the whole line
|
||||
// when single-line). Scoped to the group by `h`; the host relay authorizes the write.
|
||||
// NIP-29 kind-11 group thread. The title is the explicit subject field when filled;
|
||||
// otherwise it falls back to the first line (body = the rest). Scoped to the group by
|
||||
// `h`; the host relay authorizes the write.
|
||||
val subject = if (wantsSubject) subjectText.trim() else ""
|
||||
val text = tagger.message
|
||||
val firstBreak = text.indexOf('\n')
|
||||
val title = (if (firstBreak >= 0) text.substring(0, firstBreak) else text).trim()
|
||||
val body = (if (firstBreak >= 0) text.substring(firstBreak + 1) else text).trim()
|
||||
val title = subject.ifEmpty { (if (firstBreak >= 0) text.substring(0, firstBreak) else text).trim() }
|
||||
val body = if (subject.isNotEmpty()) text.trim() else (if (firstBreak >= 0) text.substring(firstBreak + 1) else text).trim()
|
||||
|
||||
ThreadEvent.build(body, title) {
|
||||
hTag(threadTarget.groupId)
|
||||
@@ -1261,6 +1271,8 @@ open class ShortNotePostViewModel :
|
||||
references(findURLs(tagger.message))
|
||||
quotes(findNostrUris(tagger.message))
|
||||
|
||||
if (wantsSubject && subjectText.isNotBlank()) subject(subjectText.trim())
|
||||
|
||||
geoHash?.let { geohash(it) }
|
||||
localZapRaiserAmount?.let { zapraiser(it) }
|
||||
zapReceiver?.let { zapSplits(it) }
|
||||
@@ -1434,6 +1446,8 @@ open class ShortNotePostViewModel :
|
||||
zapRaiserAmount.value = null
|
||||
|
||||
wantsForwardZapTo = false
|
||||
wantsSubject = false
|
||||
subjectText = ""
|
||||
wantsToMarkAsSensitive = false
|
||||
contentWarningDescription = ""
|
||||
wantsToAddGeoHash = false
|
||||
@@ -1821,6 +1835,12 @@ open class ShortNotePostViewModel :
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
fun toggleSubject() {
|
||||
wantsSubject = !wantsSubject
|
||||
if (!wantsSubject) subjectText = ""
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
fun toggleExpirationDate() {
|
||||
wantsExpirationDate = !wantsExpirationDate
|
||||
if (wantsExpirationDate) {
|
||||
|
||||
Reference in New Issue
Block a user