Making NewPost Screen to be about Kind 1 and polls only

This commit is contained in:
Vitor Pamplona
2025-05-20 16:30:35 -04:00
parent 00048606fa
commit fcfa2bb05a
39 changed files with 449 additions and 724 deletions
@@ -94,8 +94,8 @@ import com.vitorpamplona.amethyst.ui.note.creators.uploads.ImageVideoDescription
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.ShowUserSuggestionList
import com.vitorpamplona.amethyst.ui.painterRes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.PostButton
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
@@ -43,6 +43,7 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.UserSuggestionState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
@@ -70,10 +70,10 @@ import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.SettingSwitchItem
import com.vitorpamplona.amethyst.ui.painterRes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.PostButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size5dp
@@ -41,13 +41,13 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.coroutines.CancellationException
@Composable
fun NewPollClosing(pollViewModel: NewPostViewModel) {
fun NewPollClosing(pollViewModel: ShortNotePostViewModel) {
var text by rememberSaveable { mutableStateOf("") }
pollViewModel.isValidClosedAt.value = true
@@ -106,5 +106,5 @@ fun NewPollClosing(pollViewModel: NewPostViewModel) {
@Preview
@Composable
fun NewPollClosingPreview() {
NewPollClosing(NewPostViewModel())
NewPollClosing(ShortNotePostViewModel())
}
@@ -41,13 +41,13 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.coroutines.CancellationException
@Composable
fun NewPollConsensusThreshold(pollViewModel: NewPostViewModel) {
fun NewPollConsensusThreshold(pollViewModel: ShortNotePostViewModel) {
var text by rememberSaveable { mutableStateOf("") }
pollViewModel.isValidConsensusThreshold.value = true
@@ -106,5 +106,5 @@ fun NewPollConsensusThreshold(pollViewModel: NewPostViewModel) {
@Preview
@Composable
fun NewPollConsensusThresholdPreview() {
NewPollConsensusThreshold(NewPostViewModel())
NewPollConsensusThreshold(ShortNotePostViewModel())
}
@@ -35,12 +35,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.placeholderText
@Composable
fun NewPollOption(
pollViewModel: NewPostViewModel,
pollViewModel: ShortNotePostViewModel,
optionIndex: Int,
) {
Row {
@@ -90,5 +91,5 @@ fun NewPollOption(
@Preview
@Composable
fun NewPollOptionPreview() {
NewPollOption(NewPostViewModel(), 0)
NewPollOption(ShortNotePostViewModel(), 0)
}
@@ -41,14 +41,14 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.placeholderText
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun NewPollPrimaryDescription(pollViewModel: NewPostViewModel) {
fun NewPollPrimaryDescription(pollViewModel: ShortNotePostViewModel) {
// initialize focus reference to be able to request focus programmatically
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
@@ -28,13 +28,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.placeholderText
@Composable
fun NewPollRecipientsField(
pollViewModel: NewPostViewModel,
pollViewModel: ShortNotePostViewModel,
account: Account,
) {
// if no recipients, add user's pubkey
@@ -39,12 +39,13 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.placeholderText
@Composable
fun NewPollVoteValueRange(pollViewModel: NewPostViewModel) {
fun NewPollVoteValueRange(pollViewModel: ShortNotePostViewModel) {
val colorInValid =
OutlinedTextFieldDefaults.colors(
focusedBorderColor = MaterialTheme.colorScheme.error,
@@ -122,6 +123,6 @@ fun NewPollVoteValueRangePreview() {
Column(
modifier = Modifier.fillMaxWidth(),
) {
NewPollVoteValueRange(NewPostViewModel())
NewPollVoteValueRange(ShortNotePostViewModel())
}
}
@@ -54,8 +54,8 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.MinHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
@@ -52,8 +52,8 @@ import com.vitorpamplona.amethyst.service.Nip11Retriever
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationDialog
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@@ -59,8 +59,8 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategoryWithButton
import com.vitorpamplona.amethyst.ui.stringRes
@@ -55,7 +55,6 @@ import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NewPostScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarks.BookmarkListScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomByAuthorScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomScreen
@@ -75,6 +74,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.GeoHashScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.HashtagPostScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.HashtagScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.HomeScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.privacy.PrivacyOptionsScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen
@@ -193,7 +193,7 @@ fun AppNavigation(
}
composableFromBottomArgs<Route.NewPost> {
NewPostScreen(
ShortNotePostScreen(
message = it.message,
attachment = it.attachment?.ifBlank { null }?.toUri(),
baseReplyTo = it.baseReplyTo?.let { hex -> accountViewModel.getNoteIfExists(hex) },
@@ -86,8 +86,8 @@ import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.navigation.routeFor
import com.vitorpamplona.amethyst.ui.note.types.RenderEmojiPack
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
@@ -88,10 +88,10 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.painterRes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.keyBackup.getFragmentActivity
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner
import com.vitorpamplona.amethyst.ui.stringRes
@@ -67,9 +67,9 @@ import com.vitorpamplona.amethyst.service.ZapPaymentHandler
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
@@ -32,13 +32,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.ui.actions.NewPollOption
import com.vitorpamplona.amethyst.ui.actions.NewPollVoteValueRange
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.painterRes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
import com.vitorpamplona.amethyst.ui.theme.Size18Modifier
import com.vitorpamplona.amethyst.ui.theme.placeholderText
@Composable
fun PollField(postViewModel: NewPostViewModel) {
fun PollField(postViewModel: ShortNotePostViewModel) {
val optionsList = postViewModel.pollOptions
Column(
modifier = Modifier.fillMaxWidth(),
@@ -65,8 +65,8 @@ import com.vitorpamplona.amethyst.ui.note.ZapIcon
import com.vitorpamplona.amethyst.ui.note.ZappedIcon
import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.PostButton
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.placeholderText
@@ -43,7 +43,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
import com.vitorpamplona.amethyst.ui.actions.UserSuggestionAnchor
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing
@@ -59,6 +58,7 @@ import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.SplitBuilder
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.toZapSplitSetup
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
@@ -84,9 +84,9 @@ import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapTo
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapToButton
import com.vitorpamplona.amethyst.ui.painterRes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.Notifying
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.Notifying
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.PostButton
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size35dp
@@ -49,8 +49,8 @@ import androidx.compose.ui.window.DialogProperties
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.PostButton
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
@@ -40,7 +40,6 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.nip30CustomEmojis.EmojiPackState
import com.vitorpamplona.amethyst.service.location.LocationState
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
import com.vitorpamplona.amethyst.ui.actions.UserSuggestionAnchor
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
import com.vitorpamplona.amethyst.ui.note.creators.draftTags.DraftTagState
@@ -58,6 +57,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.ChatFileUploader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.SuccessfulUploads
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohash
import com.vitorpamplona.quartz.nip01Core.tags.geohash.getGeoHash
@@ -115,9 +115,9 @@ import com.vitorpamplona.amethyst.ui.note.creators.zapraiser.ZapRaiserRequest
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapTo
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapToButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.SuccessfulUploads
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.PostButton
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Font14SP
@@ -53,8 +53,8 @@ import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.navigation.routeFor
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.JoinButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.JoinButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory
import com.vitorpamplona.amethyst.ui.stringRes
@@ -60,9 +60,9 @@ import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.navigation.routeFor
import com.vitorpamplona.amethyst.ui.note.LoadChannel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CreateButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CreateButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog
@@ -48,7 +48,6 @@ import com.vitorpamplona.amethyst.service.location.LocationState
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
import com.vitorpamplona.amethyst.ui.actions.UserSuggestionAnchor
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
import com.vitorpamplona.amethyst.ui.note.creators.draftTags.DraftTagState
@@ -59,6 +58,7 @@ import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.SplitBuilder
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
@@ -88,8 +88,8 @@ import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapTo
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapToButton
import com.vitorpamplona.amethyst.ui.painterRes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.PostButton
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size35dp
@@ -44,7 +44,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
import com.vitorpamplona.amethyst.ui.actions.UserSuggestionAnchor
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing
@@ -60,6 +59,7 @@ import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.SplitBuilder
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.toZapSplitSetup
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn
package com.vitorpamplona.amethyst.ui.screen.loggedIn.home
import android.content.Intent
import android.net.Uri
@@ -61,10 +61,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
@@ -76,13 +74,13 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.util.Consumer
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUser
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.actions.RelaySelectionDialog
import com.vitorpamplona.amethyst.ui.actions.RelaySelectionDialogEasy
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
@@ -117,6 +115,7 @@ import com.vitorpamplona.amethyst.ui.note.creators.zapraiser.ZapRaiserRequest
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapTo
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapToButton
import com.vitorpamplona.amethyst.ui.painterRes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
@@ -138,15 +137,11 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class)
@Composable
fun NewPostScreen(
fun ShortNotePostScreen(
message: String? = null,
attachment: Uri? = null,
baseReplyTo: Note? = null,
@@ -157,28 +152,12 @@ fun NewPostScreen(
accountViewModel: AccountViewModel,
nav: Nav,
) {
val postViewModel: NewPostViewModel = viewModel()
val postViewModel: ShortNotePostViewModel = viewModel()
postViewModel.init(accountViewModel)
val context = LocalContext.current
val activity = context.getActivity()
val scrollState = rememberScrollState()
val scope = rememberCoroutineScope()
var showRelaysDialog by remember { mutableStateOf(false) }
var relayList = remember { accountViewModel.account.activeWriteRelays().toImmutableList() }
LaunchedEffect(key1 = postViewModel.draftTag) {
launch(Dispatchers.IO) {
postViewModel.draftTextChanges
.receiveAsFlow()
.debounce(1000)
.collectLatest {
postViewModel.sendDraft(relayList = relayList)
}
}
}
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
postViewModel.load(baseReplyTo, quote, fork, version, draft)
@@ -194,7 +173,6 @@ fun NewPostScreen(
DisposableEffect(nav, activity) {
// Microsoft's swift key sends Gifs as new actions
val consumer =
Consumer<Intent> { intent ->
if (intent.action == Intent.ACTION_SEND) {
@@ -213,6 +191,18 @@ fun NewPostScreen(
onDispose { activity.removeOnNewIntentListener(consumer) }
}
NewPostScreenInner(postViewModel, accountViewModel, nav)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun NewPostScreenInner(
postViewModel: ShortNotePostViewModel,
accountViewModel: AccountViewModel,
nav: Nav,
) {
val scope = rememberCoroutineScope()
WatchAndLoadMyEmojiList(accountViewModel)
Scaffold(
@@ -222,14 +212,14 @@ fun NewPostScreen(
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
verticalAlignment = CenterVertically,
) {
Spacer(modifier = StdHorzSpacer)
Box {
IconButton(
modifier = Modifier.align(Alignment.Center),
onClick = { showRelaysDialog = true },
onClick = { postViewModel.showRelaysDialog = true },
) {
Icon(
painter = painterRes(R.drawable.relays),
@@ -241,8 +231,10 @@ fun NewPostScreen(
}
PostButton(
onPost = {
postViewModel.sendPost(relayList = relayList)
scope.launch {
// uses the accountViewModel scope to avoid cancelling this
// function when the postViewModel is released
accountViewModel.viewModelScope.launch(Dispatchers.IO) {
postViewModel.sendPostSync()
delay(100)
nav.popBack()
}
@@ -256,13 +248,12 @@ fun NewPostScreen(
Spacer(modifier = StdHorzSpacer)
CloseButton(
onPress = {
scope.launch {
withContext(Dispatchers.IO) {
postViewModel.sendDraftSync(relayList = relayList)
postViewModel.cancel()
}
delay(100)
// uses the accountViewModel scope to avoid cancelling this
// function when the postViewModel is released
accountViewModel.viewModelScope.launch(Dispatchers.IO) {
postViewModel.sendDraftSync()
nav.popBack()
postViewModel.cancel()
}
},
)
@@ -275,11 +266,11 @@ fun NewPostScreen(
)
},
) { pad ->
if (showRelaysDialog) {
RelaySelectionDialog(
preSelectedList = relayList,
onClose = { showRelaysDialog = false },
onPost = { relayList = it },
if (postViewModel.showRelaysDialog) {
RelaySelectionDialogEasy(
preSelectedList = postViewModel.relayList ?: persistentListOf(),
onClose = { postViewModel.showRelaysDialog = false },
onPost = { postViewModel.relayList = it.map { it.url }.toImmutableList() },
accountViewModel = accountViewModel,
nav = nav,
)
@@ -291,199 +282,210 @@ fun NewPostScreen(
.consumeWindowInsets(pad)
.imePadding(),
) {
Column(
modifier =
Modifier.fillMaxSize(),
) {
Row(
modifier =
Modifier
.fillMaxWidth()
.padding(
start = Size10dp,
end = Size10dp,
).weight(1f),
) {
Column(
modifier =
Modifier
.fillMaxWidth()
.verticalScroll(scrollState),
) {
postViewModel.originalNote?.let {
Row {
NoteCompose(
baseNote = it,
modifier = MaterialTheme.colorScheme.replyModifier,
isQuotedNote = true,
unPackReply = false,
makeItShort = true,
quotesLeft = 1,
accountViewModel = accountViewModel,
nav = nav,
)
Spacer(modifier = StdVertSpacer)
}
}
Row {
Notifying(postViewModel.pTags?.toImmutableList(), accountViewModel) {
postViewModel.removeFromReplyList(it)
}
}
Row(
modifier = Modifier.padding(vertical = Size10dp),
) {
BaseUserPicture(
accountViewModel.userProfile(),
Size35dp,
accountViewModel = accountViewModel,
)
MessageField(
R.string.what_s_on_your_mind,
postViewModel,
)
}
if (postViewModel.wantsPoll) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
PollField(postViewModel)
}
}
DisplayPreviews(postViewModel, accountViewModel, nav)
if (postViewModel.wantsToMarkAsSensitive) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
ContentSensitivityExplainer()
}
}
if (postViewModel.wantsToAddGeoHash) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
LocationAsHash(postViewModel) {
SettingsRow(
R.string.geohash_exclusive,
R.string.geohash_exclusive_explainer,
) {
Switch(postViewModel.wantsExclusiveGeoPost, onCheckedChange = { postViewModel.wantsExclusiveGeoPost = it })
}
}
}
}
if (postViewModel.wantsForwardZapTo) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = Size5dp, bottom = Size5dp, start = Size10dp),
) {
ForwardZapTo(postViewModel, accountViewModel)
}
}
postViewModel.multiOrchestrator?.let {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
ImageVideoDescription(
it,
accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, false, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server)
}
},
onDelete = postViewModel::deleteMediaToUpload,
onCancel = { postViewModel.multiOrchestrator = null },
accountViewModel = accountViewModel,
)
}
}
if (postViewModel.wantsInvoice) {
postViewModel.lnAddress()?.let { lud16 ->
InvoiceRequest(
lud16,
accountViewModel.account.userProfile().pubkeyHex,
accountViewModel,
stringRes(id = R.string.lightning_invoice),
stringRes(id = R.string.lightning_create_and_add_invoice),
onSuccess = {
postViewModel.insertAtCursor(it)
postViewModel.wantsInvoice = false
},
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
)
}
}
if (postViewModel.wantsSecretEmoji) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
Column(Modifier.fillMaxWidth()) {
SecretEmojiRequest {
postViewModel.insertAtCursor(it)
postViewModel.wantsSecretEmoji = false
}
}
}
}
if (postViewModel.wantsZapraiser && postViewModel.hasLnAddress()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
ZapRaiserRequest(
stringRes(id = R.string.zapraiser),
postViewModel,
)
}
}
}
}
postViewModel.userSuggestions?.let {
ShowUserSuggestionList(
it,
postViewModel::autocompleteWithUser,
accountViewModel,
modifier = Modifier.heightIn(0.dp, 300.dp),
)
}
postViewModel.emojiSuggestions?.let {
ShowEmojiSuggestionList(
it,
postViewModel::autocompleteWithEmoji,
postViewModel::autocompleteWithEmojiUrl,
accountViewModel,
modifier = Modifier.heightIn(0.dp, 300.dp),
)
}
BottomRowActions(postViewModel)
}
NewPostScreenBody(postViewModel, accountViewModel, nav)
}
}
}
@Composable
private fun BottomRowActions(postViewModel: NewPostViewModel) {
private fun NewPostScreenBody(
postViewModel: ShortNotePostViewModel,
accountViewModel: AccountViewModel,
nav: Nav,
) {
val scrollState = rememberScrollState()
Column(
modifier =
Modifier.fillMaxSize(),
) {
Row(
modifier =
Modifier
.fillMaxWidth()
.padding(
start = Size10dp,
end = Size10dp,
).weight(1f),
) {
Column(
modifier =
Modifier
.fillMaxWidth()
.verticalScroll(scrollState),
) {
postViewModel.originalNote?.let {
Row {
NoteCompose(
baseNote = it,
modifier = MaterialTheme.colorScheme.replyModifier,
isQuotedNote = true,
unPackReply = false,
makeItShort = true,
quotesLeft = 1,
accountViewModel = accountViewModel,
nav = nav,
)
Spacer(modifier = StdVertSpacer)
}
}
Row {
Notifying(postViewModel.pTags?.toImmutableList(), accountViewModel) {
postViewModel.removeFromReplyList(it)
}
}
Row(
modifier = Modifier.padding(vertical = Size10dp),
) {
BaseUserPicture(
accountViewModel.userProfile(),
Size35dp,
accountViewModel = accountViewModel,
)
MessageField(
R.string.what_s_on_your_mind,
postViewModel,
)
}
if (postViewModel.wantsPoll) {
Row(
verticalAlignment = CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
PollField(postViewModel)
}
}
DisplayPreviews(postViewModel, accountViewModel, nav)
if (postViewModel.wantsToMarkAsSensitive) {
Row(
verticalAlignment = CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
ContentSensitivityExplainer()
}
}
if (postViewModel.wantsToAddGeoHash) {
Row(
verticalAlignment = CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
LocationAsHash(postViewModel) {
SettingsRow(
R.string.geohash_exclusive,
R.string.geohash_exclusive_explainer,
) {
Switch(postViewModel.wantsExclusiveGeoPost, onCheckedChange = { postViewModel.wantsExclusiveGeoPost = it })
}
}
}
}
if (postViewModel.wantsForwardZapTo) {
Row(
verticalAlignment = CenterVertically,
modifier = Modifier.padding(top = Size5dp, bottom = Size5dp, start = Size10dp),
) {
ForwardZapTo(postViewModel, accountViewModel)
}
}
postViewModel.multiOrchestrator?.let {
Row(
verticalAlignment = CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
val context = LocalContext.current
ImageVideoDescription(
it,
accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server)
}
},
onDelete = postViewModel::deleteMediaToUpload,
onCancel = { postViewModel.multiOrchestrator = null },
accountViewModel = accountViewModel,
)
}
}
if (postViewModel.wantsInvoice) {
postViewModel.lnAddress()?.let { lud16 ->
InvoiceRequest(
lud16,
accountViewModel.account.userProfile().pubkeyHex,
accountViewModel,
stringRes(id = R.string.lightning_invoice),
stringRes(id = R.string.lightning_create_and_add_invoice),
onSuccess = {
postViewModel.insertAtCursor(it)
postViewModel.wantsInvoice = false
},
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
)
}
}
if (postViewModel.wantsSecretEmoji) {
Row(
verticalAlignment = CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
Column(Modifier.fillMaxWidth()) {
SecretEmojiRequest {
postViewModel.insertAtCursor(it)
postViewModel.wantsSecretEmoji = false
}
}
}
}
if (postViewModel.wantsZapraiser && postViewModel.hasLnAddress()) {
Row(
verticalAlignment = CenterVertically,
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
) {
ZapRaiserRequest(
stringRes(id = R.string.zapraiser),
postViewModel,
)
}
}
}
}
postViewModel.userSuggestions?.let {
ShowUserSuggestionList(
it,
postViewModel::autocompleteWithUser,
accountViewModel,
modifier = Modifier.heightIn(0.dp, 300.dp),
)
}
postViewModel.emojiSuggestions?.let {
ShowEmojiSuggestionList(
it,
postViewModel::autocompleteWithEmoji,
postViewModel::autocompleteWithEmojiUrl,
accountViewModel,
modifier = Modifier.heightIn(0.dp, 300.dp),
)
}
BottomRowActions(postViewModel)
}
}
@Composable
private fun BottomRowActions(postViewModel: ShortNotePostViewModel) {
val scrollState = rememberScrollState()
Row(
modifier =
@@ -548,7 +550,7 @@ private fun BottomRowActions(postViewModel: NewPostViewModel) {
@Composable
fun DisplayPreviews(
postViewModel: NewPostViewModel,
postViewModel: ShortNotePostViewModel,
accountViewModel: AccountViewModel,
nav: INav,
) {
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.actions
package com.vitorpamplona.amethyst.ui.screen.loggedIn.home
import android.content.Context
import android.util.Log
@@ -38,20 +38,19 @@ import com.vitorpamplona.amethyst.commons.compose.currentWord
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.compose.replaceCurrentWord
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.PublicChatChannel
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.nip30CustomEmojis.EmojiPackState
import com.vitorpamplona.amethyst.model.nip30CustomEmojis.EmojiPackState.EmojiMedia
import com.vitorpamplona.amethyst.service.location.LocationState
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing
import com.vitorpamplona.amethyst.ui.note.creators.draftTags.DraftTagState
import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.EmojiSuggestionState
import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber
import com.vitorpamplona.amethyst.ui.note.creators.messagefield.IMessageField
@@ -64,7 +63,6 @@ import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.toZapSplitSetup
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
@@ -76,9 +74,7 @@ import com.vitorpamplona.quartz.experimental.zapPolls.tags.PollOptionTag
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.events.eTags
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohash
import com.vitorpamplona.quartz.nip01Core.tags.geohash.getGeoHash
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
@@ -90,27 +86,16 @@ import com.vitorpamplona.quartz.nip10Notes.content.findHashtags
import com.vitorpamplona.quartz.nip10Notes.content.findNostrUris
import com.vitorpamplona.quartz.nip10Notes.content.findURLs
import com.vitorpamplona.quartz.nip10Notes.tags.notify
import com.vitorpamplona.quartz.nip10Notes.tags.positionalMarkedTags
import com.vitorpamplona.quartz.nip18Reposts.quotes.quotes
import com.vitorpamplona.quartz.nip18Reposts.quotes.taggedQuoteIds
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip22Comments.notify
import com.vitorpamplona.quartz.nip28PublicChat.base.notify
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag
import com.vitorpamplona.quartz.nip30CustomEmoji.emojis
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.reply.GitReplyEvent
import com.vitorpamplona.quartz.nip34Git.reply.notify
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarning
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitive
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.notify
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetupLnAddress
import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplitSetup
@@ -118,7 +103,6 @@ import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplits
import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiser
import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiserAmount
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
import com.vitorpamplona.quartz.nip92IMeta.IMetaTagBuilder
import com.vitorpamplona.quartz.nip92IMeta.imetas
import com.vitorpamplona.quartz.nip94FileMetadata.alt
@@ -131,12 +115,11 @@ import com.vitorpamplona.quartz.nip94FileMetadata.originalHash
import com.vitorpamplona.quartz.nip94FileMetadata.sensitiveContent
import com.vitorpamplona.quartz.nip94FileMetadata.size
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.UUID
enum class UserSuggestionAnchor {
MAIN_MESSAGE,
@@ -145,13 +128,21 @@ enum class UserSuggestionAnchor {
}
@Stable
open class NewPostViewModel :
open class ShortNotePostViewModel :
ViewModel(),
ILocationGrabber,
IMessageField,
IZapField,
IZapRaiser {
var draftTag: String by mutableStateOf(UUID.randomUUID().toString())
val draftTag = DraftTagState()
init {
viewModelScope.launch(Dispatchers.IO) {
draftTag.versions.collectLatest {
sendDraftSync()
}
}
}
var accountViewModel: AccountViewModel? = null
var account: Account? = null
@@ -220,7 +211,8 @@ open class NewPostViewModel :
var wantsZapraiser by mutableStateOf(false)
override val zapRaiserAmount = mutableStateOf<Long?>(null)
val draftTextChanges = Channel<String>(Channel.CONFLATED)
var showRelaysDialog by mutableStateOf(false)
var relayList by mutableStateOf<ImmutableList<String>?>(null)
fun lnAddress(): String? = account?.userProfile()?.info?.lnAddress()
@@ -258,9 +250,9 @@ open class NewPostViewModel :
if (innerNote != null) {
val oldTag = (draft.event as? AddressableEvent)?.dTag()
if (oldTag != null) {
draftTag = oldTag
draftTag.set(oldTag)
}
loadFromDraft(innerNote, accountViewModel)
loadFromDraft(innerNote)
}
}
}
@@ -372,15 +364,16 @@ open class NewPostViewModel :
urlPreviews.update(message)
}
private fun loadFromDraft(
draft: Note,
accountViewModel: AccountViewModel,
) {
Log.d("draft", draft.event!!.toJson())
private fun loadFromDraft(draft: Note) {
val draftEvent = draft.event ?: return
if (draftEvent !is TextNoteEvent) return
canAddInvoice = accountViewModel.userProfile().info?.lnAddress() != null
canAddZapRaiser = accountViewModel.userProfile().info?.lnAddress() != null
loadFromDraft(draftEvent)
}
private fun loadFromDraft(draftEvent: TextNoteEvent) {
canAddInvoice = accountViewModel?.userProfile()?.info?.lnAddress() != null
canAddZapRaiser = accountViewModel?.userProfile()?.info?.lnAddress() != null
multiOrchestrator = null
val localfowardZapTo = draftEvent.tags.filter { it.size > 1 && it[0] == "zap" }
@@ -471,38 +464,59 @@ open class NewPostViewModel :
urlPreviews.update(message)
}
fun sendPost(relayList: List<RelaySetupInfo>) {
viewModelScope.launch(Dispatchers.IO) {
innerSendPost(relayList, null)
accountViewModel?.deleteDraft(draftTag)
cancel()
suspend fun sendPostSync() {
val template = createTemplate() ?: return
val relayList = relayList
if (nip95attachments.isNotEmpty() && relayList != null) {
val usedImages = template.tags.taggedQuoteIds().toSet()
nip95attachments.forEach {
if (usedImages.contains(it.second.id) == true) {
account?.sendNip95Privately(it.first, it.second, relayList)
}
}
}
accountViewModel?.account?.signAndSendPrivatelyOrBroadcast(
template,
relayList = { relayList },
)
accountViewModel?.deleteDraft(draftTag.current)
cancel()
}
fun sendDraft(relayList: List<RelaySetupInfo>) {
viewModelScope.launch(Dispatchers.IO) {
sendDraftSync(relayList)
}
}
suspend fun sendDraftSync() {
val accountViewModel = accountViewModel ?: return
suspend fun sendDraftSync(relayList: List<RelaySetupInfo>) {
if (message.text.isBlank()) {
account?.deleteDraft(draftTag)
accountViewModel.account.deleteDraft(draftTag.current)
} else {
innerSendPost(relayList, draftTag)
val template = createTemplate() ?: return
accountViewModel.account.createAndSendDraft(draftTag.current, template)
nip95attachments.forEach {
account?.sendToPrivateOutboxAndLocal(it.first)
account?.sendToPrivateOutboxAndLocal(it.second)
}
}
}
private suspend fun innerSendPost(
relayList: List<RelaySetupInfo>,
localDraft: String?,
) = withContext(Dispatchers.IO) {
private suspend fun createTemplate(): EventTemplate<out Event>? {
if (accountViewModel == null) {
cancel()
return@withContext
return null
}
val tagger = NewMessageTagger(message.text, pTags, eTags, originalNote?.channelHex(), accountViewModel!!)
val tagger =
NewMessageTagger(
message.text,
pTags,
eTags,
originalNote?.channelHex(),
accountViewModel!!,
)
tagger.run()
val zapReceiver = if (wantsForwardZapTo) forwardZapTo.value.toZapSplitSetup() else null
@@ -510,367 +524,56 @@ open class NewPostViewModel :
val geoHash = (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString()
val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount.value else null
nip95attachments.forEach {
if (eTags?.contains(LocalCache.getNoteIfExists(it.second.id)) == true) {
account?.sendNip95(it.first, it.second, relayList)
}
}
val emojis = findEmoji(tagger.message, account?.emoji?.myEmojis?.value)
val urls = findURLs(tagger.message)
val usedAttachments = iMetaAttachments.filterIsIn(urls.toSet())
val replyingTo = originalNote
val contentWarningReason = if (wantsToMarkAsSensitive) "" else null
val channel = originalNote?.channelHex()?.let { LocalCache.getChannelIfExists(it) }
return if (wantsPoll) {
val options = pollOptions.map { PollOptionTag(it.key, it.value) }
if (replyingTo?.event is CommentEvent || replyingTo?.event is RootScope) {
val eventHint = replyingTo.toEventHint<Event>() ?: return@withContext
if (options.isEmpty()) return null
val template =
CommentEvent.replyBuilder(
msg = tagger.message,
replyingTo = eventHint,
) {
tagger.pTags?.let { notify(it.map { it.toPTag() }) }
val quotes = findNostrUris(tagger.message)
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
PollNoteEvent.build(tagger.message, options) {
valueMinimum?.let { minAmount(it) }
valueMaximum?.let { maxAmount(it) }
closedAt?.let { closedAt(it) }
consensusThreshold?.let { consensusThreshold(it / 100.0) }
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
pTags(tagger.directMentionsUsers.map { it.toPTag() })
quotes(quotes)
hashtags(findHashtags(tagger.message))
emojis(emojis)
imetas(usedAttachments)
}
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
account?.signAndSend(localDraft, template, relayList, setOf(replyingTo))
} else if (wantsExclusiveGeoPost && geoHash != null && originalNote == null) {
val template =
CommentEvent.replyExternalIdentity(
msg = tagger.message,
extId = GeohashId(geoHash),
) {
tagger.pTags?.let { notify(it.map { it.toPTag() }) }
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
account?.signAndSend(localDraft, template, relayList, emptyList())
} else if (channel != null) {
if (channel is PublicChatChannel) {
val replyingToEvent = originalNote?.toEventHint<ChannelMessageEvent>()
val channelEvent = channel.event
val channelRelays = channel.relays()
val template =
if (replyingToEvent != null) {
ChannelMessageEvent.reply(tagger.message, replyingToEvent) {
tagger.pTags?.let { notify(it.map { it.toPTag() }) }
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
} else if (channelEvent != null) {
val hint = EventHintBundle(channelEvent, channelRelays.firstOrNull())
ChannelMessageEvent.message(tagger.message, hint) {
tagger.pTags?.let { notify(it.map { it.toPTag() }) }
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
} else {
ChannelMessageEvent.message(tagger.message, ETag(channel.idHex, channelRelays.firstOrNull())) {
tagger.pTags?.let { notify(it.map { it.toPTag() }) }
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
}
val broadcast = tagger.directMentionsNotes + (tagger.eTags ?: emptyList())
account?.signAndSendWithList(draftTag, template, channelRelays, broadcast)
} else if (channel is LiveActivitiesChannel) {
val replyingToEvent = originalNote?.toEventHint<LiveActivitiesChatMessageEvent>()
val activity = channel.info
val channelRelays = channel.relays()
val template =
if (replyingToEvent != null) {
LiveActivitiesChatMessageEvent.reply(tagger.message, replyingToEvent) {
tagger.pTags?.let { notify(it.map { it.toPTag() }) }
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
} else if (activity != null) {
val hint = EventHintBundle(activity, channelRelays.firstOrNull() ?: replyingToEvent?.relay)
LiveActivitiesChatMessageEvent.message(tagger.message, hint) {
tagger.pTags?.let { notify(it.map { it.toPTag() }) }
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
} else {
LiveActivitiesChatMessageEvent.message(tagger.message, channel.toATag()) {
tagger.pTags?.let { notify(it.map { it.toPTag() }) }
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
}
val broadcast = tagger.directMentionsNotes + (tagger.eTags ?: emptyList())
account?.signAndSendWithList(draftTag, template, channelRelays, broadcast)
emojis(emojis)
imetas(usedAttachments)
}
} else if (originalNote?.event is GitIssueEvent) {
val originalNoteHint = originalNote?.toEventHint<GitIssueEvent>() ?: return@withContext
val template =
GitReplyEvent.replyIssue(
tagger.message,
originalNoteHint,
) {
tagger.pTags?.let { notify(it.map { it.toPTag() }) }
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
val broadcast = tagger.directMentionsNotes + (tagger.eTags ?: emptyList())
account?.signAndSend(localDraft, template, relayList, broadcast)
} else if (originalNote?.event is TorrentCommentEvent) {
val replyToEvent = originalNote?.event as TorrentCommentEvent
val rootETag = replyToEvent.torrent()
val rootNote = rootETag?.eventId?.let { LocalCache.getNoteIfExists(it) }
val rootNoteEvent = rootNote?.event
// only uses the root node if the event is loaded.
val root =
if (rootNoteEvent != null) {
rootNote // refreshes author and relay hint to what we have.
} else {
rootETag?.let { LocalCache.getOrCreateNote(it) } // keeps what came in.
?: originalNote?.replyTo?.firstOrNull { it.event != null && it.replyTo?.isEmpty() == true } // if it has loaded events with zero replies in the reply list
?: originalNote?.replyTo?.firstOrNull() // old rules, first item is root.
?: originalNote
}
if (root != null) {
val replyToSet =
if (forkedFromNote != null) {
(listOfNotNull(forkedFromNote) + (tagger.eTags ?: emptyList())).ifEmpty { null }
} else {
tagger.eTags
}
val sortedAndMarked =
eTags?.map { it.toETag() }?.positionalMarkedTags(
root = root.toETag(),
replyingTo = replyingTo?.toETag(),
forkedFrom = forkedFromNote?.toETag(),
)
val template =
TorrentCommentEvent.build(tagger.message) {
sortedAndMarked?.let { eTags(sortedAndMarked) }
pTags(tagger.directMentionsUsers.map { it.toPTag() })
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
val broadcast = tagger.directMentionsNotes + (replyToSet ?: emptySet())
account?.sendTorrentComment(localDraft, template, broadcast, relayList)
}
} else if (originalNote?.event is TorrentEvent) {
val replyToSet =
if (forkedFromNote != null) {
(listOfNotNull(forkedFromNote) + (tagger.eTags ?: emptyList())).ifEmpty { null }
} else {
tagger.eTags
}
val sortedAndMarked =
eTags?.map { it.toETag() }?.positionalMarkedTags(
root = originalNote?.toETag(),
replyingTo = null,
forkedFrom = forkedFromNote?.toETag(),
)
val template =
TorrentCommentEvent.build(tagger.message) {
sortedAndMarked?.let { eTags(sortedAndMarked) }
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
val broadcast = tagger.directMentionsNotes + (replyToSet ?: emptySet())
account?.sendTorrentComment(localDraft, template, broadcast, relayList)
} else {
if (wantsPoll) {
val options = pollOptions.map { PollOptionTag(it.key, it.value) }
TextNoteEvent.build(
note = tagger.message,
replyingTo = originalNote?.toEventHint<TextNoteEvent>(),
forkingFrom = forkedFromNote?.toEventHint<TextNoteEvent>(),
) {
tagger.pTags?.let { notify(it.map { it.toPTag() }) }
if (options.isEmpty()) return@withContext
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
val quotes = findNostrUris(tagger.message)
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
val template =
PollNoteEvent.build(tagger.message, options) {
valueMinimum?.let { minAmount(it) }
valueMaximum?.let { maxAmount(it) }
closedAt?.let { closedAt(it) }
consensusThreshold?.let { consensusThreshold(it / 100.0) }
pTags(tagger.directMentionsUsers.map { it.toPTag() })
quotes(quotes)
hashtags(findHashtags(tagger.message))
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
account?.signAndSend(localDraft, template, relayList, quotes)
} else {
val replyToSet =
if (forkedFromNote != null) {
(listOfNotNull(forkedFromNote) + (tagger.eTags ?: emptyList())).ifEmpty { null }
} else {
tagger.eTags
}
val template =
TextNoteEvent.build(
note = tagger.message,
replyingTo = originalNote?.toEventHint<TextNoteEvent>(),
forkingFrom = forkedFromNote?.toEventHint<TextNoteEvent>(),
) {
tagger.pTags?.let { notify(it.map { it.toPTag() }) }
hashtags(findHashtags(tagger.message))
references(findURLs(tagger.message))
quotes(findNostrUris(tagger.message))
geoHash?.let { geohash(it) }
localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) }
emojis(emojis)
imetas(usedAttachments)
}
val broadcast = tagger.directMentionsNotes + (replyToSet ?: emptySet())
account?.signAndSend(localDraft, template, relayList, broadcast)
emojis(emojis)
imetas(usedAttachments)
}
}
}
@@ -889,7 +592,6 @@ open class NewPostViewModel :
alt: String?,
contentWarningReason: String?,
mediaQuality: Int,
isPrivate: Boolean = false,
server: ServerName,
onError: (title: String, message: String) -> Unit,
context: Context,
@@ -1000,13 +702,27 @@ open class NewPostViewModel :
emojiSuggestions?.reset()
draftTag = UUID.randomUUID().toString()
showRelaysDialog = false
reloadRelaySet()
draftTag.rotate()
}
fun deleteDraft() {
viewModelScope.launch(Dispatchers.IO) {
accountViewModel?.deleteDraft(draftTag)
}
fun reloadRelaySet() {
val account = accountViewModel?.account ?: return
val nip65 = account.normalizedNIP65WriteRelayList.value
val private = account.normalizedPrivateOutBoxRelaySet.value
val local = account.settings.localRelayServers
relayList =
if (nip65.isEmpty()) {
account.activeWriteRelays().map { it.url }.toImmutableList()
} else {
val combined: Set<String> = (nip65 + private + local)
combined.toImmutableList()
}
}
fun deleteMediaToUpload(selected: SelectedMediaProcessing) {
@@ -1017,10 +733,6 @@ open class NewPostViewModel :
pTags = pTags?.filter { it != userToRemove }
}
private fun saveDraft() {
draftTextChanges.trySend("")
}
open fun addToMessage(it: String) {
updateMessage(TextFieldValue(message.text + " " + it))
}
@@ -1038,7 +750,7 @@ open class NewPostViewModel :
emojiSuggestions?.processCurrentWord(lastWord)
}
saveDraft()
draftTag.newVersion()
}
override fun updateZapForwardTo(newZapForwardTo: TextFieldValue) {
@@ -1065,10 +777,10 @@ open class NewPostViewModel :
userSuggestions.reset()
}
saveDraft()
draftTag.newVersion()
}
open fun autocompleteWithEmoji(item: EmojiPackState.EmojiMedia) {
open fun autocompleteWithEmoji(item: EmojiMedia) {
val wordToInsert = ":${item.code}:"
message = message.replaceCurrentWord(wordToInsert)
@@ -1076,10 +788,10 @@ open class NewPostViewModel :
emojiSuggestions?.reset()
saveDraft()
draftTag.newVersion()
}
open fun autocompleteWithEmojiUrl(item: EmojiPackState.EmojiMedia) {
open fun autocompleteWithEmojiUrl(item: EmojiMedia) {
val wordToInsert = item.link.url + " "
viewModelScope.launch(Dispatchers.IO) {
@@ -1094,7 +806,7 @@ open class NewPostViewModel :
emojiSuggestions?.reset()
saveDraft()
draftTag.newVersion()
}
private fun newStateMapPollOptions(): SnapshotStateMap<Int, String> = mutableStateMapOf(Pair(0, ""), Pair(1, ""))
@@ -1138,13 +850,13 @@ open class NewPostViewModel :
fun updateMinZapAmountForPoll(textMin: String) {
valueMinimum = textMin.toLongOrNull()?.takeIf { it > 0 }
checkMinMax()
saveDraft()
draftTag.newVersion()
}
fun updateMaxZapAmountForPoll(textMax: String) {
valueMaximum = textMax.toLongOrNull()?.takeIf { it > 0 }
checkMinMax()
saveDraft()
draftTag.newVersion()
}
fun checkMinMax() {
@@ -1166,7 +878,8 @@ open class NewPostViewModel :
override fun updateZapFromText() {
viewModelScope.launch(Dispatchers.Default) {
val tagger = NewMessageTagger(message.text, emptyList(), emptyList(), null, accountViewModel!!)
val tagger =
NewMessageTagger(message.text, emptyList(), emptyList(), null, accountViewModel!!)
tagger.run()
tagger.pTags?.forEach { taggedUser ->
if (!forwardZapTo.value.items.any { it.key == taggedUser }) {
@@ -1178,12 +891,12 @@ open class NewPostViewModel :
override fun updateZapRaiserAmount(newAmount: Long?) {
zapRaiserAmount.value = newAmount
saveDraft()
draftTag.newVersion()
}
fun removePollOption(optionIndex: Int) {
pollOptions.removeOrdered(optionIndex)
saveDraft()
draftTag.newVersion()
}
private fun MutableMap<Int, String>.removeOrdered(index: Int) {
@@ -1207,12 +920,12 @@ open class NewPostViewModel :
text: String,
) {
pollOptions[optionIndex] = text
saveDraft()
draftTag.newVersion()
}
fun toggleMarkAsSensitive() {
wantsToMarkAsSensitive = !wantsToMarkAsSensitive
saveDraft()
draftTag.newVersion()
}
override fun locationManager(): LocationState = Amethyst.instance.locationManager
@@ -44,8 +44,8 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.tor.PrivacySettingsBody
@@ -52,8 +52,8 @@ import com.vitorpamplona.amethyst.model.DefaultDMRelayList
import com.vitorpamplona.amethyst.model.DefaultSearchRelayList
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.dm.DMRelayListViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.dm.renderDMItems
@@ -47,8 +47,8 @@ import com.vitorpamplona.amethyst.model.DefaultDMRelayList
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
@@ -47,8 +47,8 @@ import com.vitorpamplona.amethyst.model.DefaultSearchRelayList
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search.SearchRelayList
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search.SearchRelayListViewModel
@@ -37,7 +37,7 @@ import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountContent
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.stringRes
@Composable
@@ -48,9 +48,9 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.SaveButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size10dp
@@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.nip30CustomEmojis.EmojiPackState.EmojiMedia
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
@@ -51,13 +52,13 @@ class NewPostViewModelTest {
@MockK(relaxed = true)
lateinit var replyingTo: Note
private lateinit var newPostViewModelUnderTest: NewPostViewModel
private lateinit var shortNoteViewModelUnderTest: ShortNotePostViewModel
@Before
fun setup() {
mockkObject(LocalCache)
every { LocalCache.getOrCreateUser(any<HexKey>()) } returns mockk<User>()
newPostViewModelUnderTest = NewPostViewModel()
shortNoteViewModelUnderTest = ShortNotePostViewModel()
MockKAnnotations.init(this)
}
@@ -81,8 +82,8 @@ class NewPostViewModelTest {
every { accountViewModel.account.emoji.myEmojis } returns mockk<StateFlow<List<EmojiMedia>>>(relaxed = true)
// Act: Call load with mentions
newPostViewModelUnderTest.init(accountViewModel)
newPostViewModelUnderTest.load(replyingTo, quote = null, fork = null, version = null, draft = null)
shortNoteViewModelUnderTest.init(accountViewModel)
shortNoteViewModelUnderTest.load(replyingTo, quote = null, fork = null, version = null, draft = null)
// Assert
// Two mentions should call LocalCache.getOrCreateUser twice
@@ -102,7 +103,7 @@ class NewPostViewModelTest {
every { accountViewModel.userProfile() } returns mockk<User>(relaxed = true)
// Act: Call load with empty mentions
newPostViewModelUnderTest.load(replyingTo, quote = null, fork = null, version = null, draft = null)
shortNoteViewModelUnderTest.load(replyingTo, quote = null, fork = null, version = null, draft = null)
// Assert
// With no mentions LocalCache.getOrCreateUser should not be called
@@ -122,7 +123,7 @@ class NewPostViewModelTest {
every { accountViewModel.userProfile() } returns mockk<User>(relaxed = true)
// Act: Call load with empty mentions
newPostViewModelUnderTest.load(replyingTo, quote = null, fork = null, version = null, draft = null)
shortNoteViewModelUnderTest.load(replyingTo, quote = null, fork = null, version = null, draft = null)
// Assert
// Verify LocalCache.getOrCreateUser(it) is not called with empty hex, it will crash the app