Adds a card to setup the DM relay list if the user doesn't have one.
This commit is contained in:
@@ -52,6 +52,7 @@ import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
|||||||
import com.vitorpamplona.quartz.events.ChannelMessageEvent
|
import com.vitorpamplona.quartz.events.ChannelMessageEvent
|
||||||
import com.vitorpamplona.quartz.events.ChannelMetadataEvent
|
import com.vitorpamplona.quartz.events.ChannelMetadataEvent
|
||||||
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||||
|
import com.vitorpamplona.quartz.events.ChatMessageRelayListEvent
|
||||||
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||||
import com.vitorpamplona.quartz.events.Contact
|
import com.vitorpamplona.quartz.events.Contact
|
||||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||||
@@ -2545,6 +2546,40 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDMRelayList(): ChatMessageRelayListEvent? {
|
||||||
|
return LocalCache.getOrCreateAddressableNote(
|
||||||
|
ChatMessageRelayListEvent.createAddressATag(signer.pubKey),
|
||||||
|
).event as? ChatMessageRelayListEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveDMRelayList(dmRelays: List<String>) {
|
||||||
|
if (!isWriteable()) return
|
||||||
|
|
||||||
|
val relayListForDMs =
|
||||||
|
LocalCache.getOrCreateAddressableNote(
|
||||||
|
ChatMessageRelayListEvent.createAddressATag(signer.pubKey),
|
||||||
|
).event as? ChatMessageRelayListEvent
|
||||||
|
|
||||||
|
if (relayListForDMs != null && relayListForDMs.tags.isNotEmpty()) {
|
||||||
|
ChatMessageRelayListEvent.updateRelayList(
|
||||||
|
earlierVersion = relayListForDMs,
|
||||||
|
relays = dmRelays,
|
||||||
|
signer = signer,
|
||||||
|
) {
|
||||||
|
Client.send(it)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ChatMessageRelayListEvent.createFromScratch(
|
||||||
|
relays = dmRelays,
|
||||||
|
signer = signer,
|
||||||
|
) {
|
||||||
|
Client.send(it)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setHideDeleteRequestDialog() {
|
fun setHideDeleteRequestDialog() {
|
||||||
hideDeleteRequestDialog = true
|
hideDeleteRequestDialog = true
|
||||||
saveable.invalidateData()
|
saveable.invalidateData()
|
||||||
|
|||||||
@@ -0,0 +1,559 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Cancel
|
||||||
|
import androidx.compose.material.icons.filled.DeleteSweep
|
||||||
|
import androidx.compose.material.icons.filled.Download
|
||||||
|
import androidx.compose.material.icons.filled.Paid
|
||||||
|
import androidx.compose.material.icons.filled.SyncProblem
|
||||||
|
import androidx.compose.material.icons.filled.Upload
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
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.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
|
||||||
|
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||||
|
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.HalfHorzPadding
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightChat
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.WarningColor
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.allGoodColor
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.largeRelayIconModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.warningColor
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun DMRelayListView(
|
||||||
|
onClose: () -> Unit,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
relayToAdd: String = "",
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
val postViewModel: DMRelayListViewModel = viewModel()
|
||||||
|
val feedState by postViewModel.relays.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) { postViewModel.load(accountViewModel.account) }
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onClose,
|
||||||
|
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
|
||||||
|
Text(stringResource(R.string.dm_relays_title))
|
||||||
|
|
||||||
|
SaveButton(
|
||||||
|
onPost = {
|
||||||
|
postViewModel.create()
|
||||||
|
onClose()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
CloseButton(
|
||||||
|
onPress = {
|
||||||
|
postViewModel.clear()
|
||||||
|
onClose()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors =
|
||||||
|
TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { pad ->
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
Modifier.padding(
|
||||||
|
16.dp,
|
||||||
|
pad.calculateTopPadding(),
|
||||||
|
16.dp,
|
||||||
|
pad.calculateBottomPadding(),
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.SpaceAround,
|
||||||
|
) {
|
||||||
|
Card(modifier = MaterialTheme.colorScheme.imageModifier) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.dm_relays_not_found_editing),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.dm_relays_not_found_examples),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = FeedPadding,
|
||||||
|
) {
|
||||||
|
itemsIndexed(feedState, key = { _, item -> item.url }) { index, item ->
|
||||||
|
DMServerConfig(
|
||||||
|
item,
|
||||||
|
onDelete = { postViewModel.deleteRelay(item) },
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
) {
|
||||||
|
onClose()
|
||||||
|
nav(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
|
||||||
|
DMEditableServerConfig(relayToAdd) { postViewModel.addRelay(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DMServerConfig(
|
||||||
|
item: DMRelayListViewModel.DMRelaySetupInfo,
|
||||||
|
onDelete: (DMRelayListViewModel.DMRelaySetupInfo) -> Unit,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) }
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
relayInfo?.let {
|
||||||
|
RelayInformationDialog(
|
||||||
|
onClose = { relayInfo = null },
|
||||||
|
relayInfo = it.relayInfo,
|
||||||
|
relayBriefInfo = it.relayBriefInfo,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val automaticallyShowProfilePicture =
|
||||||
|
remember {
|
||||||
|
accountViewModel.settings.showProfilePictures.value
|
||||||
|
}
|
||||||
|
|
||||||
|
DMServerConfigClickableLine(
|
||||||
|
item = item,
|
||||||
|
loadProfilePicture = automaticallyShowProfilePicture,
|
||||||
|
onDelete = onDelete,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
onClick = {
|
||||||
|
accountViewModel.retrieveRelayDocument(
|
||||||
|
item.url,
|
||||||
|
onInfo = { relayInfo = RelayInfoDialog(RelayBriefInfoCache.RelayBriefInfo(item.url), it) },
|
||||||
|
onError = { url, errorCode, exceptionMessage ->
|
||||||
|
val msg =
|
||||||
|
when (errorCode) {
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL ->
|
||||||
|
context.getString(
|
||||||
|
R.string.relay_information_document_error_assemble_url,
|
||||||
|
url,
|
||||||
|
exceptionMessage,
|
||||||
|
)
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER ->
|
||||||
|
context.getString(
|
||||||
|
R.string.relay_information_document_error_assemble_url,
|
||||||
|
url,
|
||||||
|
exceptionMessage,
|
||||||
|
)
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT ->
|
||||||
|
context.getString(
|
||||||
|
R.string.relay_information_document_error_assemble_url,
|
||||||
|
url,
|
||||||
|
exceptionMessage,
|
||||||
|
)
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS ->
|
||||||
|
context.getString(
|
||||||
|
R.string.relay_information_document_error_assemble_url,
|
||||||
|
url,
|
||||||
|
exceptionMessage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
accountViewModel.toast(
|
||||||
|
context.getString(R.string.unable_to_download_relay_document),
|
||||||
|
msg,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DMServerConfigClickableLine(
|
||||||
|
item: DMRelayListViewModel.DMRelaySetupInfo,
|
||||||
|
loadProfilePicture: Boolean,
|
||||||
|
onDelete: (DMRelayListViewModel.DMRelaySetupInfo) -> Unit,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) {
|
||||||
|
Column(Modifier.fillMaxWidth()) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(vertical = 5.dp),
|
||||||
|
) {
|
||||||
|
Column(Modifier.clickable(onClick = onClick)) {
|
||||||
|
val iconUrlFromRelayInfoDoc =
|
||||||
|
remember(item) {
|
||||||
|
Nip11CachedRetriever.getFromCache(item.url)?.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderRelayIcon(
|
||||||
|
item.briefInfo.displayUrl,
|
||||||
|
iconUrlFromRelayInfoDoc ?: item.briefInfo.favIcon,
|
||||||
|
loadProfilePicture,
|
||||||
|
MaterialTheme.colorScheme.largeRelayIconModifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = HalfHorzPadding)
|
||||||
|
|
||||||
|
Column(Modifier.weight(1f)) {
|
||||||
|
FirstLine(item, onClick, onDelete, ReactionRowHeightChat.fillMaxWidth())
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = ReactionRowHeightChat.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
RenderStatusRow(
|
||||||
|
item = item,
|
||||||
|
modifier = HalfStartPadding.weight(1f),
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
private fun RenderStatusRow(
|
||||||
|
item: DMRelayListViewModel.DMRelaySetupInfo,
|
||||||
|
modifier: Modifier,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Download,
|
||||||
|
contentDescription = stringResource(R.string.read_from_relay),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(15.dp)
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = {},
|
||||||
|
onLongClick = {
|
||||||
|
accountViewModel.toast(
|
||||||
|
R.string.read_from_relay,
|
||||||
|
R.string.read_from_relay_description,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
tint = MaterialTheme.colorScheme.allGoodColor,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = countToHumanReadableBytes(item.downloadCountInBytes),
|
||||||
|
maxLines = 1,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
modifier = modifier,
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
)
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Upload,
|
||||||
|
stringResource(R.string.write_to_relay),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(15.dp)
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = { },
|
||||||
|
onLongClick = {
|
||||||
|
accountViewModel.toast(
|
||||||
|
R.string.write_to_relay,
|
||||||
|
R.string.write_to_relay_description,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
tint = MaterialTheme.colorScheme.allGoodColor,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = countToHumanReadableBytes(item.uploadCountInBytes),
|
||||||
|
maxLines = 1,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
modifier = modifier,
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
)
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.SyncProblem,
|
||||||
|
stringResource(R.string.errors),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(15.dp)
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = {},
|
||||||
|
onLongClick = {
|
||||||
|
accountViewModel.toast(
|
||||||
|
R.string.errors,
|
||||||
|
R.string.errors_description,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
tint =
|
||||||
|
if (item.errorCount > 0) {
|
||||||
|
MaterialTheme.colorScheme.warningColor
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.allGoodColor
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = countToHumanReadable(item.errorCount, "errors"),
|
||||||
|
maxLines = 1,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
modifier = modifier,
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
)
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.DeleteSweep,
|
||||||
|
stringResource(R.string.spam),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(15.dp)
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = {},
|
||||||
|
onLongClick = {
|
||||||
|
accountViewModel.toast(
|
||||||
|
R.string.spam,
|
||||||
|
R.string.spam_description,
|
||||||
|
)
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.spam),
|
||||||
|
Toast.LENGTH_SHORT,
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
tint =
|
||||||
|
if (item.spamCount > 0) {
|
||||||
|
MaterialTheme.colorScheme.warningColor
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.allGoodColor
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = countToHumanReadable(item.spamCount, "spam"),
|
||||||
|
maxLines = 1,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
modifier = modifier,
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FirstLine(
|
||||||
|
item: DMRelayListViewModel.DMRelaySetupInfo,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onDelete: (DMRelayListViewModel.DMRelaySetupInfo) -> Unit,
|
||||||
|
modifier: Modifier,
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
|
||||||
|
Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
text = item.briefInfo.displayUrl,
|
||||||
|
modifier = Modifier.clickable(onClick = onClick),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (item.paidRelay) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Paid,
|
||||||
|
null,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(start = 5.dp, top = 1.dp)
|
||||||
|
.size(14.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.allGoodColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.size(30.dp),
|
||||||
|
onClick = { onDelete(item) },
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Cancel,
|
||||||
|
contentDescription = stringResource(id = R.string.remove),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(start = 10.dp)
|
||||||
|
.size(15.dp),
|
||||||
|
tint = WarningColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DMEditableServerConfig(
|
||||||
|
relayToAdd: String,
|
||||||
|
onNewRelay: (DMRelayListViewModel.DMRelaySetupInfo) -> Unit,
|
||||||
|
) {
|
||||||
|
var url by remember { mutableStateOf<String>(relayToAdd) }
|
||||||
|
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(Size10dp)) {
|
||||||
|
OutlinedTextField(
|
||||||
|
label = { Text(text = stringResource(R.string.add_a_relay)) },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
value = url,
|
||||||
|
onValueChange = { url = it },
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = "server.com",
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (url.isNotBlank() && url != "/") {
|
||||||
|
var addedWSS =
|
||||||
|
if (!url.startsWith("wss://") && !url.startsWith("ws://")) {
|
||||||
|
if (url.endsWith(".onion") || url.endsWith(".onion/")) {
|
||||||
|
"ws://$url"
|
||||||
|
} else {
|
||||||
|
"wss://$url"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
if (url.endsWith("/")) addedWSS = addedWSS.dropLast(1)
|
||||||
|
onNewRelay(DMRelayListViewModel.DMRelaySetupInfo(addedWSS))
|
||||||
|
url = ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shape = ButtonBorder,
|
||||||
|
colors =
|
||||||
|
ButtonDefaults.buttonColors(
|
||||||
|
containerColor =
|
||||||
|
if (url.isNotBlank()) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.placeholderText
|
||||||
|
},
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.add), color = Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
|
||||||
|
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class DMRelayListViewModel : ViewModel() {
|
||||||
|
private lateinit var account: Account
|
||||||
|
|
||||||
|
private val _relays = MutableStateFlow<List<DMRelaySetupInfo>>(emptyList())
|
||||||
|
val relays = _relays.asStateFlow()
|
||||||
|
|
||||||
|
fun load(account: Account) {
|
||||||
|
this.account = account
|
||||||
|
clear()
|
||||||
|
loadRelayDocuments()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create() {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.saveDMRelayList(_relays.value.map { it.url })
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadRelayDocuments() {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
_relays.value.forEach { item ->
|
||||||
|
Nip11CachedRetriever.loadRelayInfo(
|
||||||
|
dirtyUrl = item.url,
|
||||||
|
onInfo = {
|
||||||
|
togglePaidRelay(item, it.limitation?.payment_required ?: false)
|
||||||
|
},
|
||||||
|
onError = { url, errorCode, exceptionMessage -> },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class DMRelaySetupInfo(
|
||||||
|
val url: String,
|
||||||
|
val errorCount: Int = 0,
|
||||||
|
val downloadCountInBytes: Int = 0,
|
||||||
|
val uploadCountInBytes: Int = 0,
|
||||||
|
val spamCount: Int = 0,
|
||||||
|
val paidRelay: Boolean = false,
|
||||||
|
) {
|
||||||
|
val briefInfo: RelayBriefInfoCache.RelayBriefInfo = RelayBriefInfoCache.RelayBriefInfo(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
_relays.update {
|
||||||
|
val relayList = account.getDMRelayList()?.relays() ?: emptyList()
|
||||||
|
|
||||||
|
relayList.map { relayUrl ->
|
||||||
|
val liveRelay = RelayPool.getRelay(relayUrl)
|
||||||
|
val errorCounter = liveRelay?.errorCounter ?: 0
|
||||||
|
val eventDownloadCounter = liveRelay?.eventDownloadCounterInBytes ?: 0
|
||||||
|
val eventUploadCounter = liveRelay?.eventUploadCounterInBytes ?: 0
|
||||||
|
val spamCounter = liveRelay?.spamCounter ?: 0
|
||||||
|
|
||||||
|
DMRelaySetupInfo(
|
||||||
|
relayUrl,
|
||||||
|
errorCounter,
|
||||||
|
eventDownloadCounter,
|
||||||
|
eventUploadCounter,
|
||||||
|
spamCounter,
|
||||||
|
)
|
||||||
|
}.distinctBy { it.url }.sortedBy { it.downloadCountInBytes }.reversed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addRelay(relay: DMRelaySetupInfo) {
|
||||||
|
if (relays.value.any { it.url == relay.url }) return
|
||||||
|
|
||||||
|
_relays.update { it.plus(relay) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteRelay(relay: DMRelaySetupInfo) {
|
||||||
|
_relays.update { it.minus(relay) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteAll() {
|
||||||
|
_relays.update { relays -> emptyList() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun togglePaidRelay(
|
||||||
|
relay: DMRelaySetupInfo,
|
||||||
|
paid: Boolean,
|
||||||
|
) {
|
||||||
|
_relays.update { it.updated(relay, relay.copy(paidRelay = paid)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
+71
-60
@@ -20,52 +20,45 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.elements
|
package com.vitorpamplona.amethyst.ui.note.elements
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
|
||||||
import com.vitorpamplona.amethyst.model.ThemeType
|
import com.vitorpamplona.amethyst.model.ThemeType
|
||||||
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
import com.vitorpamplona.amethyst.ui.actions.DMRelayListView
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||||
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.BigPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||||
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
import com.vitorpamplona.quartz.events.ChatMessageRelayListEvent
|
import com.vitorpamplona.quartz.events.ChatMessageRelayListEvent
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
|
||||||
import fr.acinq.secp256k1.Hex
|
import fr.acinq.secp256k1.Hex
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
@@ -89,69 +82,85 @@ fun AddInboxRelayForDMCardPreview() {
|
|||||||
scope = myCoroutineScope,
|
scope = myCoroutineScope,
|
||||||
)
|
)
|
||||||
|
|
||||||
runBlocking(Dispatchers.IO) {
|
|
||||||
val createAt = TimeUtils.now()
|
|
||||||
val list = listOf("wss://inbox.nostr.wine", "wss://vitor.nostr1.com")
|
|
||||||
val tags = ChatMessageRelayListEvent.createTagArray(list)
|
|
||||||
val id = "ab"
|
|
||||||
|
|
||||||
LocalCache.justConsume(
|
|
||||||
ChatMessageRelayListEvent(
|
|
||||||
id = id,
|
|
||||||
pubKey = pubkey,
|
|
||||||
createdAt = createAt,
|
|
||||||
tags = tags,
|
|
||||||
content = "",
|
|
||||||
sig = "",
|
|
||||||
),
|
|
||||||
null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val accountViewModel =
|
val accountViewModel =
|
||||||
AccountViewModel(
|
AccountViewModel(
|
||||||
myAccount,
|
myAccount,
|
||||||
sharedPreferencesViewModel.sharedPrefs,
|
sharedPreferencesViewModel.sharedPrefs,
|
||||||
)
|
)
|
||||||
|
|
||||||
LoadAddressableNote(
|
|
||||||
ChatMessageRelayListEvent.createAddressTag("989c3734c46abac7ce3ce229971581a5a6ee39cdd6aa7261a55823fa7f8c4799"),
|
|
||||||
accountViewModel,
|
|
||||||
) { relayList ->
|
|
||||||
Text("Test" + relayList)
|
|
||||||
if (relayList != null) {
|
|
||||||
ThemeComparisonColumn {
|
ThemeComparisonColumn {
|
||||||
AddInboxRelayForDMCard(
|
AddInboxRelayForDMCard(
|
||||||
relayList,
|
accountViewModel = accountViewModel,
|
||||||
accountViewModel,
|
|
||||||
nav = {},
|
nav = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ObserveRelayListForDMsAndDisplayIfNotFound(
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
ObserveRelayListForDMs(
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
) { relayListEvent ->
|
||||||
|
if (relayListEvent == null) {
|
||||||
|
AddInboxRelayForDMCard(
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ObserveRelayListForDMs(
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
inner: @Composable (relayListEvent: ChatMessageRelayListEvent?) -> Unit,
|
||||||
|
) {
|
||||||
|
ObserveRelayListForDMs(
|
||||||
|
pubkey = accountViewModel.account.userProfile().pubkeyHex,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
) { relayListEvent ->
|
||||||
|
inner(relayListEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ObserveRelayListForDMs(
|
||||||
|
pubkey: HexKey,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
inner: @Composable (relayListEvent: ChatMessageRelayListEvent?) -> Unit,
|
||||||
|
) {
|
||||||
|
println("AABBCC ObserveRelayListForDMs $pubkey")
|
||||||
|
LoadAddressableNote(
|
||||||
|
ChatMessageRelayListEvent.createAddressTag(pubkey),
|
||||||
|
accountViewModel,
|
||||||
|
) { relayList ->
|
||||||
|
if (relayList != null) {
|
||||||
|
val relayListNoteState by relayList.live().metadata.observeAsState()
|
||||||
|
val relayListEvent = relayListNoteState?.note?.event as? ChatMessageRelayListEvent
|
||||||
|
|
||||||
|
println("AABBCC ObserveRelayListForDMs Event $relayListEvent ${relayListNoteState?.note?.idHex}")
|
||||||
|
|
||||||
|
inner(relayListEvent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AddInboxRelayForDMCard(
|
fun AddInboxRelayForDMCard(
|
||||||
baseNote: Note,
|
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val releaseNoteState by baseNote.live().metadata.observeAsState()
|
Column(modifier = StdPadding) {
|
||||||
val releaseNote = releaseNoteState?.note ?: return
|
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(horizontal = Size10dp)) {
|
|
||||||
Card(
|
Card(
|
||||||
modifier = MaterialTheme.colorScheme.imageModifier,
|
modifier = MaterialTheme.colorScheme.imageModifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = BigPadding,
|
||||||
) {
|
) {
|
||||||
// Title
|
// Title
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
) {
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.dm_relays_not_found),
|
text = stringResource(id = R.string.dm_relays_not_found),
|
||||||
style =
|
style =
|
||||||
@@ -161,14 +170,6 @@ fun AddInboxRelayForDMCard(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
IconButton(
|
|
||||||
modifier = Size20Modifier,
|
|
||||||
onClick = { accountViewModel.markDonatedInThisVersion() },
|
|
||||||
) {
|
|
||||||
CloseIcon()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = StdVertSpacer)
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@@ -177,8 +178,20 @@ fun AddInboxRelayForDMCard(
|
|||||||
|
|
||||||
Spacer(modifier = StdVertSpacer)
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.dm_relays_not_found_examples),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
|
||||||
|
var wantsToEditRelays by remember { mutableStateOf(false) }
|
||||||
|
if (wantsToEditRelays) {
|
||||||
|
DMRelayListView({ wantsToEditRelays = false }, accountViewModel, nav = nav)
|
||||||
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
wantsToEditRelays = true
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
@@ -186,7 +199,5 @@ fun AddInboxRelayForDMCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = DoubleVertSpacer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
|
|||||||
import com.vitorpamplona.amethyst.ui.note.QuickActionAlertDialog
|
import com.vitorpamplona.amethyst.ui.note.QuickActionAlertDialog
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMs
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMsAndDisplayIfNotFound
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView
|
import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
@@ -116,6 +118,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
import com.vitorpamplona.quartz.encoders.RelayUrlFormatter
|
||||||
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
import com.vitorpamplona.quartz.events.ChatroomKey
|
||||||
import com.vitorpamplona.quartz.events.findURLs
|
import com.vitorpamplona.quartz.events.findURLs
|
||||||
@@ -295,8 +298,14 @@ fun ChatroomScreen(
|
|||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
val replyTo = remember { mutableStateOf<Note?>(null) }
|
val replyTo = remember { mutableStateOf<Note?>(null) }
|
||||||
|
ObserveRelayListForDMsAndDisplayIfNotFound(accountViewModel, nav)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxHeight().padding(vertical = 0.dp).weight(1f, true),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(vertical = 0.dp)
|
||||||
|
.weight(1f, true),
|
||||||
) {
|
) {
|
||||||
RefreshingChatroomFeedView(
|
RefreshingChatroomFeedView(
|
||||||
viewModel = feedViewModel,
|
viewModel = feedViewModel,
|
||||||
@@ -425,7 +434,10 @@ fun PrivateMessageEditFieldRow(
|
|||||||
UploadFromGallery(
|
UploadFromGallery(
|
||||||
isUploading = channelScreenModel.isUploadingImage,
|
isUploading = channelScreenModel.isUploadingImage,
|
||||||
tint = MaterialTheme.colorScheme.placeholderText,
|
tint = MaterialTheme.colorScheme.placeholderText,
|
||||||
modifier = Modifier.size(30.dp).padding(start = 2.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(30.dp)
|
||||||
|
.padding(start = 2.dp),
|
||||||
) {
|
) {
|
||||||
channelScreenModel.upload(
|
channelScreenModel.upload(
|
||||||
galleryUri = it,
|
galleryUri = it,
|
||||||
@@ -603,7 +615,8 @@ fun ChatroomHeader(
|
|||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.fillMaxWidth()
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
.clickable(
|
.clickable(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
),
|
),
|
||||||
@@ -635,7 +648,10 @@ fun GroupChatroomHeader(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth().clickable(onClick = onClick),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
@@ -669,7 +685,10 @@ private fun EditRoomSubjectButton(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(horizontal = 3.dp).width(50.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(horizontal = 3.dp)
|
||||||
|
.width(50.dp),
|
||||||
onClick = { wantsToPost = true },
|
onClick = { wantsToPost = true },
|
||||||
contentPadding = ZeroPadding,
|
contentPadding = ZeroPadding,
|
||||||
) {
|
) {
|
||||||
@@ -703,7 +722,10 @@ fun NewSubjectView(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(10.dp).verticalScroll(rememberScrollState()),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(10.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -755,7 +777,10 @@ fun NewSubjectView(
|
|||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
label = { Text(text = stringResource(R.string.messages_new_subject_message)) },
|
label = { Text(text = stringResource(R.string.messages_new_subject_message)) },
|
||||||
modifier = Modifier.fillMaxWidth().height(100.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(100.dp),
|
||||||
value = message.value,
|
value = message.value,
|
||||||
onValueChange = { message.value = it },
|
onValueChange = { message.value = it },
|
||||||
placeholder = {
|
placeholder = {
|
||||||
@@ -824,6 +849,31 @@ fun LongRoomHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DMRelayLine(
|
||||||
|
baseUser: User,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) {
|
||||||
|
ObserveRelayListForDMs(pubkey = baseUser.pubkeyHex, accountViewModel = accountViewModel) {
|
||||||
|
val relayList = it?.relays()
|
||||||
|
if (relayList.isNullOrEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.dm_relays_regular),
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.dm_relays_through, relayList.joinToString(", ") { RelayUrlFormatter.displayUrl(it) }),
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomNameOnlyDisplay(
|
fun RoomNameOnlyDisplay(
|
||||||
room: ChatroomKey,
|
room: ChatroomKey,
|
||||||
|
|||||||
@@ -672,7 +672,6 @@
|
|||||||
<string name="invalid_nip19_uri_description">Amethyst a reçu une URI à ouvrir mais cette URI était invalide : %1$s</string>
|
<string name="invalid_nip19_uri_description">Amethyst a reçu une URI à ouvrir mais cette URI était invalide : %1$s</string>
|
||||||
<string name="dm_relays_not_found">Configurer vos relais de messagerie privée</string>
|
<string name="dm_relays_not_found">Configurer vos relais de messagerie privée</string>
|
||||||
<string name="dm_relays_not_found_description">Ce paramètre informe tout le monde les relais à utiliser pour vous envoyer des messages. Sans eux, vous risquez de manquer certains messages.</string>
|
<string name="dm_relays_not_found_description">Ce paramètre informe tout le monde les relais à utiliser pour vous envoyer des messages. Sans eux, vous risquez de manquer certains messages.</string>
|
||||||
<string name="dm_relays_not_found_explanation">Les relais de messagerie DM acceptent n\'importe quel message de n\'importe qui, mais vous permet seulement de les télécharger. Par exemple, inbox.nostr.wine fonctionne de cette façon. </string>
|
|
||||||
<string name="dm_relays_not_found_create_now">Configurer maintenant</string>
|
<string name="dm_relays_not_found_create_now">Configurer maintenant</string>
|
||||||
<string name="zap_the_devs_title">Zap les Devs !</string>
|
<string name="zap_the_devs_title">Zap les Devs !</string>
|
||||||
<string name="zap_the_devs_description">Votre don nous aide à faire la différence. Chaque sat compte !</string>
|
<string name="zap_the_devs_description">Votre don nous aide à faire la différence. Chaque sat compte !</string>
|
||||||
|
|||||||
@@ -671,7 +671,6 @@
|
|||||||
<string name="invalid_nip19_uri_description">Az Amethyst kapott egy URI-t a megnyitáshoz, de az érvénytelen volt: %1$s</string>
|
<string name="invalid_nip19_uri_description">Az Amethyst kapott egy URI-t a megnyitáshoz, de az érvénytelen volt: %1$s</string>
|
||||||
<string name="dm_relays_not_found">Állítsd be a Privát postafiókod közvetítőit</string>
|
<string name="dm_relays_not_found">Állítsd be a Privát postafiókod közvetítőit</string>
|
||||||
<string name="dm_relays_not_found_description">Ez a beállítás mindenkit tájékoztat, hogy melyik közvetítőt használod, amikor üzeneteket küldenek neked. Nélkülük néhány üzenetről lemaradhatsz.</string>
|
<string name="dm_relays_not_found_description">Ez a beállítás mindenkit tájékoztat, hogy melyik közvetítőt használod, amikor üzeneteket küldenek neked. Nélkülük néhány üzenetről lemaradhatsz.</string>
|
||||||
<string name="dm_relays_not_found_explanation">A DM Inbox csomópontok bárkitől bármilyen üzenetet elfogadnak, de csak a letöltésüket teszik lehetővé. Például az inbox.nostr.wine így működik. </string>
|
|
||||||
<string name="dm_relays_not_found_create_now">Állítsd be most</string>
|
<string name="dm_relays_not_found_create_now">Állítsd be most</string>
|
||||||
<string name="zap_the_devs_title">Zap a fejlesztőknek!</string>
|
<string name="zap_the_devs_title">Zap a fejlesztőknek!</string>
|
||||||
<string name="zap_the_devs_description">Az adományod hozzájárul ahhoz, hogy változást érjünk el. Minden sat számít!</string>
|
<string name="zap_the_devs_description">Az adományod hozzájárul ahhoz, hogy változást érjünk el. Minden sat számít!</string>
|
||||||
|
|||||||
@@ -672,7 +672,6 @@
|
|||||||
<string name="invalid_nip19_uri_description">Amethist ontving een URI om te openen, maar die uri was ongeldig: %1$s</string>
|
<string name="invalid_nip19_uri_description">Amethist ontving een URI om te openen, maar die uri was ongeldig: %1$s</string>
|
||||||
<string name="dm_relays_not_found">Stel uw Privé Postvak relays in</string>
|
<string name="dm_relays_not_found">Stel uw Privé Postvak relays in</string>
|
||||||
<string name="dm_relays_not_found_description">Deze instelling geeft iedereen informatie over de relays die gebruikt worden bij het verzenden van berichten. Zonder hen mis je mogelijk een bericht.</string>
|
<string name="dm_relays_not_found_description">Deze instelling geeft iedereen informatie over de relays die gebruikt worden bij het verzenden van berichten. Zonder hen mis je mogelijk een bericht.</string>
|
||||||
<string name="dm_relays_not_found_explanation">DM postvak relays accepteren berichten van iedereen, maar kunnen ze alleen downloaden. Bijvoorbeeld, inbox.nostr.wine werkt op deze manier. </string>
|
|
||||||
<string name="dm_relays_not_found_create_now">Nu instellen</string>
|
<string name="dm_relays_not_found_create_now">Nu instellen</string>
|
||||||
<string name="zap_the_devs_title">Zap de devs!</string>
|
<string name="zap_the_devs_title">Zap de devs!</string>
|
||||||
<string name="zap_the_devs_description">Jouw donatie helpt ons om een verschil te maken. Elke sat telt!</string>
|
<string name="zap_the_devs_description">Jouw donatie helpt ons om een verschil te maken. Elke sat telt!</string>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
<string name="group_picture">Group Picture</string>
|
<string name="group_picture">Group Picture</string>
|
||||||
<string name="explicit_content">Explicit Content</string>
|
<string name="explicit_content">Explicit Content</string>
|
||||||
<string name="spam">Spam</string>
|
<string name="spam">Spam</string>
|
||||||
|
<string name="spam_description">The number of spamming events coming from this relay</string>
|
||||||
<string name="impersonation">Impersonation</string>
|
<string name="impersonation">Impersonation</string>
|
||||||
<string name="illegal_behavior">Illegal Behavior</string>
|
<string name="illegal_behavior">Illegal Behavior</string>
|
||||||
<string name="other">Other</string>
|
<string name="other">Other</string>
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
<string name="posts">Posts</string>
|
<string name="posts">Posts</string>
|
||||||
<string name="bytes">Bytes</string>
|
<string name="bytes">Bytes</string>
|
||||||
<string name="errors">Errors</string>
|
<string name="errors">Errors</string>
|
||||||
|
<string name="errors_description">The number of connection errors in this session</string>
|
||||||
<string name="home_feed">Home Feed</string>
|
<string name="home_feed">Home Feed</string>
|
||||||
<string name="private_message_feed">Private Message Feed</string>
|
<string name="private_message_feed">Private Message Feed</string>
|
||||||
<string name="public_chat_feed">Public Chat Feed</string>
|
<string name="public_chat_feed">Public Chat Feed</string>
|
||||||
@@ -446,6 +448,8 @@
|
|||||||
<string name="sats_to_complete">Zapraiser at %1$s. %2$s sats to goal</string>
|
<string name="sats_to_complete">Zapraiser at %1$s. %2$s sats to goal</string>
|
||||||
<string name="read_from_relay">Read from Relay</string>
|
<string name="read_from_relay">Read from Relay</string>
|
||||||
<string name="write_to_relay">Write to Relay</string>
|
<string name="write_to_relay">Write to Relay</string>
|
||||||
|
<string name="write_to_relay_description">The amount in bytes that was sent to this relay, including filters and events</string>
|
||||||
|
<string name="read_from_relay_description">The amount in bytes that was received from this relay, including filters and events</string>
|
||||||
<string name="an_error_occurred_trying_to_get_relay_information">An error occurred trying to get relay information from %1$s</string>
|
<string name="an_error_occurred_trying_to_get_relay_information">An error occurred trying to get relay information from %1$s</string>
|
||||||
<string name="owner">Owner</string>
|
<string name="owner">Owner</string>
|
||||||
<string name="version">Version</string>
|
<string name="version">Version</string>
|
||||||
@@ -799,9 +803,13 @@
|
|||||||
<string name="invalid_nip19_uri">Invalid address</string>
|
<string name="invalid_nip19_uri">Invalid address</string>
|
||||||
<string name="invalid_nip19_uri_description">Amethyst received a URI to open but that uri was invalid: %1$s</string>
|
<string name="invalid_nip19_uri_description">Amethyst received a URI to open but that uri was invalid: %1$s</string>
|
||||||
|
|
||||||
|
<string name="dm_relays_title">DM Inbox Relays</string>
|
||||||
|
<string name="dm_relays_through">Relays: %1$s</string>
|
||||||
|
<string name="dm_relays_regular">Using Regular Relays</string>
|
||||||
<string name="dm_relays_not_found">Set up your Private Inbox relays</string>
|
<string name="dm_relays_not_found">Set up your Private Inbox relays</string>
|
||||||
<string name="dm_relays_not_found_description">This setting informs everybody which relays to use when sending messages to you. Without them you might miss some messages.</string>
|
<string name="dm_relays_not_found_description">This setting lets everybody know which relays to use when sending messages to you. Without them you might miss some messages.</string>
|
||||||
<string name="dm_relays_not_found_explanation">DM Inbox relays accept any message from anyone, but only allows you to download them. For example, inbox.nostr.wine operates in this way. </string>
|
<string name="dm_relays_not_found_examples">Good options are:\n - inbox.nostr.wine (paid)\n - you.nostr1.com (personal relays - paid)</string>
|
||||||
|
<string name="dm_relays_not_found_editing">Insert between 1-3 relays to serve as your private inbox. DM Inbox relays should accept any message from anyone, but only allow you to download them.</string>
|
||||||
<string name="dm_relays_not_found_create_now">Set up now</string>
|
<string name="dm_relays_not_found_create_now">Set up now</string>
|
||||||
|
|
||||||
<string name="zap_the_devs_title">Zap the Devs!</string>
|
<string name="zap_the_devs_title">Zap the Devs!</string>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
package com.vitorpamplona.quartz.events
|
package com.vitorpamplona.quartz.events
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.encoders.ATag
|
||||||
import com.vitorpamplona.quartz.encoders.HexKey
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
@@ -57,6 +58,10 @@ class AdvertisedRelayListEvent(
|
|||||||
const val KIND = 10002
|
const val KIND = 10002
|
||||||
const val FIXED_D_TAG = ""
|
const val FIXED_D_TAG = ""
|
||||||
|
|
||||||
|
fun createAddressTag(pubKey: HexKey): ATag {
|
||||||
|
return ATag(KIND, pubKey, FIXED_D_TAG, null)
|
||||||
|
}
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
list: List<AdvertisedRelayInfo>,
|
list: List<AdvertisedRelayInfo>,
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ class ChatMessageRelayListEvent(
|
|||||||
const val KIND = 10050
|
const val KIND = 10050
|
||||||
const val FIXED_D_TAG = ""
|
const val FIXED_D_TAG = ""
|
||||||
|
|
||||||
|
fun createAddressATag(pubKey: HexKey): ATag {
|
||||||
|
return ATag(KIND, pubKey, FIXED_D_TAG, null)
|
||||||
|
}
|
||||||
|
|
||||||
fun createAddressTag(pubKey: HexKey): String {
|
fun createAddressTag(pubKey: HexKey): String {
|
||||||
return ATag.assembleATag(KIND, pubKey, FIXED_D_TAG)
|
return ATag.assembleATag(KIND, pubKey, FIXED_D_TAG)
|
||||||
}
|
}
|
||||||
@@ -58,7 +62,33 @@ class ChatMessageRelayListEvent(
|
|||||||
fun createTagArray(relays: List<String>): Array<Array<String>> {
|
fun createTagArray(relays: List<String>): Array<Array<String>> {
|
||||||
return relays.map {
|
return relays.map {
|
||||||
arrayOf("relay", it)
|
arrayOf("relay", it)
|
||||||
}.plusElement(arrayOf("alt", "Relay list for private messages")).toTypedArray()
|
}.plusElement(arrayOf("alt", "Relay list to receive private messages")).toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateRelayList(
|
||||||
|
earlierVersion: ChatMessageRelayListEvent,
|
||||||
|
relays: List<String>,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
onReady: (ChatMessageRelayListEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
val tags =
|
||||||
|
earlierVersion.tags.filter { it[0] != "relay" }.plus(
|
||||||
|
relays.map {
|
||||||
|
arrayOf("relay", it)
|
||||||
|
},
|
||||||
|
).toTypedArray()
|
||||||
|
|
||||||
|
signer.sign(createdAt, KIND, tags, earlierVersion.content, onReady)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createFromScratch(
|
||||||
|
relays: List<String>,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
onReady: (ChatMessageRelayListEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
create(relays, signer, createdAt, onReady)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
|
|||||||
Reference in New Issue
Block a user