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