diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 7b80b3d4c..649532377 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -171,13 +171,18 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.lang.Math.round -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) @Composable fun NewPostView( onClose: () -> Unit, @@ -201,6 +206,14 @@ fun NewPostView( var relayList = remember { accountViewModel.account.activeWriteRelays().toImmutableList() } LaunchedEffect(Unit) { + launch(Dispatchers.IO) { + postViewModel.draftTextChanges + .receiveAsFlow() + .debounce(1000) + .collectLatest { + postViewModel.sendPost(relayList = relayList, localDraft = postViewModel.draftTag) + } + } launch(Dispatchers.IO) { postViewModel.load(accountViewModel, baseReplyTo, quote, fork, version, draft) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index f14fa3be4..a177a0b9c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -69,6 +69,7 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.mapLatest @@ -83,7 +84,7 @@ enum class UserSuggestionAnchor { @Stable open class NewPostViewModel() : ViewModel() { - private var draftTag: String = UUID.randomUUID().toString() + var draftTag: String = UUID.randomUUID().toString() var accountViewModel: AccountViewModel? = null var account: Account? = null var requiresNIP24: Boolean = false @@ -166,6 +167,8 @@ open class NewPostViewModel() : ViewModel() { // NIP24 Wrapped DMs / Group messages var nip24 by mutableStateOf(false) + val draftTextChanges = Channel(Channel.CONFLATED) + fun lnAddress(): String? { return account?.userProfile()?.info?.lnAddress() } @@ -190,7 +193,7 @@ open class NewPostViewModel() : ViewModel() { this.account = accountViewModel.account if (draft != null) { - loadfromDraft(draft, accountViewModel) + loadFromDraft(draft, accountViewModel) } else { originalNote = replyingTo replyingTo?.let { replyNote -> @@ -305,7 +308,7 @@ open class NewPostViewModel() : ViewModel() { } } - private fun loadfromDraft( + private fun loadFromDraft( draft: Note, accountViewModel: AccountViewModel, ) { @@ -798,9 +801,8 @@ open class NewPostViewModel() : ViewModel() { pTags = pTags?.filter { it != userToRemove } } - open fun saveDraft() { - // TODO: find a way to send only the last modification so we dont get rate limited - sendPost(localDraft = draftTag) + open suspend fun saveDraft() { + draftTextChanges.send("") } open fun updateMessage(it: TextFieldValue) { @@ -1010,7 +1012,9 @@ open class NewPostViewModel() : ViewModel() { message = message.insertUrlAtCursor(imageUrl) urlPreview = findUrlInMessage() - saveDraft() + viewModelScope.launch(Dispatchers.IO) { + saveDraft() + } } }, onError = { @@ -1055,7 +1059,9 @@ open class NewPostViewModel() : ViewModel() { } urlPreview = findUrlInMessage() - saveDraft() + viewModelScope.launch(Dispatchers.IO) { + saveDraft() + } } }, onError = { @@ -1076,7 +1082,7 @@ open class NewPostViewModel() : ViewModel() { locUtil?.let { location = it.locationStateFlow.mapLatest { it.toGeoHash(GeohashPrecision.KM_5_X_5.digits).toString() } - saveDraft() + viewModelScope.launch(Dispatchers.IO) { saveDraft() } } viewModelScope.launch(Dispatchers.IO) { locUtil?.start() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt index 2e8bef95a..ce9bb577e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt @@ -40,11 +40,14 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.placeholderText +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch @Composable fun ZapRaiserRequest( @@ -97,7 +100,9 @@ fun ZapRaiserRequest( } else { newPostViewModel.zapRaiserAmount = it.toLongOrNull() } - newPostViewModel.saveDraft() + newPostViewModel.viewModelScope.launch(Dispatchers.IO) { + newPostViewModel.saveDraft() + } } }, placeholder = { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 5f764c63b..f90a8a85b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -165,6 +165,10 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.text.DateFormat @@ -189,6 +193,7 @@ fun ChannelScreen( } } +@OptIn(FlowPreview::class) @Composable fun PrepareChannelViewModels( baseChannel: Channel, @@ -206,6 +211,18 @@ fun PrepareChannelViewModels( ) val channelScreenModel: NewPostViewModel = viewModel() + + LaunchedEffect(Unit) { + launch(Dispatchers.IO) { + channelScreenModel.draftTextChanges + .receiveAsFlow() + .debounce(1000) + .collectLatest { + channelScreenModel.sendPost(localDraft = channelScreenModel.draftTag) + } + } + } + channelScreenModel.accountViewModel = accountViewModel channelScreenModel.account = accountViewModel.account channelScreenModel.originalNote = LocalCache.getNoteIfExists(baseChannel.idHex) @@ -680,7 +697,12 @@ fun ShowVideoStreaming( Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, - modifier = remember { Modifier.fillMaxWidth().heightIn(min = 50.dp, max = 300.dp) }, + modifier = + remember { + Modifier + .fillMaxWidth() + .heightIn(min = 50.dp, max = 300.dp) + }, ) { val zoomableUrlVideo = remember(streamingInfo) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt index 8367b2191..da63ece2d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt @@ -124,6 +124,10 @@ import com.vitorpamplona.quartz.events.findURLs import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -208,6 +212,7 @@ fun LoadRoomByAuthor( content(room) } +@OptIn(FlowPreview::class) @Composable fun PrepareChatroomViewModels( room: ChatroomKey, @@ -237,6 +242,15 @@ fun PrepareChatroomViewModels( } LaunchedEffect(key1 = newPostModel) { + launch(Dispatchers.IO) { + newPostModel.draftTextChanges + .receiveAsFlow() + .debounce(1000) + .collectLatest { + newPostModel.sendPost(localDraft = newPostModel.draftTag) + } + } + launch(Dispatchers.IO) { val hasNIP24 = accountViewModel.userProfile().privateChatrooms[room]?.roomMessages?.any {