Refactors the code to avoid using callback lambdas

This commit is contained in:
Vitor Pamplona
2025-11-11 16:37:15 -05:00
parent d4b91845d5
commit 1cfe953a65
15 changed files with 81 additions and 133 deletions
@@ -110,7 +110,7 @@ fun EditPostView(
nav: INav, nav: INav,
) { ) {
val postViewModel: EditPostViewModel = viewModel() val postViewModel: EditPostViewModel = viewModel()
postViewModel.prepare(edit, versionLookingAt, accountViewModel) postViewModel.init(accountViewModel)
val context = LocalContext.current val context = LocalContext.current
@@ -118,7 +118,7 @@ fun EditPostView(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
postViewModel.load(edit, versionLookingAt, accountViewModel) postViewModel.load(edit, versionLookingAt)
} }
Dialog( Dialog(
@@ -61,13 +61,12 @@ import com.vitorpamplona.quartz.nip94FileMetadata.originalHash
import com.vitorpamplona.quartz.nip94FileMetadata.sensitiveContent import com.vitorpamplona.quartz.nip94FileMetadata.sensitiveContent
import com.vitorpamplona.quartz.nip94FileMetadata.size import com.vitorpamplona.quartz.nip94FileMetadata.size
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Stable @Stable
open class EditPostViewModel : ViewModel() { open class EditPostViewModel : ViewModel() {
var accountViewModel: AccountViewModel? = null lateinit var accountViewModel: AccountViewModel
var account: Account? = null lateinit var account: Account
var editedFromNote: Note? = null var editedFromNote: Note? = null
@@ -94,46 +93,32 @@ open class EditPostViewModel : ViewModel() {
var canAddInvoice by mutableStateOf(false) var canAddInvoice by mutableStateOf(false)
var wantsInvoice by mutableStateOf(false) var wantsInvoice by mutableStateOf(false)
open fun prepare( open fun init(accountViewModel: AccountViewModel) {
edit: Note,
versionLookingAt: Note?,
accountViewModel: AccountViewModel,
) {
this.accountViewModel = accountViewModel this.accountViewModel = accountViewModel
this.account = accountViewModel.account this.account = accountViewModel.account
this.editedFromNote = edit
this.userSuggestions?.reset()
this.userSuggestions = UserSuggestionState(accountViewModel.account)
} }
open fun load( open fun load(
edit: Note, edit: Note,
versionLookingAt: Note?, versionLookingAt: Note?,
accountViewModel: AccountViewModel,
) { ) {
this.accountViewModel = accountViewModel
this.account = accountViewModel.account
canAddInvoice = accountViewModel.userProfile().info?.lnAddress() != null canAddInvoice = accountViewModel.userProfile().info?.lnAddress() != null
multiOrchestrator = null multiOrchestrator = null
message = TextFieldValue(versionLookingAt?.event?.content ?: edit.event?.content ?: "") message = TextFieldValue(versionLookingAt?.event?.content ?: edit.event?.content ?: "")
urlPreview = findUrlInMessage() urlPreview = findUrlInMessage()
editedFromNote = edit this.editedFromNote = edit
this.userSuggestions?.reset()
this.userSuggestions = UserSuggestionState(accountViewModel.account)
} }
fun sendPost() { fun sendPost() {
viewModelScope.launch(Dispatchers.IO) { innerSendPost() } accountViewModel.launchSigner(::innerSendPost)
} }
suspend fun innerSendPost() { suspend fun innerSendPost() {
if (accountViewModel == null) {
cancel()
return
}
val extraNotesToBroadcast = mutableListOf<Event>() val extraNotesToBroadcast = mutableListOf<Event>()
nip95attachments.forEach { nip95attachments.forEach {
@@ -142,14 +127,14 @@ open class EditPostViewModel : ViewModel() {
} }
val notify = val notify =
if (editedFromNote?.author?.pubkeyHex == account?.userProfile()?.pubkeyHex) { if (editedFromNote?.author?.pubkeyHex == account.userProfile().pubkeyHex) {
null null
} else { } else {
// notifies if it is not the logged in user // notifies if it is not the logged in user
editedFromNote?.author?.pubkeyHex editedFromNote?.author?.pubkeyHex
} }
account?.sendEdit( account.sendEdit(
message = message.text, message = message.text,
originalNote = editedFromNote!!, originalNote = editedFromNote!!,
notify = notify, notify = notify,
@@ -191,7 +176,7 @@ open class EditPostViewModel : ViewModel() {
context: Context, context: Context,
) { ) {
viewModelScope.launch { viewModelScope.launch {
val myAccount = account ?: return@launch val myAccount = account
val myMultiOrchestrator = multiOrchestrator ?: return@launch val myMultiOrchestrator = multiOrchestrator ?: return@launch
isUploadingImage = true isUploadingImage = true
@@ -218,7 +203,7 @@ open class EditPostViewModel : ViewModel() {
contentWarningReason = if (sensitiveContent) "" else null, contentWarningReason = if (sensitiveContent) "" else null,
) )
nip95attachments = nip95attachments + nip95 nip95attachments = nip95attachments + nip95
val note = nip95.let { it1 -> account?.consumeNip95(it1.first, it1.second) } val note = nip95.let { it1 -> account.consumeNip95(it1.first, it1.second) }
note?.let { note?.let {
message = message.insertUrlAtCursor("nostr:" + it.toNEvent()) message = message.insertUrlAtCursor("nostr:" + it.toNEvent())
@@ -112,7 +112,7 @@ fun LoadOrCreateNote(
if (note == null) { if (note == null) {
LaunchedEffect(key1 = event.id) { LaunchedEffect(key1 = event.id) {
accountViewModel.checkGetOrCreateNote(event) { note = it } note = accountViewModel.noteFromEvent(event)
} }
} }
@@ -254,7 +254,7 @@ fun DisplayUser(
if (userBase == null) { if (userBase == null) {
LaunchedEffect(key1 = userHex) { LaunchedEffect(key1 = userHex) {
accountViewModel.checkGetOrCreateUser(userHex) { userBase = it } userBase = accountViewModel.checkGetOrCreateUser(userHex)
} }
} }
@@ -746,7 +746,7 @@ fun LoadNote(
if (note == null) { if (note == null) {
LaunchedEffect(key1 = baseNoteHex) { LaunchedEffect(key1 = baseNoteHex) {
accountViewModel.checkGetOrCreateNote(baseNoteHex) { note = it } note = accountViewModel.checkGetOrCreateNote(baseNoteHex)
} }
} }
@@ -243,9 +243,7 @@ private fun DisplayQuoteAuthor(
if (userBase == null && authorHex != null) { if (userBase == null && authorHex != null) {
LaunchedEffect(authorHex) { LaunchedEffect(authorHex) {
accountViewModel.checkGetOrCreateUser(authorHex) { newUserBase -> userBase = accountViewModel.checkGetOrCreateUser(authorHex)
userBase = newUserBase
}
} }
} }
@@ -977,53 +977,27 @@ class AccountViewModel(
override suspend fun getOrCreateUser(hex: HexKey): User = LocalCache.getOrCreateUser(hex) override suspend fun getOrCreateUser(hex: HexKey): User = LocalCache.getOrCreateUser(hex)
fun checkGetOrCreateUser(
key: HexKey,
onResult: (User?) -> Unit,
) {
viewModelScope.launch(Dispatchers.IO) { onResult(checkGetOrCreateUser(key)) }
}
fun getUserIfExists(hex: HexKey): User? = LocalCache.getUserIfExists(hex) fun getUserIfExists(hex: HexKey): User? = LocalCache.getUserIfExists(hex)
fun checkGetOrCreateNote(key: HexKey): Note? = LocalCache.checkGetOrCreateNote(key) fun checkGetOrCreateNote(key: HexKey): Note? = LocalCache.checkGetOrCreateNote(key)
override suspend fun getOrCreateNote(hex: HexKey): Note = LocalCache.getOrCreateNote(hex) override suspend fun getOrCreateNote(hex: HexKey): Note = LocalCache.getOrCreateNote(hex)
fun checkGetOrCreateNote( fun noteFromEvent(event: Event): Note? {
key: HexKey, var note = checkGetOrCreateNote(event.id)
onResult: (Note?) -> Unit,
) {
viewModelScope.launch(Dispatchers.IO) { onResult(checkGetOrCreateNote(key)) }
}
fun checkGetOrCreateNote( if (note == null) {
event: Event, LocalCache.justConsume(event, null, false)
onResult: (Note?) -> Unit, note = checkGetOrCreateNote(event.id)
) {
viewModelScope.launch(Dispatchers.IO) {
var note = checkGetOrCreateNote(event.id)
if (note == null) {
LocalCache.justConsume(event, null, false)
note = checkGetOrCreateNote(event.id)
}
onResult(note)
} }
return note
} }
fun getNoteIfExists(hex: HexKey): Note? = LocalCache.getNoteIfExists(hex) fun getNoteIfExists(hex: HexKey): Note? = LocalCache.getNoteIfExists(hex)
override suspend fun getOrCreateAddressableNote(address: Address): AddressableNote = LocalCache.getOrCreateAddressableNote(address) override suspend fun getOrCreateAddressableNote(address: Address): AddressableNote = LocalCache.getOrCreateAddressableNote(address)
fun getOrCreateAddressableNote(
key: Address,
onResult: (AddressableNote?) -> Unit,
) {
viewModelScope.launch(Dispatchers.IO) { onResult(getOrCreateAddressableNote(key)) }
}
fun getAddressableNoteIfExists(key: String): AddressableNote? = LocalCache.getAddressableNoteIfExists(key) fun getAddressableNoteIfExists(key: String): AddressableNote? = LocalCache.getAddressableNoteIfExists(key)
fun getAddressableNoteIfExists(key: Address): AddressableNote? = LocalCache.getAddressableNoteIfExists(key) fun getAddressableNoteIfExists(key: Address): AddressableNote? = LocalCache.getAddressableNoteIfExists(key)
@@ -1178,8 +1152,7 @@ class AccountViewModel(
context: Context, context: Context,
) { ) {
if (isWriteable()) { if (isWriteable()) {
val hint = note.toEventHint<VoiceEvent>() val hint = note.toEventHint<VoiceEvent>() ?: return
if (hint == null) return
launchSigner { launchSigner {
val uploader = UploadOrchestrator() val uploader = UploadOrchestrator()
@@ -72,19 +72,17 @@ fun ChatroomView(
if (replyToNote != null) { if (replyToNote != null) {
LaunchedEffect(key1 = replyToNote, newPostModel, accountViewModel) { LaunchedEffect(key1 = replyToNote, newPostModel, accountViewModel) {
accountViewModel.checkGetOrCreateNote(replyToNote) { val replyNote = accountViewModel.checkGetOrCreateNote(replyToNote)
if (it != null) { if (replyNote != null) {
newPostModel.reply(it) newPostModel.reply(replyNote)
}
} }
} }
} }
if (editFromDraft != null) { if (editFromDraft != null) {
LaunchedEffect(editFromDraft, newPostModel, accountViewModel) { LaunchedEffect(editFromDraft, newPostModel, accountViewModel) {
accountViewModel.checkGetOrCreateNote(editFromDraft) { val draftNote = accountViewModel.checkGetOrCreateNote(editFromDraft)
if (it != null) { if (draftNote != null) {
newPostModel.editFromDraft(it) newPostModel.editFromDraft(draftNote)
}
} }
} }
} }
@@ -325,9 +325,7 @@ class ChatNewMessageViewModel :
} }
if (replyId != null) { if (replyId != null) {
accountViewModel.checkGetOrCreateNote(replyId) { replyTo.value = accountViewModel.checkGetOrCreateNote(replyId)
replyTo.value = it
}
} }
} else if (draftEvent is PrivateDmEvent) { } else if (draftEvent is PrivateDmEvent) {
val recipientNPub = draftEvent.verifiedRecipientPubKey()?.let { Hex.decode(it).toNpub() } val recipientNPub = draftEvent.verifiedRecipientPubKey()?.let { Hex.decode(it).toNpub() }
@@ -335,9 +333,7 @@ class ChatNewMessageViewModel :
val replyId = draftEvent.replyTo() val replyId = draftEvent.replyTo()
if (replyId != null) { if (replyId != null) {
accountViewModel.checkGetOrCreateNote(replyId) { replyTo.value = accountViewModel.checkGetOrCreateNote(replyId)
replyTo.value = it
}
} }
} }
@@ -36,6 +36,7 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -91,7 +92,17 @@ fun ChannelMetadataScreen(
nav: INav, nav: INav,
) { ) {
val postViewModel: ChannelMetadataViewModel = viewModel() val postViewModel: ChannelMetadataViewModel = viewModel()
postViewModel.load(accountViewModel.account, channel) postViewModel.init(accountViewModel)
if (channel != null) {
LaunchedEffect(postViewModel) {
postViewModel.load(channel)
}
} else {
LaunchedEffect(postViewModel) {
postViewModel.new()
}
}
ChannelMetadataScaffold( ChannelMetadataScaffold(
postViewModel = postViewModel, postViewModel = postViewModel,
@@ -105,7 +116,7 @@ fun ChannelMetadataScreen(
private fun DialogContentPreview() { private fun DialogContentPreview() {
val accountViewModel = mockAccountViewModel() val accountViewModel = mockAccountViewModel()
val postViewModel: ChannelMetadataViewModel = viewModel() val postViewModel: ChannelMetadataViewModel = viewModel()
postViewModel.load(accountViewModel.account, null) postViewModel.init(accountViewModel)
ThemeComparisonColumn { ThemeComparisonColumn {
ChannelMetadataScaffold( ChannelMetadataScaffold(
@@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
@@ -57,7 +58,9 @@ import kotlin.coroutines.cancellation.CancellationException
@Stable @Stable
class ChannelMetadataViewModel : ViewModel() { class ChannelMetadataViewModel : ViewModel() {
private var account: Account? = null private lateinit var accountViewModel: AccountViewModel
private lateinit var account: Account
private var originalChannel: PublicChatChannel? = null private var originalChannel: PublicChatChannel? = null
val channelName = mutableStateOf(TextFieldValue()) val channelName = mutableStateOf(TextFieldValue())
@@ -73,24 +76,28 @@ class ChannelMetadataViewModel : ViewModel() {
channelName.value.text.isNotBlank() channelName.value.text.isNotBlank()
} }
fun load( fun init(accountViewModel: AccountViewModel) {
account: Account, this.accountViewModel = accountViewModel
channel: PublicChatChannel?, this.account = accountViewModel.account
) { }
this.account = account
if (channel != null) {
originalChannel = channel
channelName.value = TextFieldValue(channel.info.name ?: "")
channelPicture.value = TextFieldValue(channel.info.picture ?: "")
channelDescription.value = TextFieldValue(channel.info.about ?: "")
val relays = fun new() {
channel.info.relays originalChannel = null
?.map { relaySetupInfoBuilder(it) } clear()
?.distinctBy { it.relay } }
_channelRelays.update { relays ?: emptyList() } fun load(channel: PublicChatChannel) {
} originalChannel = channel
channelName.value = TextFieldValue(channel.info.name ?: "")
channelPicture.value = TextFieldValue(channel.info.picture ?: "")
channelDescription.value = TextFieldValue(channel.info.about ?: "")
val relays =
channel.info.relays
?.map { relaySetupInfoBuilder(it) }
?.distinctBy { it.relay }
_channelRelays.update { relays ?: emptyList() }
} }
fun isNewChannel() = originalChannel == null && _channelRelays.value.isNotEmpty() fun isNewChannel() = originalChannel == null && _channelRelays.value.isNotEmpty()
@@ -242,16 +242,12 @@ open class ChannelNewMessageViewModel :
if (draftEvent as? ChannelMessageEvent != null) { if (draftEvent as? ChannelMessageEvent != null) {
val replyId = draftEvent.reply()?.eventId val replyId = draftEvent.reply()?.eventId
if (replyId != null) { if (replyId != null) {
accountViewModel.checkGetOrCreateNote(replyId) { replyTo.value = accountViewModel.checkGetOrCreateNote(replyId)
replyTo.value = it
}
} }
} else if (draftEvent as? LiveActivitiesChatMessageEvent != null) { } else if (draftEvent as? LiveActivitiesChatMessageEvent != null) {
val replyId = draftEvent.reply()?.eventId val replyId = draftEvent.reply()?.eventId
if (replyId != null) { if (replyId != null) {
accountViewModel.checkGetOrCreateNote(replyId) { replyTo.value = accountViewModel.checkGetOrCreateNote(replyId)
replyTo.value = it
}
} }
} }
@@ -327,11 +327,7 @@ fun LoadUser(
if (user == null) { if (user == null) {
LaunchedEffect(key1 = baseUserHex) { LaunchedEffect(key1 = baseUserHex) {
accountViewModel.checkGetOrCreateUser(baseUserHex) { newUser -> user = accountViewModel.checkGetOrCreateUser(baseUserHex)
if (user != newUser) {
user = newUser
}
}
} }
} }
@@ -45,7 +45,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
@@ -81,11 +80,9 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.Size5dp
import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) @OptIn(ExperimentalMaterial3Api::class, FlowPreview::class)
@@ -161,16 +158,9 @@ fun NewProductScreen(
nav.popBack() nav.popBack()
}, },
onPost = { onPost = {
try { accountViewModel.launchSigner {
accountViewModel.viewModelScope.launch(Dispatchers.IO) { postViewModel.sendPostSync()
postViewModel.sendPostSync() nav.popBack()
nav.popBack()
}
} catch (e: SignerExceptions.ReadOnlyException) {
accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_sign_events,
)
} }
}, },
) )
@@ -104,15 +104,15 @@ open class NewProductViewModel :
IZapRaiser { IZapRaiser {
val draftTag = DraftTagState() val draftTag = DraftTagState()
var accountViewModel: AccountViewModel? = null lateinit var accountViewModel: AccountViewModel
var account: Account? = null lateinit var account: Account
init { init {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
draftTag.versions.collectLatest { draftTag.versions.collectLatest {
// don't save the first // don't save the first
if (it > 0) { if (it > 0) {
accountViewModel?.launchSigner { accountViewModel.launchSigner {
sendDraftSync() sendDraftSync()
} }
} }
@@ -189,8 +189,6 @@ open class NewProductViewModel :
} }
fun editFromDraft(draft: Note) { fun editFromDraft(draft: Note) {
val accountViewModel = accountViewModel ?: return
val noteEvent = draft.event val noteEvent = draft.event
val noteAuthor = draft.author val noteAuthor = draft.author
@@ -483,7 +483,7 @@ open class ShortNotePostViewModel :
cancel() cancel()
accountViewModel.account.signAndComputeBroadcast(template, extraNotesToBroadcast) accountViewModel.account.signAndComputeBroadcast(template, extraNotesToBroadcast)
accountViewModel.viewModelScope.launch(Dispatchers.IO) { accountViewModel.launchSigner {
accountViewModel.account.deleteDraftIgnoreErrors(version) accountViewModel.account.deleteDraftIgnoreErrors(version)
} }
} }