Merge pull request #511 from greenart7c3/main

add option to send notes to selected relays
This commit is contained in:
Vitor Pamplona
2023-07-21 09:06:44 -04:00
committed by GitHub
8 changed files with 330 additions and 30 deletions

View File

@@ -628,19 +628,19 @@ class Account(
return Pair(data, signedEvent) return Pair(data, signedEvent)
} }
fun sendNip95(data: FileStorageEvent, signedEvent: FileStorageHeaderEvent): Note? { fun sendNip95(data: FileStorageEvent, signedEvent: FileStorageHeaderEvent, relayList: List<Relay>? = null): Note? {
if (!isWriteable()) return null if (!isWriteable()) return null
Client.send(data) Client.send(data, relayList = relayList)
LocalCache.consume(data, null) LocalCache.consume(data, null)
Client.send(signedEvent) Client.send(signedEvent, relayList = relayList)
LocalCache.consume(signedEvent, null) LocalCache.consume(signedEvent, null)
return LocalCache.notes[signedEvent.id] return LocalCache.notes[signedEvent.id]
} }
fun sendHeader(headerInfo: FileHeader): Note? { fun sendHeader(headerInfo: FileHeader, relayList: List<Relay>? = null): Note? {
if (!isWriteable()) return null if (!isWriteable()) return null
val signedEvent = FileHeaderEvent.create( val signedEvent = FileHeaderEvent.create(
@@ -655,7 +655,7 @@ class Account(
privateKey = loggedIn.privKey!! privateKey = loggedIn.privKey!!
) )
Client.send(signedEvent) Client.send(signedEvent, relayList = relayList)
LocalCache.consume(signedEvent, null) LocalCache.consume(signedEvent, null)
return LocalCache.notes[signedEvent.id] return LocalCache.notes[signedEvent.id]
@@ -671,7 +671,8 @@ class Account(
zapRaiserAmount: Long? = null, zapRaiserAmount: Long? = null,
replyingTo: String?, replyingTo: String?,
root: String?, root: String?,
directMentions: Set<HexKey> directMentions: Set<HexKey>,
relayList: List<Relay>? = null
) { ) {
if (!isWriteable()) return if (!isWriteable()) return
@@ -694,7 +695,7 @@ class Account(
privateKey = loggedIn.privKey!! privateKey = loggedIn.privKey!!
) )
Client.send(signedEvent) Client.send(signedEvent, relayList = relayList)
LocalCache.consume(signedEvent) LocalCache.consume(signedEvent)
} }
@@ -709,7 +710,8 @@ class Account(
closedAt: Int?, closedAt: Int?,
zapReceiver: String? = null, zapReceiver: String? = null,
wantsToMarkAsSensitive: Boolean, wantsToMarkAsSensitive: Boolean,
zapRaiserAmount: Long? = null zapRaiserAmount: Long? = null,
relayList: List<Relay>? = null
) { ) {
if (!isWriteable()) return if (!isWriteable()) return
@@ -733,7 +735,7 @@ class Account(
zapRaiserAmount = zapRaiserAmount zapRaiserAmount = zapRaiserAmount
) )
// println("Sending new PollNoteEvent: %s".format(signedEvent.toJson())) // println("Sending new PollNoteEvent: %s".format(signedEvent.toJson()))
Client.send(signedEvent) Client.send(signedEvent, relayList = relayList)
LocalCache.consume(signedEvent) LocalCache.consume(signedEvent)
} }

View File

@@ -73,10 +73,18 @@ object Client : RelayPool.Listener {
RelayPool.sendFilterOnlyIfDisconnected() RelayPool.sendFilterOnlyIfDisconnected()
} }
fun send(signedEvent: EventInterface, relay: String? = null, feedTypes: Set<FeedType>? = null, onDone: (() -> Unit)? = null) { fun send(
signedEvent: EventInterface,
relay: String? = null,
feedTypes: Set<FeedType>? = null,
relayList: List<Relay>? = null,
onDone: (() -> Unit)? = null
) {
checkNotInMainThread() checkNotInMainThread()
if (relay == null) { if (relayList != null) {
RelayPool.sendToSelectedRelays(relayList, signedEvent)
} else if (relay == null) {
RelayPool.send(signedEvent) RelayPool.send(signedEvent)
} else { } else {
val useConnectedRelayIfPresent = RelayPool.getRelays(relay) val useConnectedRelayIfPresent = RelayPool.getRelays(relay)

View File

@@ -62,6 +62,12 @@ object RelayPool : Relay.Listener {
relays.forEach { it.sendFilterOnlyIfDisconnected() } relays.forEach { it.sendFilterOnlyIfDisconnected() }
} }
fun sendToSelectedRelays(list: List<Relay>, signedEvent: EventInterface) {
list.forEach { relay ->
relays.filter { it.url == relay.url }.forEach { it.send(signedEvent) }
}
}
fun send(signedEvent: EventInterface) { fun send(signedEvent: EventInterface) {
relays.forEach { it.send(signedEvent) } relays.forEach { it.send(signedEvent) }
} }

View File

@@ -11,6 +11,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.* import com.vitorpamplona.amethyst.model.*
import com.vitorpamplona.amethyst.service.FileHeader import com.vitorpamplona.amethyst.service.FileHeader
import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.components.MediaCompressor import com.vitorpamplona.amethyst.ui.components.MediaCompressor
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@@ -56,7 +57,7 @@ open class NewMediaModel : ViewModel() {
} }
} }
fun upload(context: Context) { fun upload(context: Context, relayList: List<Relay>? = null) {
isUploadingImage = true isUploadingImage = true
val contentResolver = context.contentResolver val contentResolver = context.contentResolver
@@ -78,7 +79,7 @@ open class NewMediaModel : ViewModel() {
uploadingPercentage.value = 0.2f uploadingPercentage.value = 0.2f
uploadingDescription.value = "Loading" uploadingDescription.value = "Loading"
contentResolver.openInputStream(fileUri)?.use { contentResolver.openInputStream(fileUri)?.use {
createNIP95Record(it.readBytes(), contentType, description, sensitiveContent) createNIP95Record(it.readBytes(), contentType, description, sensitiveContent, relayList = relayList)
} }
?: run { ?: run {
viewModelScope.launch { viewModelScope.launch {
@@ -98,7 +99,7 @@ open class NewMediaModel : ViewModel() {
server = serverToUse, server = serverToUse,
contentResolver = contentResolver, contentResolver = contentResolver,
onSuccess = { imageUrl, mimeType -> onSuccess = { imageUrl, mimeType ->
createNIP94Record(imageUrl, mimeType, description, sensitiveContent) createNIP94Record(imageUrl, mimeType, description, sensitiveContent, relayList = relayList)
}, },
onError = { onError = {
isUploadingImage = false isUploadingImage = false
@@ -138,7 +139,7 @@ open class NewMediaModel : ViewModel() {
return !isUploadingImage && galleryUri != null && selectedServer != null return !isUploadingImage && galleryUri != null && selectedServer != null
} }
fun createNIP94Record(imageUrl: String, mimeType: String?, description: String, sensitiveContent: Boolean) { fun createNIP94Record(imageUrl: String, mimeType: String?, description: String, sensitiveContent: Boolean, relayList: List<Relay>? = null) {
uploadingPercentage.value = 0.40f uploadingPercentage.value = 0.40f
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
uploadingDescription.value = "Server Processing" uploadingDescription.value = "Server Processing"
@@ -162,7 +163,7 @@ open class NewMediaModel : ViewModel() {
onReady = { onReady = {
uploadingPercentage.value = 0.90f uploadingPercentage.value = 0.90f
uploadingDescription.value = "Sending" uploadingDescription.value = "Sending"
account?.sendHeader(it) account?.sendHeader(it, relayList)
uploadingPercentage.value = 1.00f uploadingPercentage.value = 1.00f
isUploadingImage = false isUploadingImage = false
onceUploaded() onceUploaded()
@@ -191,7 +192,7 @@ open class NewMediaModel : ViewModel() {
} }
} }
fun createNIP95Record(bytes: ByteArray, mimeType: String?, description: String, sensitiveContent: Boolean) { fun createNIP95Record(bytes: ByteArray, mimeType: String?, description: String, sensitiveContent: Boolean, relayList: List<Relay>? = null) {
uploadingPercentage.value = 0.30f uploadingPercentage.value = 0.30f
uploadingDescription.value = "Hashing" uploadingDescription.value = "Hashing"
@@ -210,7 +211,7 @@ open class NewMediaModel : ViewModel() {
if (nip95 != null) { if (nip95 != null) {
uploadingDescription.value = "Sending" uploadingDescription.value = "Sending"
uploadingPercentage.value = 0.60f uploadingPercentage.value = 0.60f
account?.sendNip95(nip95.first, nip95.second) account?.sendNip95(nip95.first, nip95.second, relayList)
} }
uploadingPercentage.value = 1.00f uploadingPercentage.value = 1.00f

View File

@@ -7,17 +7,21 @@ import android.util.Size
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Surface import androidx.compose.material.Surface
@@ -33,6 +37,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -70,6 +75,17 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
} }
} }
var showRelaysDialog by remember {
mutableStateOf(false)
}
var relayList = account.activeRelays()?.filter {
it.write
}?.map {
it
} ?: account.convertLocalRelays().filter {
it.write
}
Dialog( Dialog(
onDismissRequest = { onClose() }, onDismissRequest = { onClose() },
properties = DialogProperties( properties = DialogProperties(
@@ -82,6 +98,20 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
if (showRelaysDialog) {
RelaySelectionDialog(
list = relayList,
onClose = {
showRelaysDialog = false
},
onPost = {
relayList = it
},
accountViewModel = accountViewModel,
nav = nav
)
}
Column( Column(
modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp) modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp)
.fillMaxWidth() .fillMaxWidth()
@@ -97,10 +127,26 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
onClose() onClose()
}) })
Box {
IconButton(
modifier = Modifier.align(Alignment.Center),
onClick = {
showRelaysDialog = true
}
) {
Icon(
painter = painterResource(R.drawable.relays),
contentDescription = null,
modifier = Modifier.height(25.dp),
tint = MaterialTheme.colors.onBackground
)
}
}
PostButton( PostButton(
onPost = { onPost = {
onClose() onClose()
postViewModel.upload(context) postViewModel.upload(context, relayList)
postViewModel.selectedServer?.let { account.changeDefaultFileServer(it) } postViewModel.selectedServer?.let { account.changeDefaultFileServer(it) }
}, },
isActive = postViewModel.canPost() isActive = postViewModel.canPost()

View File

@@ -110,6 +110,16 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var showRelaysDialog by remember {
mutableStateOf(false)
}
var relayList = account.activeRelays()?.filter {
it.write
}?.map {
it
} ?: account.convertLocalRelays().filter {
it.write
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
postViewModel.load(account, baseReplyTo, quote) postViewModel.load(account, baseReplyTo, quote)
@@ -144,6 +154,20 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight() .fillMaxHeight()
) { ) {
if (showRelaysDialog) {
RelaySelectionDialog(
list = relayList,
onClose = {
showRelaysDialog = false
},
onPost = {
relayList = it
},
accountViewModel = accountViewModel,
nav = nav
)
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -165,10 +189,25 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
onClose() onClose()
}) })
Box {
IconButton(
modifier = Modifier.align(Alignment.Center),
onClick = {
showRelaysDialog = true
}
) {
Icon(
painter = painterResource(R.drawable.relays),
contentDescription = null,
modifier = Modifier.height(25.dp),
tint = MaterialTheme.colors.onBackground
)
}
}
PostButton( PostButton(
onPost = { onPost = {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
postViewModel.sendPost() postViewModel.sendPost(relayList = relayList)
onClose() onClose()
} }
}, },
@@ -265,7 +304,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
url, url,
account.defaultFileServer, account.defaultFileServer,
onAdd = { description, server, sensitiveContent -> onAdd = { description, server, sensitiveContent ->
postViewModel.upload(url, description, sensitiveContent, server, context) postViewModel.upload(url, description, sensitiveContent, server, context, relayList)
account.changeDefaultFileServer(server) account.changeDefaultFileServer(server)
}, },
onCancel = { onCancel = {

View File

@@ -22,6 +22,7 @@ import com.vitorpamplona.amethyst.service.model.CommunityDefinitionEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.service.model.TextNoteEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.components.MediaCompressor import com.vitorpamplona.amethyst.ui.components.MediaCompressor
import com.vitorpamplona.amethyst.ui.components.isValidURL import com.vitorpamplona.amethyst.ui.components.isValidURL
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -128,7 +129,7 @@ open class NewPostViewModel() : ViewModel() {
this.account = account this.account = account
} }
fun sendPost() { fun sendPost(relayList: List<Relay>? = null) {
val tagger = NewMessageTagger(originalNote?.channelHex(), mentions, replyTos, message.text) val tagger = NewMessageTagger(originalNote?.channelHex(), mentions, replyTos, message.text)
tagger.run() tagger.run()
@@ -145,7 +146,20 @@ open class NewPostViewModel() : ViewModel() {
val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount else null val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount else null
if (wantsPoll) { if (wantsPoll) {
account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount) account?.sendPoll(
tagger.message,
tagger.replyTos,
tagger.mentions,
pollOptions,
valueMaximum,
valueMinimum,
consensusThreshold,
closedAt,
zapReceiver,
wantsToMarkAsSensitive,
localZapRaiserAmount,
relayList
)
} else if (originalNote?.channelHex() != null) { } else if (originalNote?.channelHex() != null) {
if (originalNote is AddressableEvent && originalNote?.address() != null) { if (originalNote is AddressableEvent && originalNote?.address() != null) {
account?.sendLiveMessage(tagger.message, originalNote?.address()!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount) account?.sendLiveMessage(tagger.message, originalNote?.address()!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount)
@@ -172,14 +186,15 @@ open class NewPostViewModel() : ViewModel() {
zapRaiserAmount = localZapRaiserAmount, zapRaiserAmount = localZapRaiserAmount,
replyingTo = replyId, replyingTo = replyId,
root = rootId, root = rootId,
directMentions = tagger.directMentions directMentions = tagger.directMentions,
relayList = relayList
) )
} }
cancel() cancel()
} }
fun upload(galleryUri: Uri, description: String, sensitiveContent: Boolean, server: ServersAvailable, context: Context) { fun upload(galleryUri: Uri, description: String, sensitiveContent: Boolean, server: ServersAvailable, context: Context, relayList: List<Relay>? = null) {
isUploadingImage = true isUploadingImage = true
contentToAddUrl = null contentToAddUrl = null
@@ -194,7 +209,7 @@ open class NewPostViewModel() : ViewModel() {
onReady = { fileUri, contentType, size -> onReady = { fileUri, contentType, size ->
if (server == ServersAvailable.NIP95) { if (server == ServersAvailable.NIP95) {
contentResolver.openInputStream(fileUri)?.use { contentResolver.openInputStream(fileUri)?.use {
createNIP95Record(it.readBytes(), contentType, description, sensitiveContent) createNIP95Record(it.readBytes(), contentType, description, sensitiveContent, relayList = relayList)
} }
} else { } else {
ImageUploader.uploadImage( ImageUploader.uploadImage(
@@ -370,7 +385,7 @@ open class NewPostViewModel() : ViewModel() {
} }
} }
fun createNIP94Record(imageUrl: String, mimeType: String?, description: String, sensitiveContent: Boolean) { fun createNIP94Record(imageUrl: String, mimeType: String?, description: String, sensitiveContent: Boolean, relayList: List<Relay>? = null) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
// Images don't seem to be ready immediately after upload // Images don't seem to be ready immediately after upload
FileHeader.prepare( FileHeader.prepare(
@@ -379,7 +394,7 @@ open class NewPostViewModel() : ViewModel() {
description, description,
sensitiveContent, sensitiveContent,
onReady = { onReady = {
val note = account?.sendHeader(it) val note = account?.sendHeader(it, relayList = relayList)
isUploadingImage = false isUploadingImage = false
@@ -401,7 +416,7 @@ open class NewPostViewModel() : ViewModel() {
} }
} }
fun createNIP95Record(bytes: ByteArray, mimeType: String?, description: String, sensitiveContent: Boolean) { fun createNIP95Record(bytes: ByteArray, mimeType: String?, description: String, sensitiveContent: Boolean, relayList: List<Relay>? = null) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
FileHeader.prepare( FileHeader.prepare(
bytes, bytes,
@@ -411,7 +426,7 @@ open class NewPostViewModel() : ViewModel() {
sensitiveContent, sensitiveContent,
onReady = { onReady = {
val nip95 = account?.createNip95(bytes, headerInfo = it) val nip95 = account?.createNip95(bytes, headerInfo = it)
val note = nip95?.let { it1 -> account?.sendNip95(it1.first, it1.second) } val note = nip95?.let { it1 -> account?.sendNip95(it1.first, it1.second, relayList = relayList) }
isUploadingImage = false isUploadingImage = false

View File

@@ -0,0 +1,183 @@
package com.vitorpamplona.amethyst.ui.actions
import android.widget.Toast
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.Surface
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
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.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.vitorpamplona.amethyst.model.RelayInformation
import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.launch
data class RelayList(
val relay: Relay,
val isSelected: Boolean
)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun RelaySelectionDialog(
list: List<Relay>,
onClose: () -> Unit,
onPost: (list: List<Relay>) -> Unit,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val relayList = accountViewModel.account.activeRelays()?.filter {
it.write
}?.map {
it
} ?: accountViewModel.account.convertLocalRelays().filter {
it.write
}
var relays by remember {
mutableStateOf(
relayList.map {
RelayList(
it,
list.any { relay -> it.url == relay.url }
)
}
)
}
var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
if (relayInfo != null) {
RelayInformationDialog(
onClose = {
relayInfo = null
},
relayInfo = relayInfo!!,
accountViewModel,
nav
)
}
Dialog(
onDismissRequest = { onClose() },
properties = DialogProperties(
usePlatformDefaultWidth = false,
dismissOnClickOutside = false,
decorFitsSystemWindows = false
)
) {
Surface(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.padding(start = 10.dp, end = 10.dp, top = 10.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
CloseButton(
onCancel = {
onClose()
}
)
PostButton(
onPost = {
val selectedRelays = relays.filter { it.isSelected }
if (selectedRelays.isEmpty()) {
scope.launch {
Toast.makeText(context, "Select a relay to continue", Toast.LENGTH_SHORT).show()
}
return@PostButton
}
onPost(selectedRelays.map { it.relay })
onClose()
},
isActive = true
)
}
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
)
) {
itemsIndexed(
relays,
key = { _, item -> item.relay.url }
) { index, item ->
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.combinedClickable(
onClick = {
relays = relays.mapIndexed { j, item ->
if (index == j) {
item.copy(isSelected = !item.isSelected)
} else {
item
}
}
},
onLongClick = {
loadRelayInfo(item.relay.url, context, scope) {
relayInfo = it
}
}
)
) {
Text(
item.relay.url
.removePrefix("ws://")
.removePrefix("wss://")
.removeSuffix("/")
)
Switch(
checked = item.isSelected,
onCheckedChange = {
relays = relays.mapIndexed { j, item ->
if (index == j) {
item.copy(isSelected = !item.isSelected)
} else { item }
}
}
)
}
}
}
}
}
}
}