mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-09 04:18:11 +02:00
Adds a card to setup the DM relay list if the user doesn't have one.
This commit is contained in:
parent
76103ac057
commit
599fddb369
@ -52,6 +52,7 @@ import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.events.Contact
|
||||
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() {
|
||||
hideDeleteRequestDialog = true
|
||||
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)) }
|
||||
}
|
||||
}
|
@ -20,52 +20,45 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.elements
|
||||
|
||||
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.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
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.ui.note.CloseIcon
|
||||
import com.vitorpamplona.amethyst.ui.actions.DMRelayListView
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.BigPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.events.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import fr.acinq.secp256k1.Hex
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
@ -89,85 +82,93 @@ fun AddInboxRelayForDMCardPreview() {
|
||||
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 =
|
||||
AccountViewModel(
|
||||
myAccount,
|
||||
sharedPreferencesViewModel.sharedPrefs,
|
||||
)
|
||||
|
||||
ThemeComparisonColumn {
|
||||
AddInboxRelayForDMCard(
|
||||
accountViewModel = accountViewModel,
|
||||
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("989c3734c46abac7ce3ce229971581a5a6ee39cdd6aa7261a55823fa7f8c4799"),
|
||||
ChatMessageRelayListEvent.createAddressTag(pubkey),
|
||||
accountViewModel,
|
||||
) { relayList ->
|
||||
Text("Test" + relayList)
|
||||
if (relayList != null) {
|
||||
ThemeComparisonColumn {
|
||||
AddInboxRelayForDMCard(
|
||||
relayList,
|
||||
accountViewModel,
|
||||
nav = {},
|
||||
)
|
||||
}
|
||||
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
|
||||
fun AddInboxRelayForDMCard(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
val releaseNoteState by baseNote.live().metadata.observeAsState()
|
||||
val releaseNote = releaseNoteState?.note ?: return
|
||||
|
||||
Column(modifier = Modifier.padding(horizontal = Size10dp)) {
|
||||
Column(modifier = StdPadding) {
|
||||
Card(
|
||||
modifier = MaterialTheme.colorScheme.imageModifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
modifier = BigPadding,
|
||||
) {
|
||||
// Title
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.dm_relays_not_found),
|
||||
style =
|
||||
TextStyle(
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
),
|
||||
)
|
||||
|
||||
IconButton(
|
||||
modifier = Size20Modifier,
|
||||
onClick = { accountViewModel.markDonatedInThisVersion() },
|
||||
) {
|
||||
CloseIcon()
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = R.string.dm_relays_not_found),
|
||||
style =
|
||||
TextStyle(
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
),
|
||||
)
|
||||
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
|
||||
@ -177,8 +178,20 @@ fun AddInboxRelayForDMCard(
|
||||
|
||||
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(
|
||||
onClick = {
|
||||
wantsToEditRelays = true
|
||||
},
|
||||
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.UserCompose
|
||||
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.RefreshingChatroomFeedView
|
||||
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.ZeroPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.encoders.RelayUrlFormatter
|
||||
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
||||
import com.vitorpamplona.quartz.events.findURLs
|
||||
@ -295,8 +298,14 @@ fun ChatroomScreen(
|
||||
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
val replyTo = remember { mutableStateOf<Note?>(null) }
|
||||
ObserveRelayListForDMsAndDisplayIfNotFound(accountViewModel, nav)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxHeight().padding(vertical = 0.dp).weight(1f, true),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(vertical = 0.dp)
|
||||
.weight(1f, true),
|
||||
) {
|
||||
RefreshingChatroomFeedView(
|
||||
viewModel = feedViewModel,
|
||||
@ -425,7 +434,10 @@ fun PrivateMessageEditFieldRow(
|
||||
UploadFromGallery(
|
||||
isUploading = channelScreenModel.isUploadingImage,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
modifier = Modifier.size(30.dp).padding(start = 2.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(30.dp)
|
||||
.padding(start = 2.dp),
|
||||
) {
|
||||
channelScreenModel.upload(
|
||||
galleryUri = it,
|
||||
@ -603,7 +615,8 @@ fun ChatroomHeader(
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
onClick = onClick,
|
||||
),
|
||||
@ -635,7 +648,10 @@ fun GroupChatroomHeader(
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().clickable(onClick = onClick),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick),
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
@ -669,7 +685,10 @@ private fun EditRoomSubjectButton(
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp).width(50.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = { wantsToPost = true },
|
||||
contentPadding = ZeroPadding,
|
||||
) {
|
||||
@ -703,7 +722,10 @@ fun NewSubjectView(
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp).verticalScroll(rememberScrollState()),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(10.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@ -755,7 +777,10 @@ fun NewSubjectView(
|
||||
|
||||
OutlinedTextField(
|
||||
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,
|
||||
onValueChange = { message.value = it },
|
||||
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
|
||||
fun RoomNameOnlyDisplay(
|
||||
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="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_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="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>
|
||||
|
@ -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="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_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="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>
|
||||
|
@ -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="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_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="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>
|
||||
|
@ -16,6 +16,7 @@
|
||||
<string name="group_picture">Group Picture</string>
|
||||
<string name="explicit_content">Explicit Content</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="illegal_behavior">Illegal Behavior</string>
|
||||
<string name="other">Other</string>
|
||||
@ -96,6 +97,7 @@
|
||||
<string name="posts">Posts</string>
|
||||
<string name="bytes">Bytes</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="private_message_feed">Private Message 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="read_from_relay">Read from 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="owner">Owner</string>
|
||||
<string name="version">Version</string>
|
||||
@ -799,9 +803,13 @@
|
||||
<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="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_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_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_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_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="zap_the_devs_title">Zap the Devs!</string>
|
||||
|
@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
@ -57,6 +58,10 @@ class AdvertisedRelayListEvent(
|
||||
const val KIND = 10002
|
||||
const val FIXED_D_TAG = ""
|
||||
|
||||
fun createAddressTag(pubKey: HexKey): ATag {
|
||||
return ATag(KIND, pubKey, FIXED_D_TAG, null)
|
||||
}
|
||||
|
||||
fun create(
|
||||
list: List<AdvertisedRelayInfo>,
|
||||
signer: NostrSigner,
|
||||
|
@ -51,6 +51,10 @@ class ChatMessageRelayListEvent(
|
||||
const val KIND = 10050
|
||||
const val FIXED_D_TAG = ""
|
||||
|
||||
fun createAddressATag(pubKey: HexKey): ATag {
|
||||
return ATag(KIND, pubKey, FIXED_D_TAG, null)
|
||||
}
|
||||
|
||||
fun createAddressTag(pubKey: HexKey): String {
|
||||
return ATag.assembleATag(KIND, pubKey, FIXED_D_TAG)
|
||||
}
|
||||
@ -58,7 +62,33 @@ class ChatMessageRelayListEvent(
|
||||
fun createTagArray(relays: List<String>): Array<Array<String>> {
|
||||
return relays.map {
|
||||
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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user