From d0dee84031a7de4f2c8a4feb833d364753c9e4ad Mon Sep 17 00:00:00 2001 From: greenart7c3 <115044884+greenart7c3@users.noreply.github.com> Date: Mon, 17 Jul 2023 09:48:34 -0300 Subject: [PATCH 1/9] add option to send notes to selected relays --- .../vitorpamplona/amethyst/model/Account.kt | 10 +- .../amethyst/service/relays/Client.kt | 13 +- .../amethyst/service/relays/RelayPool.kt | 6 + .../amethyst/ui/actions/NewPostView.kt | 131 +++++++++++++++++- .../amethyst/ui/actions/NewPostViewModel.kt | 21 ++- 5 files changed, 171 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index c28e9fe10..47b27033d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -671,7 +671,8 @@ class Account( zapRaiserAmount: Long? = null, replyingTo: String?, root: String?, - directMentions: Set + directMentions: Set, + relayList: List? = null ) { if (!isWriteable()) return @@ -694,7 +695,7 @@ class Account( privateKey = loggedIn.privKey!! ) - Client.send(signedEvent) + Client.send(signedEvent, relayList = relayList) LocalCache.consume(signedEvent) } @@ -709,7 +710,8 @@ class Account( closedAt: Int?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, - zapRaiserAmount: Long? = null + zapRaiserAmount: Long? = null, + relayList: List? = null ) { if (!isWriteable()) return @@ -733,7 +735,7 @@ class Account( zapRaiserAmount = zapRaiserAmount ) // println("Sending new PollNoteEvent: %s".format(signedEvent.toJson())) - Client.send(signedEvent) + Client.send(signedEvent, relayList = relayList) LocalCache.consume(signedEvent) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt index f300f41f2..d20dc44b8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.service.relays +import android.util.Log import com.vitorpamplona.amethyst.service.HttpClient import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.model.Event @@ -73,10 +74,18 @@ object Client : RelayPool.Listener { RelayPool.sendFilterOnlyIfDisconnected() } - fun send(signedEvent: EventInterface, relay: String? = null, feedTypes: Set? = null, onDone: (() -> Unit)? = null) { + fun send( + signedEvent: EventInterface, + relay: String? = null, + feedTypes: Set? = null, + relayList: List? = null, + onDone: (() -> Unit)? = null + ) { checkNotInMainThread() - if (relay == null) { + if (relayList != null) { + RelayPool.sendToSelectedRelays(relayList, signedEvent) + } else if (relay == null) { RelayPool.send(signedEvent) } else { val useConnectedRelayIfPresent = RelayPool.getRelays(relay) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt index 0d2140c18..e42293631 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt @@ -62,6 +62,12 @@ object RelayPool : Relay.Listener { relays.forEach { it.sendFilterOnlyIfDisconnected() } } + fun sendToSelectedRelays(list: List, signedEvent: EventInterface) { + list.forEach { relay -> + relays.filter { it.url == relay.url }.forEach { it.send(signedEvent) } + } + } + fun send(signedEvent: EventInterface) { relays.forEach { it.send(signedEvent) } } 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 97b7263e8..75b474b38 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 @@ -9,6 +9,7 @@ import android.widget.Toast import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -72,6 +73,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource import com.vitorpamplona.amethyst.service.noProtocolUrlValidator +import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.* import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -92,6 +94,11 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +data class RelayList( + val relay: Relay, + val isSelected: Boolean +) + @OptIn(ExperimentalComposeUiApi::class) @Composable fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, accountViewModel: AccountViewModel, nav: (String) -> Unit) { @@ -107,6 +114,27 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n val scrollState = rememberScrollState() val scope = rememberCoroutineScope() + var showRelaysDialog by remember { + mutableStateOf(false) + } + val relayList = account.activeRelays()?.filter { + it.write + }?.map { + it + } ?: account.convertLocalRelays().filter { + it.write + } + + var relays by remember { + mutableStateOf( + relayList.map { + RelayList( + it, + true + ) + } + ) + } LaunchedEffect(Unit) { postViewModel.load(account, baseReplyTo, quote) @@ -141,6 +169,90 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n .fillMaxWidth() .fillMaxHeight() ) { + if (showRelaysDialog) { + Dialog( + onDismissRequest = { showRelaysDialog = false }, + 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 = { + showRelaysDialog = false + } + ) + + PostButton( + onPost = { + scope.launch(Dispatchers.IO) { + showRelaysDialog = false + } + }, + 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() + .clickable { + relays = relays.mapIndexed { j, item -> + if (index == j) { + item.copy(isSelected = !item.isSelected) + } else { item } + } + } + ) { + Text(text = item.relay.url) + Switch( + checked = item.isSelected, + onCheckedChange = { + relays = relays.mapIndexed { j, item -> + if (index == j) { + item.copy(isSelected = !item.isSelected) + } else { item } + } + } + ) + } + } + } + } + } + } + } + Column( modifier = Modifier .fillMaxWidth() @@ -162,10 +274,27 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n 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( onPost = { + val list = relays.filter { it.isSelected }.map { it.relay } + scope.launch(Dispatchers.IO) { - postViewModel.sendPost() + postViewModel.sendPost(relayList = list) onClose() } }, 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 ec1cbb04a..dcaa20007 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 @@ -22,6 +22,7 @@ import com.vitorpamplona.amethyst.service.model.CommunityDefinitionEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent 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.isValidURL import kotlinx.coroutines.Dispatchers @@ -128,7 +129,7 @@ open class NewPostViewModel() : ViewModel() { this.account = account } - fun sendPost() { + fun sendPost(relayList: List? = null) { val tagger = NewMessageTagger(originalNote?.channelHex(), mentions, replyTos, message.text) tagger.run() @@ -145,7 +146,20 @@ open class NewPostViewModel() : ViewModel() { val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount else null 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) { if (originalNote is AddressableEvent && originalNote?.address() != null) { account?.sendLiveMessage(tagger.message, originalNote?.address()!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount) @@ -172,7 +186,8 @@ open class NewPostViewModel() : ViewModel() { zapRaiserAmount = localZapRaiserAmount, replyingTo = replyId, root = rootId, - directMentions = tagger.directMentions + directMentions = tagger.directMentions, + relayList = relayList ) } From f0ecf382e05477f041a1ede7b9d87235ba5642c5 Mon Sep 17 00:00:00 2001 From: greenart7c3 <115044884+greenart7c3@users.noreply.github.com> Date: Mon, 17 Jul 2023 09:56:16 -0300 Subject: [PATCH 2/9] remove unused import --- .../java/com/vitorpamplona/amethyst/service/relays/Client.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt index d20dc44b8..aa24332f0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt @@ -1,6 +1,5 @@ package com.vitorpamplona.amethyst.service.relays -import android.util.Log import com.vitorpamplona.amethyst.service.HttpClient import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.model.Event From 0d63441611d83681e8bfeb074dfe7e59800bcc60 Mon Sep 17 00:00:00 2001 From: greenart7c3 <115044884+greenart7c3@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:41:47 -0300 Subject: [PATCH 3/9] created RelaySelectionDialog file --- .../amethyst/ui/actions/NewPostView.kt | 115 ++------------ .../ui/actions/RelaySelectionDialog.kt | 150 ++++++++++++++++++ 2 files changed, 162 insertions(+), 103 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt 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 75b474b38..ce20c7400 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 @@ -9,7 +9,6 @@ import android.widget.Toast import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -73,7 +72,6 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource import com.vitorpamplona.amethyst.service.noProtocolUrlValidator -import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.* import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -94,11 +92,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -data class RelayList( - val relay: Relay, - val isSelected: Boolean -) - @OptIn(ExperimentalComposeUiApi::class) @Composable fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, accountViewModel: AccountViewModel, nav: (String) -> Unit) { @@ -117,7 +110,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n var showRelaysDialog by remember { mutableStateOf(false) } - val relayList = account.activeRelays()?.filter { + var relayList = account.activeRelays()?.filter { it.write }?.map { it @@ -125,17 +118,6 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n it.write } - var relays by remember { - mutableStateOf( - relayList.map { - RelayList( - it, - true - ) - } - ) - } - LaunchedEffect(Unit) { postViewModel.load(account, baseReplyTo, quote) delay(100) @@ -170,87 +152,16 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n .fillMaxHeight() ) { if (showRelaysDialog) { - Dialog( - onDismissRequest = { showRelaysDialog = false }, - 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 = { - showRelaysDialog = false - } - ) - - PostButton( - onPost = { - scope.launch(Dispatchers.IO) { - showRelaysDialog = false - } - }, - 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() - .clickable { - relays = relays.mapIndexed { j, item -> - if (index == j) { - item.copy(isSelected = !item.isSelected) - } else { item } - } - } - ) { - Text(text = item.relay.url) - Switch( - checked = item.isSelected, - onCheckedChange = { - relays = relays.mapIndexed { j, item -> - if (index == j) { - item.copy(isSelected = !item.isSelected) - } else { item } - } - } - ) - } - } - } - } - } - } + RelaySelectionDialog( + list = relayList, + onClose = { + showRelaysDialog = false + }, + onPost = { + relayList = it + }, + account = account + ) } Column( @@ -291,10 +202,8 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n } PostButton( onPost = { - val list = relays.filter { it.isSelected }.map { it.relay } - scope.launch(Dispatchers.IO) { - postViewModel.sendPost(relayList = list) + postViewModel.sendPost(relayList = relayList) onClose() } }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt new file mode 100644 index 000000000..9d0bc2ee5 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt @@ -0,0 +1,150 @@ +package com.vitorpamplona.amethyst.ui.actions + +import android.widget.Toast +import androidx.compose.foundation.clickable +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.Account +import com.vitorpamplona.amethyst.service.relays.Relay +import kotlinx.coroutines.launch + +data class RelayList( + val relay: Relay, + val isSelected: Boolean +) + +@Composable +fun RelaySelectionDialog(list: List, onClose: () -> Unit, onPost: (list: List) -> Unit, account: Account) { + val scope = rememberCoroutineScope() + val context = LocalContext.current + val relayList = account.activeRelays()?.filter { + it.write + }?.map { + it + } ?: account.convertLocalRelays().filter { + it.write + } + + var relays by remember { + mutableStateOf( + relayList.map { + RelayList( + it, + list.any { relay -> it.url == relay.url } + ) + } + ) + } + + 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@launch + } + } + 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() + .clickable { + relays = relays.mapIndexed { j, item -> + if (index == j) { + item.copy(isSelected = !item.isSelected) + } else { + item + } + } + } + ) { + Text(text = item.relay.url) + Switch( + checked = item.isSelected, + onCheckedChange = { + relays = relays.mapIndexed { j, item -> + if (index == j) { + item.copy(isSelected = !item.isSelected) + } else { item } + } + } + ) + } + } + } + } + } + } +} From d730eeb9a10018f80b7c160fc43a48afcd3917fb Mon Sep 17 00:00:00 2001 From: greenart7c3 <115044884+greenart7c3@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:15:19 -0300 Subject: [PATCH 4/9] add relay selection to nip 94 and 95 uploads --- .../vitorpamplona/amethyst/model/Account.kt | 10 ++-- .../amethyst/ui/actions/NewMediaModel.kt | 15 +++--- .../amethyst/ui/actions/NewMediaView.kt | 47 ++++++++++++++++++- .../amethyst/ui/actions/NewPostView.kt | 2 +- .../amethyst/ui/actions/NewPostViewModel.kt | 12 ++--- 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 47b27033d..45688e602 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -628,19 +628,19 @@ class Account( return Pair(data, signedEvent) } - fun sendNip95(data: FileStorageEvent, signedEvent: FileStorageHeaderEvent): Note? { + fun sendNip95(data: FileStorageEvent, signedEvent: FileStorageHeaderEvent, relayList: List? = null): Note? { if (!isWriteable()) return null - Client.send(data) + Client.send(data, relayList = relayList) LocalCache.consume(data, null) - Client.send(signedEvent) + Client.send(signedEvent, relayList = relayList) LocalCache.consume(signedEvent, null) return LocalCache.notes[signedEvent.id] } - fun sendHeader(headerInfo: FileHeader): Note? { + fun sendHeader(headerInfo: FileHeader, relayList: List? = null): Note? { if (!isWriteable()) return null val signedEvent = FileHeaderEvent.create( @@ -655,7 +655,7 @@ class Account( privateKey = loggedIn.privKey!! ) - Client.send(signedEvent) + Client.send(signedEvent, relayList = relayList) LocalCache.consume(signedEvent, null) return LocalCache.notes[signedEvent.id] diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt index a8e8c513c..610d77ced 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt @@ -11,6 +11,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.model.* import com.vitorpamplona.amethyst.service.FileHeader +import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.MediaCompressor import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow @@ -56,7 +57,7 @@ open class NewMediaModel : ViewModel() { } } - fun upload(context: Context) { + fun upload(context: Context, relayList: List? = null) { isUploadingImage = true val contentResolver = context.contentResolver @@ -78,7 +79,7 @@ open class NewMediaModel : ViewModel() { uploadingPercentage.value = 0.2f uploadingDescription.value = "Loading" contentResolver.openInputStream(fileUri)?.use { - createNIP95Record(it.readBytes(), contentType, description, sensitiveContent) + createNIP95Record(it.readBytes(), contentType, description, sensitiveContent, relayList = relayList) } ?: run { viewModelScope.launch { @@ -98,7 +99,7 @@ open class NewMediaModel : ViewModel() { server = serverToUse, contentResolver = contentResolver, onSuccess = { imageUrl, mimeType -> - createNIP94Record(imageUrl, mimeType, description, sensitiveContent) + createNIP94Record(imageUrl, mimeType, description, sensitiveContent, relayList = relayList) }, onError = { isUploadingImage = false @@ -138,7 +139,7 @@ open class NewMediaModel : ViewModel() { 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? = null) { uploadingPercentage.value = 0.40f viewModelScope.launch(Dispatchers.IO) { uploadingDescription.value = "Server Processing" @@ -162,7 +163,7 @@ open class NewMediaModel : ViewModel() { onReady = { uploadingPercentage.value = 0.90f uploadingDescription.value = "Sending" - account?.sendHeader(it) + account?.sendHeader(it, relayList) uploadingPercentage.value = 1.00f isUploadingImage = false 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? = null) { uploadingPercentage.value = 0.30f uploadingDescription.value = "Hashing" @@ -210,7 +211,7 @@ open class NewMediaModel : ViewModel() { if (nip95 != null) { uploadingDescription.value = "Sending" uploadingPercentage.value = 0.60f - account?.sendNip95(nip95.first, nip95.second) + account?.sendNip95(nip95.first, nip95.second, relayList) } uploadingPercentage.value = 1.00f diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index ef708495f..014e69e99 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -7,17 +7,21 @@ import android.util.Size import android.widget.Toast import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedTextField import androidx.compose.material.Surface @@ -33,6 +37,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization 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( onDismissRequest = { onClose() }, properties = DialogProperties( @@ -82,6 +98,19 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac modifier = Modifier .fillMaxWidth() ) { + if (showRelaysDialog) { + RelaySelectionDialog( + list = relayList, + onClose = { + showRelaysDialog = false + }, + onPost = { + relayList = it + }, + account = account + ) + } + Column( modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp) .fillMaxWidth() @@ -97,10 +126,26 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac 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( onPost = { onClose() - postViewModel.upload(context) + postViewModel.upload(context, relayList) postViewModel.selectedServer?.let { account.changeDefaultFileServer(it) } }, isActive = postViewModel.canPost() 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 ce20c7400..7f0649c2f 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 @@ -300,7 +300,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n url, account.defaultFileServer, onAdd = { description, server, sensitiveContent -> - postViewModel.upload(url, description, sensitiveContent, server, context) + postViewModel.upload(url, description, sensitiveContent, server, context, relayList) account.changeDefaultFileServer(server) }, onCancel = { 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 dcaa20007..5c366c367 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 @@ -194,7 +194,7 @@ open class NewPostViewModel() : ViewModel() { 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? = null) { isUploadingImage = true contentToAddUrl = null @@ -209,7 +209,7 @@ open class NewPostViewModel() : ViewModel() { onReady = { fileUri, contentType, size -> if (server == ServersAvailable.NIP95) { contentResolver.openInputStream(fileUri)?.use { - createNIP95Record(it.readBytes(), contentType, description, sensitiveContent) + createNIP95Record(it.readBytes(), contentType, description, sensitiveContent, relayList = relayList) } } else { ImageUploader.uploadImage( @@ -385,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? = null) { viewModelScope.launch(Dispatchers.IO) { // Images don't seem to be ready immediately after upload FileHeader.prepare( @@ -394,7 +394,7 @@ open class NewPostViewModel() : ViewModel() { description, sensitiveContent, onReady = { - val note = account?.sendHeader(it) + val note = account?.sendHeader(it, relayList = relayList) isUploadingImage = false @@ -416,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? = null) { viewModelScope.launch(Dispatchers.IO) { FileHeader.prepare( bytes, @@ -426,7 +426,7 @@ open class NewPostViewModel() : ViewModel() { sensitiveContent, onReady = { 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 From 91488f035003f645d016512370359dd62b701e5a Mon Sep 17 00:00:00 2001 From: greenart7c3 <115044884+greenart7c3@users.noreply.github.com> Date: Mon, 17 Jul 2023 09:48:34 -0300 Subject: [PATCH 5/9] add option to send notes to selected relays --- .../vitorpamplona/amethyst/model/Account.kt | 10 +- .../amethyst/service/relays/Client.kt | 13 +- .../amethyst/service/relays/RelayPool.kt | 6 + .../amethyst/ui/actions/NewPostView.kt | 131 +++++++++++++++++- .../amethyst/ui/actions/NewPostViewModel.kt | 21 ++- 5 files changed, 171 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index c28e9fe10..47b27033d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -671,7 +671,8 @@ class Account( zapRaiserAmount: Long? = null, replyingTo: String?, root: String?, - directMentions: Set + directMentions: Set, + relayList: List? = null ) { if (!isWriteable()) return @@ -694,7 +695,7 @@ class Account( privateKey = loggedIn.privKey!! ) - Client.send(signedEvent) + Client.send(signedEvent, relayList = relayList) LocalCache.consume(signedEvent) } @@ -709,7 +710,8 @@ class Account( closedAt: Int?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, - zapRaiserAmount: Long? = null + zapRaiserAmount: Long? = null, + relayList: List? = null ) { if (!isWriteable()) return @@ -733,7 +735,7 @@ class Account( zapRaiserAmount = zapRaiserAmount ) // println("Sending new PollNoteEvent: %s".format(signedEvent.toJson())) - Client.send(signedEvent) + Client.send(signedEvent, relayList = relayList) LocalCache.consume(signedEvent) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt index f300f41f2..d20dc44b8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.service.relays +import android.util.Log import com.vitorpamplona.amethyst.service.HttpClient import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.model.Event @@ -73,10 +74,18 @@ object Client : RelayPool.Listener { RelayPool.sendFilterOnlyIfDisconnected() } - fun send(signedEvent: EventInterface, relay: String? = null, feedTypes: Set? = null, onDone: (() -> Unit)? = null) { + fun send( + signedEvent: EventInterface, + relay: String? = null, + feedTypes: Set? = null, + relayList: List? = null, + onDone: (() -> Unit)? = null + ) { checkNotInMainThread() - if (relay == null) { + if (relayList != null) { + RelayPool.sendToSelectedRelays(relayList, signedEvent) + } else if (relay == null) { RelayPool.send(signedEvent) } else { val useConnectedRelayIfPresent = RelayPool.getRelays(relay) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt index 0d2140c18..e42293631 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt @@ -62,6 +62,12 @@ object RelayPool : Relay.Listener { relays.forEach { it.sendFilterOnlyIfDisconnected() } } + fun sendToSelectedRelays(list: List, signedEvent: EventInterface) { + list.forEach { relay -> + relays.filter { it.url == relay.url }.forEach { it.send(signedEvent) } + } + } + fun send(signedEvent: EventInterface) { relays.forEach { it.send(signedEvent) } } 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 4fec46b38..f2a9407d8 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 @@ -9,6 +9,7 @@ import android.widget.Toast import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -71,6 +72,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource import com.vitorpamplona.amethyst.service.noProtocolUrlValidator +import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.* import com.vitorpamplona.amethyst.ui.note.CancelIcon import com.vitorpamplona.amethyst.ui.note.CloseIcon @@ -95,6 +97,11 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +data class RelayList( + val relay: Relay, + val isSelected: Boolean +) + @OptIn(ExperimentalComposeUiApi::class) @Composable fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, accountViewModel: AccountViewModel, nav: (String) -> Unit) { @@ -110,6 +117,27 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n val scrollState = rememberScrollState() val scope = rememberCoroutineScope() + var showRelaysDialog by remember { + mutableStateOf(false) + } + val relayList = account.activeRelays()?.filter { + it.write + }?.map { + it + } ?: account.convertLocalRelays().filter { + it.write + } + + var relays by remember { + mutableStateOf( + relayList.map { + RelayList( + it, + true + ) + } + ) + } LaunchedEffect(Unit) { postViewModel.load(account, baseReplyTo, quote) @@ -144,6 +172,90 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n .fillMaxWidth() .fillMaxHeight() ) { + if (showRelaysDialog) { + Dialog( + onDismissRequest = { showRelaysDialog = false }, + 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 = { + showRelaysDialog = false + } + ) + + PostButton( + onPost = { + scope.launch(Dispatchers.IO) { + showRelaysDialog = false + } + }, + 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() + .clickable { + relays = relays.mapIndexed { j, item -> + if (index == j) { + item.copy(isSelected = !item.isSelected) + } else { item } + } + } + ) { + Text(text = item.relay.url) + Switch( + checked = item.isSelected, + onCheckedChange = { + relays = relays.mapIndexed { j, item -> + if (index == j) { + item.copy(isSelected = !item.isSelected) + } else { item } + } + } + ) + } + } + } + } + } + } + } + Column( modifier = Modifier .fillMaxWidth() @@ -165,10 +277,27 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n 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( onPost = { + val list = relays.filter { it.isSelected }.map { it.relay } + scope.launch(Dispatchers.IO) { - postViewModel.sendPost() + postViewModel.sendPost(relayList = list) onClose() } }, 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 ec1cbb04a..dcaa20007 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 @@ -22,6 +22,7 @@ import com.vitorpamplona.amethyst.service.model.CommunityDefinitionEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent 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.isValidURL import kotlinx.coroutines.Dispatchers @@ -128,7 +129,7 @@ open class NewPostViewModel() : ViewModel() { this.account = account } - fun sendPost() { + fun sendPost(relayList: List? = null) { val tagger = NewMessageTagger(originalNote?.channelHex(), mentions, replyTos, message.text) tagger.run() @@ -145,7 +146,20 @@ open class NewPostViewModel() : ViewModel() { val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount else null 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) { if (originalNote is AddressableEvent && originalNote?.address() != null) { account?.sendLiveMessage(tagger.message, originalNote?.address()!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount) @@ -172,7 +186,8 @@ open class NewPostViewModel() : ViewModel() { zapRaiserAmount = localZapRaiserAmount, replyingTo = replyId, root = rootId, - directMentions = tagger.directMentions + directMentions = tagger.directMentions, + relayList = relayList ) } From 66cc4b5526f6b9c44020d3ea92dc4a8f90b93de1 Mon Sep 17 00:00:00 2001 From: greenart7c3 <115044884+greenart7c3@users.noreply.github.com> Date: Mon, 17 Jul 2023 09:56:16 -0300 Subject: [PATCH 6/9] remove unused import --- .../java/com/vitorpamplona/amethyst/service/relays/Client.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt index d20dc44b8..aa24332f0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt @@ -1,6 +1,5 @@ package com.vitorpamplona.amethyst.service.relays -import android.util.Log import com.vitorpamplona.amethyst.service.HttpClient import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.model.Event From 2fed65db8dcb65603535ccba5553118fa604f436 Mon Sep 17 00:00:00 2001 From: greenart7c3 <115044884+greenart7c3@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:41:47 -0300 Subject: [PATCH 7/9] created RelaySelectionDialog file --- .../amethyst/ui/actions/NewPostView.kt | 115 ++------------ .../ui/actions/RelaySelectionDialog.kt | 150 ++++++++++++++++++ 2 files changed, 162 insertions(+), 103 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt 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 f2a9407d8..3c08850d7 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 @@ -9,7 +9,6 @@ import android.widget.Toast import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -72,7 +71,6 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource import com.vitorpamplona.amethyst.service.noProtocolUrlValidator -import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.* import com.vitorpamplona.amethyst.ui.note.CancelIcon import com.vitorpamplona.amethyst.ui.note.CloseIcon @@ -97,11 +95,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -data class RelayList( - val relay: Relay, - val isSelected: Boolean -) - @OptIn(ExperimentalComposeUiApi::class) @Composable fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, accountViewModel: AccountViewModel, nav: (String) -> Unit) { @@ -120,7 +113,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n var showRelaysDialog by remember { mutableStateOf(false) } - val relayList = account.activeRelays()?.filter { + var relayList = account.activeRelays()?.filter { it.write }?.map { it @@ -128,17 +121,6 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n it.write } - var relays by remember { - mutableStateOf( - relayList.map { - RelayList( - it, - true - ) - } - ) - } - LaunchedEffect(Unit) { postViewModel.load(account, baseReplyTo, quote) delay(100) @@ -173,87 +155,16 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n .fillMaxHeight() ) { if (showRelaysDialog) { - Dialog( - onDismissRequest = { showRelaysDialog = false }, - 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 = { - showRelaysDialog = false - } - ) - - PostButton( - onPost = { - scope.launch(Dispatchers.IO) { - showRelaysDialog = false - } - }, - 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() - .clickable { - relays = relays.mapIndexed { j, item -> - if (index == j) { - item.copy(isSelected = !item.isSelected) - } else { item } - } - } - ) { - Text(text = item.relay.url) - Switch( - checked = item.isSelected, - onCheckedChange = { - relays = relays.mapIndexed { j, item -> - if (index == j) { - item.copy(isSelected = !item.isSelected) - } else { item } - } - } - ) - } - } - } - } - } - } + RelaySelectionDialog( + list = relayList, + onClose = { + showRelaysDialog = false + }, + onPost = { + relayList = it + }, + account = account + ) } Column( @@ -294,10 +205,8 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n } PostButton( onPost = { - val list = relays.filter { it.isSelected }.map { it.relay } - scope.launch(Dispatchers.IO) { - postViewModel.sendPost(relayList = list) + postViewModel.sendPost(relayList = relayList) onClose() } }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt new file mode 100644 index 000000000..9d0bc2ee5 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt @@ -0,0 +1,150 @@ +package com.vitorpamplona.amethyst.ui.actions + +import android.widget.Toast +import androidx.compose.foundation.clickable +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.Account +import com.vitorpamplona.amethyst.service.relays.Relay +import kotlinx.coroutines.launch + +data class RelayList( + val relay: Relay, + val isSelected: Boolean +) + +@Composable +fun RelaySelectionDialog(list: List, onClose: () -> Unit, onPost: (list: List) -> Unit, account: Account) { + val scope = rememberCoroutineScope() + val context = LocalContext.current + val relayList = account.activeRelays()?.filter { + it.write + }?.map { + it + } ?: account.convertLocalRelays().filter { + it.write + } + + var relays by remember { + mutableStateOf( + relayList.map { + RelayList( + it, + list.any { relay -> it.url == relay.url } + ) + } + ) + } + + 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@launch + } + } + 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() + .clickable { + relays = relays.mapIndexed { j, item -> + if (index == j) { + item.copy(isSelected = !item.isSelected) + } else { + item + } + } + } + ) { + Text(text = item.relay.url) + Switch( + checked = item.isSelected, + onCheckedChange = { + relays = relays.mapIndexed { j, item -> + if (index == j) { + item.copy(isSelected = !item.isSelected) + } else { item } + } + } + ) + } + } + } + } + } + } +} From b46bb7ecc5e1136b41002b6989ccfcbeece9a638 Mon Sep 17 00:00:00 2001 From: greenart7c3 <115044884+greenart7c3@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:15:19 -0300 Subject: [PATCH 8/9] add relay selection to nip 94 and 95 uploads --- .../vitorpamplona/amethyst/model/Account.kt | 10 ++-- .../amethyst/ui/actions/NewMediaModel.kt | 15 +++--- .../amethyst/ui/actions/NewMediaView.kt | 47 ++++++++++++++++++- .../amethyst/ui/actions/NewPostView.kt | 2 +- .../amethyst/ui/actions/NewPostViewModel.kt | 12 ++--- 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 47b27033d..45688e602 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -628,19 +628,19 @@ class Account( return Pair(data, signedEvent) } - fun sendNip95(data: FileStorageEvent, signedEvent: FileStorageHeaderEvent): Note? { + fun sendNip95(data: FileStorageEvent, signedEvent: FileStorageHeaderEvent, relayList: List? = null): Note? { if (!isWriteable()) return null - Client.send(data) + Client.send(data, relayList = relayList) LocalCache.consume(data, null) - Client.send(signedEvent) + Client.send(signedEvent, relayList = relayList) LocalCache.consume(signedEvent, null) return LocalCache.notes[signedEvent.id] } - fun sendHeader(headerInfo: FileHeader): Note? { + fun sendHeader(headerInfo: FileHeader, relayList: List? = null): Note? { if (!isWriteable()) return null val signedEvent = FileHeaderEvent.create( @@ -655,7 +655,7 @@ class Account( privateKey = loggedIn.privKey!! ) - Client.send(signedEvent) + Client.send(signedEvent, relayList = relayList) LocalCache.consume(signedEvent, null) return LocalCache.notes[signedEvent.id] diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt index a8e8c513c..610d77ced 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt @@ -11,6 +11,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.model.* import com.vitorpamplona.amethyst.service.FileHeader +import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.MediaCompressor import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow @@ -56,7 +57,7 @@ open class NewMediaModel : ViewModel() { } } - fun upload(context: Context) { + fun upload(context: Context, relayList: List? = null) { isUploadingImage = true val contentResolver = context.contentResolver @@ -78,7 +79,7 @@ open class NewMediaModel : ViewModel() { uploadingPercentage.value = 0.2f uploadingDescription.value = "Loading" contentResolver.openInputStream(fileUri)?.use { - createNIP95Record(it.readBytes(), contentType, description, sensitiveContent) + createNIP95Record(it.readBytes(), contentType, description, sensitiveContent, relayList = relayList) } ?: run { viewModelScope.launch { @@ -98,7 +99,7 @@ open class NewMediaModel : ViewModel() { server = serverToUse, contentResolver = contentResolver, onSuccess = { imageUrl, mimeType -> - createNIP94Record(imageUrl, mimeType, description, sensitiveContent) + createNIP94Record(imageUrl, mimeType, description, sensitiveContent, relayList = relayList) }, onError = { isUploadingImage = false @@ -138,7 +139,7 @@ open class NewMediaModel : ViewModel() { 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? = null) { uploadingPercentage.value = 0.40f viewModelScope.launch(Dispatchers.IO) { uploadingDescription.value = "Server Processing" @@ -162,7 +163,7 @@ open class NewMediaModel : ViewModel() { onReady = { uploadingPercentage.value = 0.90f uploadingDescription.value = "Sending" - account?.sendHeader(it) + account?.sendHeader(it, relayList) uploadingPercentage.value = 1.00f isUploadingImage = false 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? = null) { uploadingPercentage.value = 0.30f uploadingDescription.value = "Hashing" @@ -210,7 +211,7 @@ open class NewMediaModel : ViewModel() { if (nip95 != null) { uploadingDescription.value = "Sending" uploadingPercentage.value = 0.60f - account?.sendNip95(nip95.first, nip95.second) + account?.sendNip95(nip95.first, nip95.second, relayList) } uploadingPercentage.value = 1.00f diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index ef708495f..014e69e99 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -7,17 +7,21 @@ import android.util.Size import android.widget.Toast import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedTextField import androidx.compose.material.Surface @@ -33,6 +37,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization 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( onDismissRequest = { onClose() }, properties = DialogProperties( @@ -82,6 +98,19 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac modifier = Modifier .fillMaxWidth() ) { + if (showRelaysDialog) { + RelaySelectionDialog( + list = relayList, + onClose = { + showRelaysDialog = false + }, + onPost = { + relayList = it + }, + account = account + ) + } + Column( modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp) .fillMaxWidth() @@ -97,10 +126,26 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac 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( onPost = { onClose() - postViewModel.upload(context) + postViewModel.upload(context, relayList) postViewModel.selectedServer?.let { account.changeDefaultFileServer(it) } }, isActive = postViewModel.canPost() 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 3c08850d7..cf72d36f4 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 @@ -303,7 +303,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n url, account.defaultFileServer, onAdd = { description, server, sensitiveContent -> - postViewModel.upload(url, description, sensitiveContent, server, context) + postViewModel.upload(url, description, sensitiveContent, server, context, relayList) account.changeDefaultFileServer(server) }, onCancel = { 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 dcaa20007..5c366c367 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 @@ -194,7 +194,7 @@ open class NewPostViewModel() : ViewModel() { 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? = null) { isUploadingImage = true contentToAddUrl = null @@ -209,7 +209,7 @@ open class NewPostViewModel() : ViewModel() { onReady = { fileUri, contentType, size -> if (server == ServersAvailable.NIP95) { contentResolver.openInputStream(fileUri)?.use { - createNIP95Record(it.readBytes(), contentType, description, sensitiveContent) + createNIP95Record(it.readBytes(), contentType, description, sensitiveContent, relayList = relayList) } } else { ImageUploader.uploadImage( @@ -385,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? = null) { viewModelScope.launch(Dispatchers.IO) { // Images don't seem to be ready immediately after upload FileHeader.prepare( @@ -394,7 +394,7 @@ open class NewPostViewModel() : ViewModel() { description, sensitiveContent, onReady = { - val note = account?.sendHeader(it) + val note = account?.sendHeader(it, relayList = relayList) isUploadingImage = false @@ -416,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? = null) { viewModelScope.launch(Dispatchers.IO) { FileHeader.prepare( bytes, @@ -426,7 +426,7 @@ open class NewPostViewModel() : ViewModel() { sensitiveContent, onReady = { 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 From c2b9bb413d1245455f2f18640201cfda2c8065c3 Mon Sep 17 00:00:00 2001 From: greenart7c3 <115044884+greenart7c3@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:08:14 -0300 Subject: [PATCH 9/9] add long press to show relay info, fix validation, show relay url without prefix --- .../amethyst/ui/actions/NewMediaView.kt | 3 +- .../amethyst/ui/actions/NewPostView.kt | 3 +- .../ui/actions/RelaySelectionDialog.kt | 61 ++++++++++++++----- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index 014e69e99..f772dedc0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -107,7 +107,8 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac onPost = { relayList = it }, - account = account + accountViewModel = accountViewModel, + nav = nav ) } 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 cf72d36f4..0aa09784e 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 @@ -163,7 +163,8 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n onPost = { relayList = it }, - account = account + accountViewModel = accountViewModel, + nav = nav ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt index 9d0bc2ee5..a2becfb94 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt @@ -1,7 +1,8 @@ package com.vitorpamplona.amethyst.ui.actions import android.widget.Toast -import androidx.compose.foundation.clickable +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 @@ -26,8 +27,9 @@ 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.Account +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( @@ -35,15 +37,22 @@ data class RelayList( val isSelected: Boolean ) +@OptIn(ExperimentalFoundationApi::class) @Composable -fun RelaySelectionDialog(list: List, onClose: () -> Unit, onPost: (list: List) -> Unit, account: Account) { +fun RelaySelectionDialog( + list: List, + onClose: () -> Unit, + onPost: (list: List) -> Unit, + accountViewModel: AccountViewModel, + nav: (String) -> Unit +) { val scope = rememberCoroutineScope() val context = LocalContext.current - val relayList = account.activeRelays()?.filter { + val relayList = accountViewModel.account.activeRelays()?.filter { it.write }?.map { it - } ?: account.convertLocalRelays().filter { + } ?: accountViewModel.account.convertLocalRelays().filter { it.write } @@ -57,6 +66,18 @@ fun RelaySelectionDialog(list: List, onClose: () -> Unit, onPost: (list: } ) } + var relayInfo: RelayInformation? by remember { mutableStateOf(null) } + + if (relayInfo != null) { + RelayInformationDialog( + onClose = { + relayInfo = null + }, + relayInfo = relayInfo!!, + accountViewModel, + nav + ) + } Dialog( onDismissRequest = { onClose() }, @@ -95,8 +116,8 @@ fun RelaySelectionDialog(list: List, onClose: () -> Unit, onPost: (list: if (selectedRelays.isEmpty()) { scope.launch { Toast.makeText(context, "Select a relay to continue", Toast.LENGTH_SHORT).show() - return@launch } + return@PostButton } onPost(selectedRelays.map { it.relay }) onClose() @@ -120,17 +141,29 @@ fun RelaySelectionDialog(list: List, onClose: () -> Unit, onPost: (list: verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() - .clickable { - relays = relays.mapIndexed { j, item -> - if (index == j) { - item.copy(isSelected = !item.isSelected) - } else { - item + .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(text = item.relay.url) + Text( + item.relay.url + .removePrefix("ws://") + .removePrefix("wss://") + .removeSuffix("/") + ) Switch( checked = item.isSelected, onCheckedChange = {