diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalNostrEvent.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalNostrEvent.kt index 537d4e87..34a68cdf 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalNostrEvent.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalNostrEvent.kt @@ -3,6 +3,22 @@ package ac.cord.auxiliary.compose.database.model.intermdiate import ac.cord.auxiliary.compose.database.model.NostrEvent import ac.cord.auxiliary.compose.database.model.Profile import ac.cord.auxiliary.compose.database.model.Repost +import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString +import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp import androidx.room.Embedded import androidx.room.Relation @@ -32,4 +48,57 @@ data class LocalNostrEvent( entityColumn = "publicKey" ) val taggedAuthor: Profile? = null, -) \ No newline at end of file +) { + @Composable + fun RenderNotePreview() { + + Column { + Row( + modifier = Modifier.fillMaxWidth().padding(12.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + val profile = profile + + ProfileAvatar( + profile = profile, + publicKey = nostrEvent.pubKey + ) + + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = profile?.displayName ?: profile?.userName ?: nostrEvent.pubKey, + style = MaterialTheme.typography.labelLarge + ) + + profile?.nip05?.let { + Text( + text = profile.nip05 ?: "", + style = MaterialTheme.typography.labelMedium + ) + } + } + } + Text( + modifier = Modifier.fillMaxWidth().padding( + horizontal = 20.dp + ), + style = MaterialTheme.typography.labelSmall, + textAlign = TextAlign.Start, + overflow = TextOverflow.Ellipsis, + text = nostrEvent.content + ) + Row( + modifier = Modifier.fillMaxWidth().padding(20.dp), + horizontalArrangement = Arrangement.Start + ) { + Text( + text = nostrEvent.createdAt.toFormattedTimeAndDateString(), + style = MaterialTheme.typography.labelSmall + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/WriteNewNoteScreen.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/WriteNewNoteScreen.kt index a61fbe58..af44298a 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/WriteNewNoteScreen.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/WriteNewNoteScreen.kt @@ -2,10 +2,12 @@ package ac.cord.auxiliary.compose.ui.composable import ac.cord.auxiliary.compose.database.model.NostrEvent import ac.cord.auxiliary.compose.database.model.Profile +import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowing import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString import ac.cord.auxiliary.compose.repository.NostrRepository import ac.cord.auxiliary.compose.ui.composable.widgets.LoadingDataIndicator +import ac.cord.auxiliary.compose.ui.composable.widgets.detail.TextNoteEventDetail import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar import ac.cord.auxiliary.compose.ui.theme.AuxTheme import ac.cord.auxiliary.compose.ui.view.model.WriteNewNoteViewModel @@ -16,14 +18,21 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Create +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.NavigateNext import androidx.compose.material3.Button +import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -31,14 +40,17 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import kotlin.time.Clock import kotlin.time.Instant @@ -47,11 +59,15 @@ import kotlin.time.Instant @Composable fun WriteNewNoteScreen( initialWriteNewNoteUIState: WriteNewNoteUIState = WriteNewNoteUIState.Loading, + replyToNostrEventId: HexKey?, + quotedNostrEventId: HexKey?, onNostrEventPublished: () -> Unit, nostrRepository: NostrRepository ) { val writeNewNoteViewModel: WriteNewNoteViewModel = viewModel ( factory = WriteNewNoteViewModel.factory( + replyToNostrEventId = replyToNostrEventId, + quotedNostrEventId = quotedNostrEventId, initialWriteNewNoteUIState, nostrRepository ) @@ -74,55 +90,99 @@ fun WriteNewNoteScreen( LoadingDataIndicator() } is WriteNewNoteUIState.InputPrompt -> { - Text( - "Write new note", - style = MaterialTheme.typography.headlineSmall - ) - TextField( - modifier = Modifier.fillMaxWidth(), - value = writeNewNoteViewModel.writeNewNoteFormState.textField.text.value, - onValueChange = { - writeNewNoteViewModel.writeNewNoteFormState.textField.text.value = it - }, - isError = writeNewNoteViewModel.writeNewNoteFormState.textField.errorMessage.value != null, - supportingText = writeNewNoteViewModel.writeNewNoteFormState.textField.errorMessage.value?.let { - { - Text( - text = it, - modifier = Modifier.padding( - bottom = 8.dp - ) + val listState = rememberLazyListState() + + LazyColumn( + state = listState, + modifier = Modifier.weight(1f) + ) { + // TODO: Stick Header with post button... + + item { + if (writeNewNoteUIState.inReplyToNostrEvent?.nostrEvent?.kind == TextNoteEvent.KIND) { + writeNewNoteUIState.inReplyToNostrEvent.RenderNotePreview() + } else { + Spacer( + modifier = Modifier.height(1.dp) ) } - }, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Text, - ), - label = { - Text( - text = "What vibrations do you want to send out?", - maxLines = 1, - ) - }, - placeholder = { - Text( - text = "Type out what you would like to publish", - maxLines = 1, - ) - }, - leadingIcon = { - ProfileAvatar( - profile = writeNewNoteUIState.localProfile.profile, - size = 35.dp - ) } - ) + + item { + Column( + modifier = Modifier.fillParentMaxHeight() + ) { + if (writeNewNoteUIState.inReplyToNostrEvent != null) { + Text( + modifier = Modifier.padding( + start = 50.dp, + bottom = 10.dp, + + ), + text = "replying To ${writeNewNoteUIState.inReplyToNostrEvent.profile?.humanReadableNameOrPubkey() ?: "note"}", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + TextField( + modifier = Modifier.fillMaxWidth(), + value = writeNewNoteViewModel.writeNewNoteFormState.textField.text.value, + onValueChange = { + writeNewNoteViewModel.writeNewNoteFormState.textField.text.value = it + }, + isError = writeNewNoteViewModel.writeNewNoteFormState.textField.errorMessage.value != null, + supportingText = writeNewNoteViewModel.writeNewNoteFormState.textField.errorMessage.value?.let { + { + Text( + text = it, + modifier = Modifier.padding( + bottom = 8.dp + ) + ) + } + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + ), + label = { + Text( + text = "What vibrations do you want to send out?", + maxLines = 1, + ) + }, + placeholder = { + Text( + text = "Type out what you would like to publish", + maxLines = 1, + ) + }, + leadingIcon = { + ProfileAvatar( + profile = writeNewNoteUIState.localProfile.profile, + size = 35.dp + ) + } + ) + } + + + } + } + + if (writeNewNoteUIState.inReplyToNostrEvent != null) { + LaunchedEffect(writeNewNoteUIState) { + listState.scrollToItem(1) + } + } + Button( onClick = { writeNewNoteViewModel.confirmNote( - localProfile = writeNewNoteUIState.localProfile + localProfile = writeNewNoteUIState.localProfile, + inReplyToNostrEvent = writeNewNoteUIState.inReplyToNostrEvent, + quotedNostrEvent = writeNewNoteUIState.quotedNostrEvent ) }, ) { @@ -138,43 +198,82 @@ fun WriteNewNoteScreen( } is WriteNewNoteUIState.ConfirmNote-> { - // TODO: Get profile for active pubKey + val listState = rememberLazyListState() - Text( - text = writeNewNoteUIState.localProfile.profile.humanReadableNameOrPubkey(), - style = MaterialTheme.typography.bodyLarge - ) + LazyColumn( + state = listState, + modifier = Modifier.weight(1f) + ) { + item { + if (writeNewNoteUIState.inReplyToNostrEvent?.nostrEvent?.kind == TextNoteEvent.KIND) { + + Card { + writeNewNoteUIState.inReplyToNostrEvent.RenderNotePreview() + } - writeNewNoteUIState.localProfile.profile.staticIdentifier()?.let { staticIdentifier -> - Text( - text = staticIdentifier, - style = MaterialTheme.typography.labelSmallEmphasized - ) + HorizontalDivider( + modifier = Modifier.padding(20.dp) + ) + } else { + Spacer( + modifier = Modifier.height(1.dp) + ) + } + } + + item { + Column( + modifier = Modifier.fillParentMaxHeight().fillMaxWidth().padding( + 10.dp + ), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + Text( + text = writeNewNoteUIState.localProfile.profile.humanReadableNameOrPubkey(), + style = MaterialTheme.typography.bodyLarge + ) + + writeNewNoteUIState.localProfile.profile.staticIdentifier()?.let { staticIdentifier -> + Text( + text = staticIdentifier, + style = MaterialTheme.typography.labelSmallEmphasized + ) + } + + ProfileAvatar( + profile = writeNewNoteUIState.localProfile.profile + ) + + if (writeNewNoteUIState.inReplyToNostrEvent != null) { + Text( + text = "replying To ${writeNewNoteUIState.inReplyToNostrEvent.profile?.humanReadableNameOrPubkey() ?: "note"}", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + Text( + text = writeNewNoteUIState.text, + style = MaterialTheme.typography.bodySmall, + textAlign = TextAlign.Center + ) + + Text( + text = Clock.System.now().toFormattedTimeAndDateString(), + style = MaterialTheme.typography.labelSmall + + ) + } + } } - - ProfileAvatar( - profile = writeNewNoteUIState.localProfile.profile - ) - - Text( - text = writeNewNoteUIState.text, - style = MaterialTheme.typography.bodySmall, - textAlign = TextAlign.Center - ) - - Text( - text = Clock.System.now().toFormattedTimeAndDateString(), - style = MaterialTheme.typography.labelSmall - ) - - - Spacer( - modifier = Modifier.weight(1f) - ) - - + if (writeNewNoteUIState.inReplyToNostrEvent != null) { + LaunchedEffect(writeNewNoteUIState) { + listState.scrollToItem(1) + } + } Text( text = "The above will be your new note.", @@ -231,9 +330,30 @@ private fun WriteNewNoteScreenPreview() { modifier = Modifier.fillMaxSize() ) { WriteNewNoteScreen( + replyToNostrEventId = null, + quotedNostrEventId = null, initialWriteNewNoteUIState = WriteNewNoteUIState.ConfirmNote( text = "Yes, I did it. So WHAT?!", + inReplyToNostrEvent = LocalNostrEvent( + nostrEvent = NostrEvent( + id = "eventId", + pubKey = "publicKey", + content = """Lorem Ipsum is simply dummy text of the printing and typesetting industry. + +Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. + +It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.""".trimIndent(), + tags = emptyArray(), + sig = "", + kind = TextNoteEvent.KIND + ), + profile = Profile( + publicKey = "pubKey", + displayName = "John Doe", + nostrEventId = "nostrEventId" + ) + ), localProfile = LocalProfileWithFollowing( nostrEvent = NostrEvent( id = "eventId", diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/AuxNavHost.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/AuxNavHost.kt index f8124218..63e8f30f 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/AuxNavHost.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/AuxNavHost.kt @@ -237,8 +237,11 @@ fun AuxNavHost( nostrRepository = databaseNostrRepository ) } - composable< WriteNewNoteRoute> { + composable { backStackEntry -> + val route = backStackEntry.toRoute() WriteNewNoteScreen( + replyToNostrEventId = route.inReplyToEventId, + quotedNostrEventId = route.quotedEventId, onNostrEventPublished = { applicationMainScope.launch { navController.popBackStack() diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/routes/WriteNewNoteRoute.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/routes/WriteNewNoteRoute.kt index e29a4cc4..605ba3d3 100755 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/routes/WriteNewNoteRoute.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/routes/WriteNewNoteRoute.kt @@ -5,5 +5,6 @@ import kotlinx.serialization.Serializable @Serializable data class WriteNewNoteRoute( val inReplyToEventId: String? = null, + val quotedEventId: String? = null, ): Route() { } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt index c9113328..f28a5d9c 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt @@ -4,6 +4,7 @@ import ac.cord.auxiliary.compose.database.model.NostrEvent import ac.cord.auxiliary.compose.database.model.Profile import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString +import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar import ac.cord.auxiliary.compose.ui.theme.AuxTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -50,7 +51,6 @@ fun TextNoteEventDetail( onNavigateBack: () -> Unit, onNavigateToWriteAReply: (String) -> Unit ) { - Scaffold( modifier = Modifier.fillMaxSize(), topBar = { @@ -91,10 +91,9 @@ fun TextNoteEventDetail( ) { val profile = localNostrEvent.profile - Icon( - Icons.Default.Person, - modifier = Modifier.size(50.dp), - contentDescription = profile?.displayName ?: profile?.userName ?: "Profile picture" + ProfileAvatar( + profile = localNostrEvent.profile, + publicKey = localNostrEvent.nostrEvent.pubKey ) Column( diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/WriteNewNoteViewModel.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/WriteNewNoteViewModel.kt index 2f073030..fd6bad79 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/WriteNewNoteViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/WriteNewNoteViewModel.kt @@ -1,5 +1,6 @@ package ac.cord.auxiliary.compose.ui.view.model +import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfile import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowing import ac.cord.auxiliary.compose.managers.SeedManager @@ -17,13 +18,18 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.lifecycle.viewModelScope import co.touchlab.kermit.Logger +import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.toHexKey import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch class WriteNewNoteViewModel( - val initialWriteNewNoteUIState: WriteNewNoteUIState, + val replyToNostrEventId: HexKey?, + val quotedNostrEventId: HexKey?, + + initialWriteNewNoteUIState: WriteNewNoteUIState, val writeNewNoteFormState: WriteNewNoteFormState = WriteNewNoteFormState(), val nostrRepository: NostrRepository ): ViewModel() { @@ -31,12 +37,16 @@ class WriteNewNoteViewModel( private const val TAG = "WriteNewNoteViewModel" fun factory( + replyToNostrEventId: HexKey?, + quotedNostrEventId: HexKey?, initialWriteNewNoteUIState: WriteNewNoteUIState, - nostrRepository: NostrRepository + nostrRepository: NostrRepository, ): ViewModelProvider.Factory = viewModelFactory { initializer { WriteNewNoteViewModel( - initialWriteNewNoteUIState, + replyToNostrEventId = replyToNostrEventId, + quotedNostrEventId = quotedNostrEventId, + initialWriteNewNoteUIState = initialWriteNewNoteUIState, nostrRepository = nostrRepository ) } @@ -57,6 +67,12 @@ class WriteNewNoteViewModel( fun observeActiveUserProfileWithFollowing() { logger.d("observeActiveUserProfileWithFollowing") viewModelScope.launch(Dispatchers.IO) { + val nostrEventBeingRepliedTo = replyToNostrEventId?.let { + nostrRepository.observeLocalNostrEventById( + it + ).firstOrNull() + } + val activePublicKey = SeedManager.activePublicKey().toHexKey() // TODO: Get note being replied too... @@ -67,7 +83,8 @@ class WriteNewNoteViewModel( ).collect { profileWithFollowing -> writeNewNoteUIState = if (profileWithFollowing != null) { WriteNewNoteUIState.InputPrompt( - localProfile = profileWithFollowing + localProfile = profileWithFollowing, + inReplyToNostrEvent = nostrEventBeingRepliedTo ) } else { WriteNewNoteUIState.Error @@ -76,12 +93,18 @@ class WriteNewNoteViewModel( } } - fun confirmNote(localProfile: LocalProfileWithFollowing) { + fun confirmNote( + localProfile: LocalProfileWithFollowing, + inReplyToNostrEvent: LocalNostrEvent?, + quotedNostrEvent: LocalNostrEvent? + ) { validateInput().let { isInputValid -> if (isInputValid) { writeNewNoteUIState = WriteNewNoteUIState.ConfirmNote( localProfile = localProfile, + inReplyToNostrEvent = inReplyToNostrEvent, + quotedNostrEvent = quotedNostrEvent, text = writeNewNoteFormState.textField.text.value, ) } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/state/WriteNewNoteUIState.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/state/WriteNewNoteUIState.kt index e7f50405..33d848cc 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/state/WriteNewNoteUIState.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/state/WriteNewNoteUIState.kt @@ -1,7 +1,9 @@ package ac.cord.auxiliary.compose.ui.view.state +import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfile import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowing +import com.vitorpamplona.quartz.nip01Core.core.HexKey abstract class WriteNewNoteUIState { data object Loading: WriteNewNoteUIState() @@ -9,11 +11,16 @@ abstract class WriteNewNoteUIState { data object Error: WriteNewNoteUIState() data class InputPrompt( - val localProfile: LocalProfileWithFollowing + val localProfile: LocalProfileWithFollowing, + val inReplyToNostrEvent: LocalNostrEvent? = null, + val quotedNostrEvent: LocalNostrEvent? = null, + val replayToId: HexKey? = null, ): WriteNewNoteUIState() data class ConfirmNote( val localProfile: LocalProfileWithFollowing, + val inReplyToNostrEvent: LocalNostrEvent? = null, + val quotedNostrEvent: LocalNostrEvent? = null, val text: String, ) : WriteNewNoteUIState()