Merge branch 'main-upstream' into claude/strip-file-metadata-sVIEd
This commit is contained in:
+13
-5
@@ -26,14 +26,16 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMs
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMsAndDisplayIfNotFound
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.RefreshingChatroomFeedView
|
||||
@@ -44,6 +46,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.Privat
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
@@ -92,10 +95,15 @@ fun ChatroomView(
|
||||
}
|
||||
}
|
||||
|
||||
if (room.users.size == 1) {
|
||||
// Activates NIP-17 if the user has DM relays
|
||||
ObserveRelayListForDMs(pubkey = room.users.first(), accountViewModel = accountViewModel) {
|
||||
newPostModel.nip17 = !it?.relays().isNullOrEmpty()
|
||||
// Reactively check if recipients have DM relays for NIP-17 delivery
|
||||
for (userHex in room.users) {
|
||||
LoadAddressableNote(
|
||||
ChatMessageRelayListEvent.createAddress(userHex),
|
||||
accountViewModel,
|
||||
) { note ->
|
||||
if (note != null) {
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+141
-150
@@ -74,6 +74,7 @@ import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.NIP17Group
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.quotes
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
|
||||
@@ -89,16 +90,29 @@ import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplitSetup
|
||||
import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplits
|
||||
import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiser
|
||||
import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiserAmount
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip92IMeta.imetas
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
@@ -127,9 +141,57 @@ class ChatNewMessageViewModel :
|
||||
}
|
||||
}
|
||||
|
||||
var room: ChatroomKey? by mutableStateOf(null)
|
||||
val room = MutableStateFlow<ChatroomKey?>(null)
|
||||
|
||||
var requiresNIP17: Boolean = false
|
||||
val roomUsers: StateFlow<List<User>> =
|
||||
room
|
||||
.mapNotNull {
|
||||
it?.users?.mapNotNull { userHex -> LocalCache.checkGetOrCreateUser(userHex) } ?: emptyList()
|
||||
}.flowOn(Dispatchers.IO)
|
||||
.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.Eagerly,
|
||||
room.value?.users?.mapNotNull { userHex -> LocalCache.checkGetOrCreateUser(userHex) } ?: emptyList(),
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val recipientsMissingDmRelays: StateFlow<ImmutableList<User>> =
|
||||
roomUsers
|
||||
.transformLatest {
|
||||
val dmRelayListNoteFlows =
|
||||
it.map { user ->
|
||||
user.dmRelayListNote
|
||||
.flow()
|
||||
.metadata.stateFlow
|
||||
}
|
||||
|
||||
if (dmRelayListNoteFlows.isEmpty()) {
|
||||
emitAll(MutableStateFlow(persistentListOf()))
|
||||
} else {
|
||||
val flow =
|
||||
combine(dmRelayListNoteFlows) { dmRelayListNotes ->
|
||||
dmRelayListNotes
|
||||
.mapNotNull { noteState ->
|
||||
val noteEvent = noteState.note.event as? ChatMessageRelayListEvent
|
||||
if (noteEvent == null || noteEvent.relays().isEmpty()) {
|
||||
noteState.note.author
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
emitAll(flow)
|
||||
}
|
||||
}.onStart {
|
||||
}.onCompletion {
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.Eagerly,
|
||||
roomUsers.value
|
||||
.filter { user ->
|
||||
user.dmInboxRelays().isNullOrEmpty()
|
||||
}.toImmutableList(),
|
||||
)
|
||||
|
||||
val replyTo = mutableStateOf<Note?>(null)
|
||||
|
||||
@@ -185,9 +247,6 @@ class ChatNewMessageViewModel :
|
||||
var wantsZapraiser by mutableStateOf(false)
|
||||
override var zapRaiserAmount = mutableStateOf<Long?>(null)
|
||||
|
||||
// NIP17 Wrapped DMs / Group messages
|
||||
var nip17 by mutableStateOf(false)
|
||||
|
||||
fun lnAddress(): String? = account.userProfile().lnAddress()
|
||||
|
||||
fun hasLnAddress(): Boolean = account.userProfile().lnAddress() != null
|
||||
@@ -214,26 +273,11 @@ class ChatNewMessageViewModel :
|
||||
}
|
||||
|
||||
fun load(room: ChatroomKey) {
|
||||
this.room = room
|
||||
this.room.tryEmit(room)
|
||||
this.toUsers =
|
||||
TextFieldValue(
|
||||
room.users.mapNotNull { runCatching { Hex.decode(it).toNpub() }.getOrNull() }.joinToString(", ") { "@$it" },
|
||||
)
|
||||
|
||||
updateNIP17StatusFromRoom()
|
||||
}
|
||||
|
||||
fun updateNIP17StatusFromRoom() {
|
||||
val room = this.room
|
||||
if (room != null) {
|
||||
this.requiresNIP17 = room.users.size > 1
|
||||
if (this.requiresNIP17) {
|
||||
this.nip17 = true
|
||||
}
|
||||
} else {
|
||||
this.requiresNIP17 = false
|
||||
this.nip17 = false
|
||||
}
|
||||
}
|
||||
|
||||
fun reply(replyNote: Note) {
|
||||
@@ -366,9 +410,6 @@ class ChatNewMessageViewModel :
|
||||
urlPreviews.update(message)
|
||||
|
||||
iMetaAttachments.addAll(draftEvent.imetas())
|
||||
|
||||
requiresNIP17 = draftEvent is NIP17Group
|
||||
nip17 = draftEvent is NIP17Group
|
||||
}
|
||||
|
||||
suspend fun sendPostSync() {
|
||||
@@ -408,28 +449,20 @@ class ChatNewMessageViewModel :
|
||||
val uploadState = uploadState ?: return
|
||||
|
||||
accountViewModel.launchSigner {
|
||||
if (nip17) {
|
||||
ChatFileUploader(account).justUploadNIP17(
|
||||
uploadState,
|
||||
onError,
|
||||
onEncryptedUploadError = { title, message ->
|
||||
encryptedUploadErrorTitle = title
|
||||
encryptedUploadErrorMessage = message
|
||||
pendingRetryMode = RetryMode.HOLD
|
||||
},
|
||||
context,
|
||||
onStrippingFailed = strippingFailureConfirmation::awaitConfirmation,
|
||||
) {
|
||||
uploadsWaitingToBeSent += it
|
||||
draftTag.newVersion()
|
||||
onceUploaded()
|
||||
}
|
||||
} else {
|
||||
ChatFileUploader(account).justUploadNIP04(uploadState, onError, context, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation) {
|
||||
uploadsWaitingToBeSent += it
|
||||
draftTag.newVersion()
|
||||
onceUploaded()
|
||||
}
|
||||
ChatFileUploader(account).justUploadNIP17(
|
||||
uploadState,
|
||||
onError,
|
||||
onEncryptedUploadError = { title, message ->
|
||||
encryptedUploadErrorTitle = title
|
||||
encryptedUploadErrorMessage = message
|
||||
pendingRetryMode = RetryMode.HOLD
|
||||
},
|
||||
context,
|
||||
onStrippingFailed = strippingFailureConfirmation::awaitConfirmation,
|
||||
) {
|
||||
uploadsWaitingToBeSent += it
|
||||
draftTag.newVersion()
|
||||
onceUploaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -439,35 +472,27 @@ class ChatNewMessageViewModel :
|
||||
context: Context,
|
||||
onceUploaded: () -> Unit,
|
||||
) {
|
||||
val room = room ?: return
|
||||
val room = room.value ?: return
|
||||
val uploadState = uploadState ?: return
|
||||
|
||||
accountViewModel.launchSigner {
|
||||
if (nip17) {
|
||||
ChatFileUploader(account).justUploadNIP17(
|
||||
uploadState,
|
||||
onError,
|
||||
onEncryptedUploadError = { title, message ->
|
||||
encryptedUploadErrorTitle = title
|
||||
encryptedUploadErrorMessage = message
|
||||
pendingRetryMode = RetryMode.SEND
|
||||
pendingRetryOnError = onError
|
||||
pendingRetryContext = context
|
||||
pendingRetryOnceUploaded = onceUploaded
|
||||
},
|
||||
context,
|
||||
onStrippingFailed = strippingFailureConfirmation::awaitConfirmation,
|
||||
) {
|
||||
ChatFileSender(room, account).sendNIP17(it)
|
||||
draftTag.newVersion()
|
||||
onceUploaded()
|
||||
}
|
||||
} else {
|
||||
ChatFileUploader(account).justUploadNIP04(uploadState, onError, context, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation) {
|
||||
ChatFileSender(room, account).sendNIP04(it)
|
||||
draftTag.newVersion()
|
||||
onceUploaded()
|
||||
}
|
||||
ChatFileUploader(account).justUploadNIP17(
|
||||
uploadState,
|
||||
onError,
|
||||
onEncryptedUploadError = { title, message ->
|
||||
encryptedUploadErrorTitle = title
|
||||
encryptedUploadErrorMessage = message
|
||||
pendingRetryMode = RetryMode.SEND
|
||||
pendingRetryOnError = onError
|
||||
pendingRetryContext = context
|
||||
pendingRetryOnceUploaded = onceUploaded
|
||||
},
|
||||
context,
|
||||
onStrippingFailed = strippingFailureConfirmation::awaitConfirmation,
|
||||
) {
|
||||
ChatFileSender(room, account).sendNIP17(it)
|
||||
draftTag.newVersion()
|
||||
onceUploaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,12 +521,12 @@ class ChatNewMessageViewModel :
|
||||
val onError = pendingRetryOnError
|
||||
val context = pendingRetryContext
|
||||
val onceUploaded = pendingRetryOnceUploaded
|
||||
val room = room
|
||||
val room = room.value
|
||||
val uploadState = uploadState
|
||||
|
||||
dismissEncryptedUploadError()
|
||||
|
||||
if (uploadState == null || context == null) return
|
||||
if (room == null || uploadState == null || context == null) return
|
||||
|
||||
uploadState.encryptFiles = false
|
||||
|
||||
@@ -521,7 +546,6 @@ class ChatNewMessageViewModel :
|
||||
}
|
||||
|
||||
RetryMode.SEND -> {
|
||||
if (room == null) return@launchSigner
|
||||
ChatFileUploader(account).justUploadNIP17Unencrypted(
|
||||
uploadState,
|
||||
onError ?: accountViewModel.toastManager::toast,
|
||||
@@ -538,7 +562,7 @@ class ChatNewMessageViewModel :
|
||||
}
|
||||
|
||||
private suspend fun innerSendPost(draftTag: String?) {
|
||||
val room = room ?: return
|
||||
val room = room.value ?: return
|
||||
|
||||
val urls = findURLs(message.text)
|
||||
val usedAttachments = iMetaAttachments.filterIsIn(urls.toSet())
|
||||
@@ -551,70 +575,49 @@ class ChatNewMessageViewModel :
|
||||
val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount.value else null
|
||||
val zapReceiver = if (wantsForwardZapTo) forwardZapTo.value.toZapSplitSetup() else null
|
||||
|
||||
if (nip17 || room.users.size > 1 || replyTo.value?.event is NIP17Group) {
|
||||
val replyHint = replyTo.value?.toEventHint<BaseDMGroupEvent>()
|
||||
val replyHint = replyTo.value?.toEventHint<BaseDMGroupEvent>()
|
||||
|
||||
val template =
|
||||
if (replyHint == null) {
|
||||
ChatMessageEvent.build(message, room.users.map { LocalCache.getOrCreateUser(it).toPTag() }) {
|
||||
hashtags(findHashtags(message))
|
||||
references(findURLs(message))
|
||||
quotes(findNostrEventUris(message))
|
||||
val template =
|
||||
if (replyHint == null) {
|
||||
ChatMessageEvent.build(message, room.users.map { LocalCache.getOrCreateUser(it).toPTag() }) {
|
||||
hashtags(findHashtags(message))
|
||||
references(findURLs(message))
|
||||
quotes(findNostrEventUris(message))
|
||||
|
||||
geoHash?.let { geohash(it) }
|
||||
localZapRaiserAmount?.let { zapraiser(it) }
|
||||
zapReceiver?.let { zapSplits(it) }
|
||||
contentWarningReason?.let { contentWarning(it) }
|
||||
localExpirationDate?.let { expiration(it) }
|
||||
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
} else {
|
||||
ChatMessageEvent.reply(message, replyHint) {
|
||||
hashtags(findHashtags(message))
|
||||
references(findURLs(message))
|
||||
quotes(findNostrEventUris(message))
|
||||
|
||||
geoHash?.let { geohash(it) }
|
||||
localZapRaiserAmount?.let { zapraiser(it) }
|
||||
zapReceiver?.let { zapSplits(it) }
|
||||
contentWarningReason?.let { contentWarning(it) }
|
||||
localExpirationDate?.let { expiration(it) }
|
||||
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
}
|
||||
|
||||
if (draftTag != null) {
|
||||
accountViewModel.account.createAndSendDraftIgnoreErrors(draftTag, template)
|
||||
} else {
|
||||
accountViewModel.account.sendNip17PrivateMessage(template)
|
||||
}
|
||||
} else {
|
||||
val toUser = room.users.first().let { LocalCache.getOrCreateUser(it).toPTag() }
|
||||
|
||||
val template =
|
||||
PrivateDmEvent.build(
|
||||
toUser = toUser,
|
||||
message = message,
|
||||
imetas = usedAttachments,
|
||||
replyingTo = replyTo.value?.toEventHint<PrivateDmEvent>(),
|
||||
signer = accountViewModel.account.signer,
|
||||
) {
|
||||
geoHash?.let { geohash(it) }
|
||||
localZapRaiserAmount?.let { zapraiser(it) }
|
||||
zapReceiver?.let { zapSplits(it) }
|
||||
contentWarningReason?.let { contentWarning(it) }
|
||||
localExpirationDate?.let { expiration(it) }
|
||||
}
|
||||
|
||||
if (draftTag != null) {
|
||||
accountViewModel.account.createAndSendDraftIgnoreErrors(draftTag, template)
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
} else {
|
||||
accountViewModel.account.sendNip04PrivateMessage(template)
|
||||
ChatMessageEvent.reply(message, replyHint) {
|
||||
hashtags(findHashtags(message))
|
||||
references(findURLs(message))
|
||||
quotes(findNostrEventUris(message))
|
||||
|
||||
geoHash?.let { geohash(it) }
|
||||
localZapRaiserAmount?.let { zapraiser(it) }
|
||||
zapReceiver?.let { zapSplits(it) }
|
||||
contentWarningReason?.let { contentWarning(it) }
|
||||
localExpirationDate?.let { expiration(it) }
|
||||
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
}
|
||||
|
||||
if (draftTag != null) {
|
||||
accountViewModel.account.createAndSendDraftIgnoreErrors(draftTag, template)
|
||||
} else {
|
||||
accountViewModel.account.sendNip17PrivateMessage(template)
|
||||
}
|
||||
|
||||
if (draftTag == null) {
|
||||
ChatFileSender(room, accountViewModel.account).sendAll(uploadsWaitingToBeSent)
|
||||
ChatFileSender(room, accountViewModel.account).sendNIP17(uploadsWaitingToBeSent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,12 +712,10 @@ class ChatNewMessageViewModel :
|
||||
|
||||
val users = toUsersTagger.pTags?.mapTo(mutableSetOf()) { it.pubkeyHex }
|
||||
if (users.isNullOrEmpty()) {
|
||||
room = null
|
||||
updateNIP17StatusFromRoom()
|
||||
room.emit(null)
|
||||
} else {
|
||||
if (users != room?.users) {
|
||||
room = ChatroomKey(users)
|
||||
updateNIP17StatusFromRoom()
|
||||
if (users != room.value?.users) {
|
||||
room.emit(ChatroomKey(users))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -747,9 +748,6 @@ class ChatNewMessageViewModel :
|
||||
val lastWord = toUsers.currentWord()
|
||||
toUsers = userSuggestions.replaceCurrentWord(toUsers, lastWord, item)
|
||||
updateRoomFromUsersInput()
|
||||
|
||||
val relayList = (LocalCache.getAddressableNoteIfExists(AdvertisedRelayListEvent.createAddressTag(item.pubkeyHex))?.event as? AdvertisedRelayListEvent)?.readRelaysNorm()
|
||||
nip17 = relayList != null
|
||||
}
|
||||
|
||||
userSuggestionsMainMessage = null
|
||||
@@ -793,7 +791,8 @@ class ChatNewMessageViewModel :
|
||||
!wantsInvoice &&
|
||||
(!wantsZapraiser || zapRaiserAmount.value != null) &&
|
||||
(toUsers.text.isNotBlank()) &&
|
||||
uploadState?.multiOrchestrator == null
|
||||
uploadState?.multiOrchestrator == null &&
|
||||
recipientsMissingDmRelays.value.isEmpty()
|
||||
|
||||
fun insertAtCursor(newElement: String) {
|
||||
message = message.insertUrlAtCursor(newElement)
|
||||
@@ -805,15 +804,7 @@ class ChatNewMessageViewModel :
|
||||
Log.d("Init", "OnCleared: ${this.javaClass.simpleName}")
|
||||
}
|
||||
|
||||
fun toggleNIP04And24() {
|
||||
if (requiresNIP17) {
|
||||
nip17 = true
|
||||
} else {
|
||||
nip17 = !nip17
|
||||
}
|
||||
|
||||
draftTag.newVersion()
|
||||
}
|
||||
// NIP-04 sending is deprecated. NIP-17 is always used.
|
||||
|
||||
override fun updateZapPercentage(
|
||||
index: Int,
|
||||
|
||||
+11
-6
@@ -197,7 +197,7 @@ fun NewGroupDMScreen(
|
||||
// function when the postViewModel is released
|
||||
accountViewModel.launchSigner {
|
||||
postViewModel.sendPostSync()
|
||||
postViewModel.room?.let {
|
||||
postViewModel.room.value?.let {
|
||||
nav.nav(routeToMessage(it, null, null, null, null, accountViewModel))
|
||||
}
|
||||
}
|
||||
@@ -332,6 +332,11 @@ fun GroupDMScreenContent(
|
||||
)
|
||||
}
|
||||
|
||||
val missingRelays by postViewModel.recipientsMissingDmRelays.collectAsStateWithLifecycle()
|
||||
if (missingRelays.isNotEmpty()) {
|
||||
RecipientMissingRelaysWarning(missingRelays, accountViewModel, nav)
|
||||
}
|
||||
|
||||
BottomRowActions(postViewModel, accountViewModel)
|
||||
}
|
||||
}
|
||||
@@ -393,7 +398,9 @@ private fun BottomRowActions(
|
||||
.height(50.dp),
|
||||
verticalAlignment = CenterVertically,
|
||||
) {
|
||||
if (postViewModel.room != null) {
|
||||
val room by postViewModel.room.collectAsStateWithLifecycle()
|
||||
|
||||
if (room != null) {
|
||||
SelectFromGallery(
|
||||
isUploading = postViewModel.isUploadingImage,
|
||||
enabled = !postViewModel.isUploadingFile,
|
||||
@@ -424,7 +431,7 @@ private fun BottomRowActions(
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.room != null) {
|
||||
if (room != null) {
|
||||
TakePictureButton(
|
||||
onPictureTaken = { postViewModel.pickedMedia(it) },
|
||||
)
|
||||
@@ -443,7 +450,7 @@ private fun BottomRowActions(
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.room != null) {
|
||||
if (room != null) {
|
||||
TakeVideoButton(
|
||||
onVideoTaken = { postViewModel.pickedMedia(it) },
|
||||
)
|
||||
@@ -550,8 +557,6 @@ fun SendDirectMessageTo(
|
||||
focusedBorderColor = Color.Transparent,
|
||||
),
|
||||
)
|
||||
|
||||
ToggleNip17Button(postViewModel, accountViewModel)
|
||||
}
|
||||
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
|
||||
+111
-4
@@ -21,34 +21,50 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.input.InputTransformation.Companion.keyboardOptions
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.actions.StrippingFailureDialog
|
||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
|
||||
import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.ShowEmojiSuggestionList
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.ShowUserSuggestionList
|
||||
import com.vitorpamplona.amethyst.ui.note.showCount
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAheadNoDot
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.RoomChatFileUploadDialog
|
||||
@@ -61,9 +77,13 @@ import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font12SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.PostKeyboard
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.SpacedBy10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.SuggestionListDefaultHeightChat
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
@@ -166,7 +186,12 @@ fun PrivateMessageEditFieldRow(
|
||||
}
|
||||
}
|
||||
|
||||
EditField(channelScreenModel, onSendNewMessage, accountViewModel)
|
||||
val missingRelays by channelScreenModel.recipientsMissingDmRelays.collectAsStateWithLifecycle()
|
||||
if (missingRelays.isNotEmpty()) {
|
||||
RecipientMissingRelaysWarning(missingRelays, accountViewModel, nav)
|
||||
} else {
|
||||
EditField(channelScreenModel, onSendNewMessage, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +236,90 @@ fun EditField(
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun RecipientMissingRelaysWarningPreview() {
|
||||
val user1 = LocalCache.getOrCreateUser("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c")
|
||||
val user2 = LocalCache.getOrCreateUser("ca89cb11f1c75d5b6622268ff43d2288ea8b2cb5b9aa996ff9ff704fc904b78b")
|
||||
|
||||
ThemeComparisonColumn {
|
||||
RecipientMissingRelaysWarning(
|
||||
persistentListOf(user1, user2),
|
||||
mockAccountViewModel(),
|
||||
EmptyNav(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RecipientMissingRelaysWarning(
|
||||
users: ImmutableList<User>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = SpacedBy10dp,
|
||||
) {
|
||||
UserGallery(users) { user ->
|
||||
ClickableUserPicture(
|
||||
user,
|
||||
Size25dp,
|
||||
accountViewModel,
|
||||
onClick = {
|
||||
nav.nav { routeFor(user) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringRes(R.string.recipient_missing_dm_relays),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
fontSize = Font12SP,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserGallery(
|
||||
users: ImmutableList<User>,
|
||||
galleryUser: @Composable RowScope.(user: User) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy((-10).dp),
|
||||
) {
|
||||
users.take(6).forEach {
|
||||
key(it.pubkeyHex) {
|
||||
galleryUser(it)
|
||||
}
|
||||
}
|
||||
|
||||
if (users.size > 6) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier =
|
||||
Modifier
|
||||
.size(Size25dp)
|
||||
.clip(shape = CircleShape)
|
||||
.background(MaterialTheme.colorScheme.secondaryContainer),
|
||||
) {
|
||||
Text(
|
||||
text = "+" + showCount(users.size - 6),
|
||||
fontSize = 10.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun KeyboardLeadingIcon(
|
||||
channelScreenModel: ChatNewMessageViewModel,
|
||||
@@ -218,7 +327,7 @@ fun KeyboardLeadingIcon(
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(start = 4.dp, end = 10.dp),
|
||||
modifier = Modifier.padding(start = 4.dp, end = 4.dp),
|
||||
) {
|
||||
SelectFromGallery(
|
||||
isUploading = channelScreenModel.isUploadingImage,
|
||||
@@ -226,8 +335,6 @@ fun KeyboardLeadingIcon(
|
||||
modifier = Modifier,
|
||||
onImageChosen = channelScreenModel::pickedMedia,
|
||||
)
|
||||
|
||||
ToggleNip17Button(channelScreenModel, accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-78
@@ -25,94 +25,25 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.note.QuickActionAlertDialog
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.IncognitoIconButtonModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ToggleNip17Button(
|
||||
channelScreenModel: ChatNewMessageViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
var wantsToActivateNIP17 by remember { mutableStateOf(false) }
|
||||
|
||||
if (wantsToActivateNIP17) {
|
||||
NewFeatureNIP17AlertDialog(
|
||||
accountViewModel = accountViewModel,
|
||||
onConfirm = { channelScreenModel.toggleNIP04And24() },
|
||||
onDismiss = { wantsToActivateNIP17 = false },
|
||||
)
|
||||
}
|
||||
|
||||
fun Nip17Indicator(channelScreenModel: ChatNewMessageViewModel) {
|
||||
IconButton(
|
||||
modifier = Modifier.width(30.dp),
|
||||
onClick = {
|
||||
if (
|
||||
!accountViewModel.account.settings.hideNIP17WarningDialog &&
|
||||
!channelScreenModel.nip17 &&
|
||||
!channelScreenModel.requiresNIP17
|
||||
) {
|
||||
wantsToActivateNIP17 = true
|
||||
} else {
|
||||
channelScreenModel.toggleNIP04And24()
|
||||
}
|
||||
},
|
||||
onClick = { },
|
||||
enabled = false,
|
||||
) {
|
||||
if (channelScreenModel.nip17) {
|
||||
Icon(
|
||||
painter = painterRes(R.drawable.incognito, 2),
|
||||
contentDescription = stringRes(id = R.string.accessibility_turn_off_sealed_message),
|
||||
modifier = IncognitoIconButtonModifier,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
painter = painterRes(R.drawable.incognito_off, 2),
|
||||
contentDescription = stringRes(id = R.string.accessibility_turn_on_sealed_message),
|
||||
modifier = IncognitoIconButtonModifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
painter = painterRes(R.drawable.incognito, 2),
|
||||
contentDescription = stringRes(id = R.string.accessibility_turn_off_sealed_message),
|
||||
modifier = IncognitoIconButtonModifier,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NewFeatureNIP17AlertDialog(
|
||||
accountViewModel: AccountViewModel,
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
QuickActionAlertDialog(
|
||||
title = stringRes(R.string.new_feature_nip17_might_not_be_available_title),
|
||||
textContent = stringRes(R.string.new_feature_nip17_might_not_be_available_description),
|
||||
buttonIconResource = R.drawable.incognito,
|
||||
buttonIconReference = 3,
|
||||
buttonText = stringRes(R.string.new_feature_nip17_activate),
|
||||
onClickDoOnce = {
|
||||
scope.launch { onConfirm() }
|
||||
onDismiss()
|
||||
},
|
||||
onClickDontShowAgain = {
|
||||
scope.launch {
|
||||
onConfirm()
|
||||
accountViewModel.account.settings.setHideNIP17WarningDialog()
|
||||
}
|
||||
onDismiss()
|
||||
},
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-42
@@ -25,7 +25,6 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.references.references
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||
@@ -100,47 +99,7 @@ class ChatFileSender(
|
||||
account.sendNip17PrivateMessage(template)
|
||||
}
|
||||
|
||||
// ------
|
||||
// NIP 04
|
||||
// ------
|
||||
|
||||
suspend fun sendNIP04(uploads: List<SuccessfulUploads>) {
|
||||
uploads.forEach {
|
||||
if (it.cipher == null) {
|
||||
sendNIP04(it.result, it.caption, it.contentWarningReason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sendNIP04(
|
||||
result: UploadOrchestrator.OrchestratorResult.ServerResult,
|
||||
caption: String?,
|
||||
contentWarningReason: String?,
|
||||
) {
|
||||
val iMetaAttachments = IMetaAttachments()
|
||||
iMetaAttachments.add(result, caption, contentWarningReason)
|
||||
|
||||
val toUser = chatroom.users.first().let { LocalCache.getOrCreateUser(it).toPTag() }
|
||||
|
||||
val template =
|
||||
PrivateDmEvent.build(
|
||||
toUser = toUser,
|
||||
message = result.url,
|
||||
imetas = iMetaAttachments.iMetaAttachments,
|
||||
replyingTo = null,
|
||||
signer = account.signer,
|
||||
)
|
||||
|
||||
account.sendNip04PrivateMessage(template)
|
||||
}
|
||||
|
||||
suspend fun sendAll(uploads: List<SuccessfulUploads>) {
|
||||
uploads.forEach {
|
||||
if (it.cipher != null) {
|
||||
sendNIP17(it.result, it.caption, it.contentWarningReason, it.cipher)
|
||||
} else {
|
||||
sendNIP04(it.result, it.caption, it.contentWarningReason)
|
||||
}
|
||||
}
|
||||
sendNIP17(uploads)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -28,6 +28,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@@ -46,7 +47,8 @@ fun RoomChatFileUploadDialog(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val room = channelScreenModel.room ?: return
|
||||
val roomState = channelScreenModel.room.collectAsStateWithLifecycle()
|
||||
val room = roomState.value ?: return
|
||||
val context = LocalContext.current
|
||||
|
||||
ChatFileUploadDialog(
|
||||
@@ -75,6 +77,6 @@ fun RoomChatFileUploadDialog(
|
||||
onCancel,
|
||||
accountViewModel,
|
||||
nav,
|
||||
isNip17 = channelScreenModel.nip17,
|
||||
isNip17 = true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1532,8 +1532,20 @@
|
||||
<string name="following_accounts">Obserwujesz %1$d kont(a)…</string>
|
||||
<string name="import_follows_tips">"Wprowadź profil znajomego lub lidera społeczności. Możesz użyć ich npub-u, adresu NIP-05 lub nazwy jak alicja@domena.pl lub id/alicja dla zweryfikowanych przez blockchain tożsamości."</string>
|
||||
<string name="select_all">Wybierz Wszystkie</string>
|
||||
<string name="event_sync_what_happens_body">To narzędzie skanuje wszystkie transmitery, z którymi zetknęła się Twoja aplikacja, i przekierowuje zdarzenia do właściwych miejsc docelowych: </string>
|
||||
<string name="event_sync_step1">Pobierz wszystkie wydarzenia, których jesteś autorem, i wyślij je do transmiterów nadawczych.</string>
|
||||
<string name="event_sync_step2">Pobierz wszystkie wydarzenia, w których jesteś wymieniony, i prześlij je do swoich transmiterów odbiorczych.</string>
|
||||
<string name="event_sync_step3">Pobierz wszystkie bezpośrednie wiadomości adresowane do Ciebie i wyślij je do swoich transmiterów DM.</string>
|
||||
<string name="event_sync_wifi_warning">⚠️ Wygląda na to, że korzystasz z połączenia komórkowego lub z limitem transferu danych. Ta operacja może wymagać przesłania bardzo dużej ilości danych. Przed rozpoczęciem podłącz się do sieci Wi-Fi.</string>
|
||||
<string name="event_sync_mobile_data_dialog_title">Włączyć transmisje danych?</string>
|
||||
<string name="event_sync_pause">Pauza</string>
|
||||
<string name="event_sync_resume">Wznów</string>
|
||||
<string name="event_sync_start_over">Zacznij od początku</string>
|
||||
<string name="event_sync_cancel">Anuluj</string>
|
||||
<string name="event_sync_relays_progress">Transmitery: %1$d / %2$d</string>
|
||||
<string name="events">wydarzenia</string>
|
||||
<string name="profiles">profile</string>
|
||||
<string name="relay_settings_lower">ustawienia transmiterów</string>
|
||||
<string name="last_seen">Ostatnio widziano %1$s temu</string>
|
||||
<string name="event_sync_status_error">Błąd</string>
|
||||
</resources>
|
||||
|
||||
@@ -1328,4 +1328,202 @@ Za podpisovanje se je potrebno prijaviti s privatnim ključem</string>
|
||||
<string name="badge_and_n_others"> in %1$d ostalih</string>
|
||||
<string name="remove_language">Odstrani %1$s</string>
|
||||
<!-- Kind display names for relay subscription filter chips -->
|
||||
<string name="kind_audio_header">Zvočna glava</string>
|
||||
<string name="kind_audio_track">Zvočni zapis</string>
|
||||
<string name="kind_badge_awards">Nagradne značke</string>
|
||||
<string name="kind_badge_definitions">Definicija značke</string>
|
||||
<string name="kind_profile_badges">Značke profila</string>
|
||||
<string name="kind_blocked_relays">Blokirani releji</string>
|
||||
<string name="kind_blossom_servers">Blossom strežniki</string>
|
||||
<string name="kind_blossom_auth">Blossom avtentikacija</string>
|
||||
<string name="kind_broadcast_relays">Oddajni releji</string>
|
||||
<string name="kind_bookmark_list">Seznam zaznamkov</string>
|
||||
<string name="kind_day_appointment">Dnevni termin</string>
|
||||
<string name="kind_calendar">Koledar</string>
|
||||
<string name="kind_appointment">Sestanek</string>
|
||||
<string name="kind_appt_rsvp">Potrditev termina</string>
|
||||
<string name="kind_chess_games">Šahovske igre</string>
|
||||
<string name="kind_chess_auth">Šahovska avtentikacija</string>
|
||||
<string name="kind_favorite_relays">Priljubljeni releji</string>
|
||||
<string name="kind_chess_challenges">Šahovski izzivi</string>
|
||||
<string name="kind_chess_game_accept">Sprejmi partijo</string>
|
||||
<string name="kind_chess_move">Šahovska poteza</string>
|
||||
<string name="kind_chess_game_end">Konec partije</string>
|
||||
<string name="kind_chess_draw_offer">Predlagaj remi</string>
|
||||
<string name="kind_channel_definition">Definicija kanala</string>
|
||||
<string name="kind_channel_hide_msg">Skrij sporočilo v kanalu</string>
|
||||
<string name="kind_channel_list">Seznam kanalov</string>
|
||||
<string name="kind_channel_message">Sporočilo v kanalu</string>
|
||||
<string name="kind_channel_metadata">Metapodatki kanala</string>
|
||||
<string name="kind_channel_mute_user">Utišani uporabniki kanala</string>
|
||||
<string name="kind_dm_file">Datoteka ZS</string>
|
||||
<string name="kind_dm_message">ZS sporočilo</string>
|
||||
<string name="kind_dm_relays">Releji ZS</string>
|
||||
<string name="kind_classifieds">Oglasi</string>
|
||||
<string name="kind_comments">Komentarji</string>
|
||||
<string name="kind_community_def">Opis skupnosti</string>
|
||||
<string name="kind_community_list">Seznam skupnosti</string>
|
||||
<string name="kind_community_post">Objava skupnosti</string>
|
||||
<string name="kind_follow_list">Seznam sledenih</string>
|
||||
<string name="kind_deletions">Izbrisano</string>
|
||||
<string name="kind_drafts">Osnutki</string>
|
||||
<string name="kind_emoji_packs">Zbirke emojijev</string>
|
||||
<string name="kind_emoji_pack_list">Pregled zbirk emojijev</string>
|
||||
<string name="kind_ephemeral_chat">Minljivi klepet</string>
|
||||
<string name="kind_ephemeral_chatrooms">Minljive klepetalnice</string>
|
||||
<string name="kind_file_headers">Glave datotek</string>
|
||||
<string name="kind_profile_gallery">Galerija profila</string>
|
||||
<string name="kind_file_servers">Datotečni strežniki</string>
|
||||
<string name="kind_blob_data">Blob podatki</string>
|
||||
<string name="kind_blob_headers">Blob glava</string>
|
||||
<string name="kind_medical_data">Zdravstveni podatki</string>
|
||||
<string name="kind_follow_packs">Paketi za sledenje</string>
|
||||
<string name="kind_reposts_16">Ponovno preposlani (16)</string>
|
||||
<string name="kind_geohash_follows">Sledeni geohash-i</string>
|
||||
<string name="kind_gift_wraps">GiftWraps</string>
|
||||
<string name="kind_git_issue">Git zadeva</string>
|
||||
<string name="kind_git_patch">Git popravek</string>
|
||||
<string name="kind_git_repo">Git repozitorij</string>
|
||||
<string name="kind_git_reply">Git odgovor</string>
|
||||
<string name="kind_zap_goals">Ciljni znesek zapov</string>
|
||||
<string name="kind_hashtag_follows">Sledeni ključniki</string>
|
||||
<string name="kind_highlights">Osvetlitve</string>
|
||||
<string name="kind_http_auth">Http Auth</string>
|
||||
<string name="kind_index_relay_list">Indeksiraj seznam relejev</string>
|
||||
<string name="kind_adventure_prologue">Pustolovski prolog</string>
|
||||
<string name="kind_adventure_scene">Pustolovska scena</string>
|
||||
<string name="kind_adventure_reading">Pustolovsko branje</string>
|
||||
<string name="kind_named_bookmarks">Poimenovani zaznamki</string>
|
||||
<string name="kind_live_chats">Pogovori v živo</string>
|
||||
<string name="kind_live_streams">Prenosi v živo</string>
|
||||
<string name="kind_zaps">Zapi</string>
|
||||
<string name="kind_nwc_request">NWC zahtevek</string>
|
||||
<string name="kind_nwc_response">NWC odgovor</string>
|
||||
<string name="kind_private_zaps">Zasebni zapi</string>
|
||||
<string name="kind_zap_req">Zap zahtevek</string>
|
||||
<string name="kind_blogs">Blogi</string>
|
||||
<string name="kind_meeting_room">Sejna soba</string>
|
||||
<string name="kind_room_presence">Prisotnost v sobi</string>
|
||||
<string name="kind_meeting_space">Srečevalnica</string>
|
||||
<string name="kind_profile">Profil</string>
|
||||
<string name="kind_mute_list">Seznam utišanih</string>
|
||||
<string name="kind_nns">NNS</string>
|
||||
<string name="kind_nip">NIP</string>
|
||||
<string name="kind_nostr_connect">Nostr Connect</string>
|
||||
<string name="kind_dvm_status">DVM status</string>
|
||||
<string name="kind_dvm_content_req">DVM zahtevek vsebine</string>
|
||||
<string name="kind_dvm_content_resp">DVM odgovor na vsebino</string>
|
||||
<string name="kind_dvm_user_req">DVM zahtevek uporabnika</string>
|
||||
<string name="kind_dvm_user_resp">DVM odgovor uporabnika</string>
|
||||
<string name="kind_ots">OTS</string>
|
||||
<string name="kind_pay_to">Plačaj</string>
|
||||
<string name="kind_people_lists">Seznam oseb</string>
|
||||
<string name="kind_pictures">Fotografije</string>
|
||||
<string name="kind_pins">Bucike</string>
|
||||
<string name="kind_zap_poll">Zap sklad</string>
|
||||
<string name="kind_poll">Glasovanje</string>
|
||||
<string name="kind_poll_response">Odziv na glasovanje</string>
|
||||
<string name="kind_nip04_dms">NIP-04 ZS</string>
|
||||
<string name="kind_private_relays">Zasebni releji</string>
|
||||
<string name="kind_proxy_relays">Proxy releji</string>
|
||||
<string name="kind_public_message">Javno sporočilo</string>
|
||||
<string name="kind_reactions">Odzivne ikone</string>
|
||||
<string name="kind_contact_card">Kartica kontakta</string>
|
||||
<string name="kind_relay_auth">Autorizacija releja</string>
|
||||
<string name="kind_relay_discovery">Odkrivanje relejev</string>
|
||||
<string name="kind_relay_monitor">Obvestilo nadzornika relejev</string>
|
||||
<string name="kind_relay_set">Nabor relejev</string>
|
||||
<string name="kind_reports">Prijave</string>
|
||||
<string name="kind_reposts">Ponovne objave</string>
|
||||
<string name="kind_user_delete">Brisanje uporabnika</string>
|
||||
<string name="kind_seals">Pečat</string>
|
||||
<string name="kind_search_relays">Iskalni releji</string>
|
||||
<string name="kind_user_status">Status uporabnika</string>
|
||||
<string name="kind_notes">Zapiski</string>
|
||||
<string name="kind_edits">Urejeni</string>
|
||||
<string name="kind_torrents">Torenti</string>
|
||||
<string name="kind_torrent_comments">Komentarji torentov</string>
|
||||
<string name="kind_trusted_relays">Zaupanja vredni releji</string>
|
||||
<string name="kind_trusted_providers">Zaupanja vredni ponudniki</string>
|
||||
<string name="kind_video_repl">Videoposnetki (odgovor)</string>
|
||||
<string name="kind_shorts_repl">Kratki posnetki (odgovor)</string>
|
||||
<string name="kind_video">Videoposnetki</string>
|
||||
<string name="kind_shorts">Kratki videoposnetki</string>
|
||||
<string name="kind_voice_msg">Zvočno sporočilo</string>
|
||||
<string name="kind_voice_reply">Zvočni odgovori</string>
|
||||
<string name="kind_wiki">Wiki</string>
|
||||
<string name="start_with_a_great_feed_by_following_the_same_people_as_someone_you_trust">Zagotovite si odličen vir objav tako, da sledite istim ljudem kot nekdo, ki mu zaupate.</string>
|
||||
<string name="import_follow_list">Uvozi seznam sledenih</string>
|
||||
<string name="select_users_to_follow">Izberite uporabnike, ki jim želite slediti</string>
|
||||
<string name="profile_to_import_from">Profil, iz katerega želite uvoziti</string>
|
||||
<string name="name_search_npub1_alice_example_com">Iskanje, npub1…, janez@primer.si</string>
|
||||
<string name="supports_npub_nip_05_hex_and_namecoin_bit_d_id">Podpira npub, nprofile, NIP-05, hex in namecoin (.bit, d/, id/)</string>
|
||||
<string name="look_up_follow_list">Poišči seznam sledenih</string>
|
||||
<string name="tip">Daj napitnino</string>
|
||||
<string name="accounts_found">Najdeno %1$d računov</string>
|
||||
<string name="num_selected">Izbranih %1$d</string>
|
||||
<string name="resolved_via_namecoin">Razrešeno prek Namecoina</string>
|
||||
<string name="now_following_accounts">Zdaj sledite %1$d računom</string>
|
||||
<string name="your_feed_is_ready">Vaš vsebina objav je pripravljen.</string>
|
||||
<string name="skip">Preskoči</string>
|
||||
<string name="search_another">Poišči še koga</string>
|
||||
<string name="follow_accounts">Sledi %1$d računom</string>
|
||||
<string name="import_more">Dodaten uvoz</string>
|
||||
<string name="continue_txt">Nadaljuj</string>
|
||||
<string name="skip_for_now">Preskoči za zdaj</string>
|
||||
<string name="resolving">Razreševanje %1$s…</string>
|
||||
<string name="fetching_follow_list">Pridobivanje seznama sledenih…</string>
|
||||
<string name="no_follows_found">Ni najdenih sledilcev</string>
|
||||
<string name="following_accounts">Sledenje %1$d računom…</string>
|
||||
<string name="import_follows_tips">"Vnesite profil prijatelja ali vodje skupnosti. Uporabite lahko njihov npub, naslov NIP-05 ali ime Namecoin (npr. janez@primer.bit ali id/janez) za identitete, preverjene prek verige blokov."</string>
|
||||
<string name="select_all">Izberi vse</string>
|
||||
<string name="uptime">%1$d%% časa delovanja</string>
|
||||
<string name="namecoin_settings">Namecoin nastavitve</string>
|
||||
<string name="event_sync_title">Sinhronizacija relejev</string>
|
||||
<string name="event_sync_section">Sinhronizacija relejev</string>
|
||||
<string name="event_sync_section_explainer">Ponovno objavite svoje dogodke na vseh znanih relejih, da posodobite svoje izhodne, vhodne in releje ZS. Zahtevana je povezava Wi-Fi — postopek lahko porabi veliko podatkov.</string>
|
||||
<string name="event_sync_open_button">Odpri sinhronizacijo relejev…</string>
|
||||
<string name="event_sync_what_happens_title">Kaj to naredi</string>
|
||||
<string name="event_sync_what_happens_body">To orodje pregleda vse releje, ki jih je zaznala vaša aplikacija, in vaše dogodke prerazporedi na ustrezne cilje:</string>
|
||||
<string name="event_sync_step1">Prenesite vse dogodke katere ste ustvarili, in jih pošljite v vaše izhodne releje.</string>
|
||||
<string name="event_sync_step2">Prenesite vse dogodke, v katerih ste omenjeni, in jih pošljite v vaše vhodne releje.</string>
|
||||
<string name="event_sync_step3">Prenesite vsa zasebna sporočila, naslovljena na vas, in jih pošljite v svoje releje za zasebna sporočila.</string>
|
||||
<string name="event_sync_wifi_warning">⚠ Kaže, da uporabljate omejeno ali mobilno povezavo. Ta postopek lahko prenese zelo veliko količino podatkov. Pred začetkom se povežite v omrežje Wi-Fi.</string>
|
||||
<string name="event_sync_mobile_data_dialog_title">Uporabi mobilne podatke?</string>
|
||||
<string name="event_sync_start">Začni sinhronizacijo</string>
|
||||
<string name="event_sync_start_anyway">Vseeno začni (mobilni podatki)</string>
|
||||
<string name="event_sync_pause">Premor</string>
|
||||
<string name="event_sync_resume">Nadaljuj</string>
|
||||
<string name="event_sync_start_over">Začni znova</string>
|
||||
<string name="event_sync_cancel">Prekini</string>
|
||||
<string name="event_sync_relays_progress">Releji: %1$d / %2$d</string>
|
||||
<string name="event_sync_events_sent">Prerazporejeni dogodki: %1$d novih od %2$d poslanih in %3$d prejetih</string>
|
||||
<string name="event_sync_paused_title">Premor sinhronizacije</string>
|
||||
<string name="event_sync_paused_body">Zaključenih %1$d od %2$d relejev — do zdaj prerazporejenih %3$d dogodkov. Dotaknite se »Nadaljuj« za nadaljevanje.</string>
|
||||
<string name="event_sync_done_title">Sinhronizacija zaključena</string>
|
||||
<string name="event_sync_done_sent">Naprej posredovanih %1$d od %2$d prejetih dogodkov do ciljnih relejev.</string>
|
||||
<string name="event_sync_done_accepted">Število dogodkov, ki jih je ciljni rele sprejel kot nove: %1$d.</string>
|
||||
<string name="event_sync_done_duration">Zaključeno v %1$d sekundah</string>
|
||||
<string name="event_sync_error_title">Napaka pri sinhronizaciji</string>
|
||||
<string name="event_sync_sending_to">Pošiljam k</string>
|
||||
<string name="event_sync_outbox_relays">Odhodni</string>
|
||||
<string name="event_sync_inbox_relays">Vhodni</string>
|
||||
<string name="event_sync_dm_relays">ZS</string>
|
||||
<string name="event_sync_activity_log">Trenutno preverjam (%1$d relejev)</string>
|
||||
<string name="event_sync_activity_log_finished">Finished (%1$d relays)</string>
|
||||
<string name="event_sync_log_sent">poslano %1$s</string>
|
||||
<string name="event_sync_log_recv">sprejeto %1$s</string>
|
||||
<string name="event_sync_log_new">nov %1$s</string>
|
||||
<string name="event_sync_no_events">ni dogodkov</string>
|
||||
<string name="ots_explorer_settings">Bitcoin Raziskovalec (OTS)</string>
|
||||
<string name="events">dogodki</string>
|
||||
<string name="dms">ZS</string>
|
||||
<string name="profiles">profili</string>
|
||||
<string name="relay_settings_lower">Nastavitve releja</string>
|
||||
<string name="last_seen">Nazadnje viden pred %1$s </string>
|
||||
<string name="event_sync_less_than_until"><%1$s</string>
|
||||
<string name="event_sync_status_connecting">Povezujem</string>
|
||||
<string name="event_sync_status_downloading">Prenašam</string>
|
||||
<string name="event_sync_status_error">Napaka</string>
|
||||
<string name="event_sync_status_completed">Zaključeno</string>
|
||||
</resources>
|
||||
|
||||
@@ -1359,6 +1359,7 @@
|
||||
<string name="dm_relays_not_found_examples2">Good options are:\n - auth.nostr1.com (free)\n - inbox.nostr.wine (paid)\n - relay.0xchat.com (free)</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="recipient_missing_dm_relays">DM inbox relays not found. Messages cannot be delivered until they configure their relay list.</string>
|
||||
|
||||
<string name="search_relays_title">Search Relays</string>
|
||||
<string name="search_relays_not_found">Set up your Search relays</string>
|
||||
|
||||
Reference in New Issue
Block a user