Final touches to remove nip04 dms
This commit is contained in:
+11
-3
@@ -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
|
||||
@@ -94,8 +97,13 @@ fun ChatroomView(
|
||||
|
||||
// Reactively check if recipients have DM relays for NIP-17 delivery
|
||||
for (userHex in room.users) {
|
||||
ObserveRelayListForDMs(pubkey = userHex, accountViewModel = accountViewModel) {
|
||||
newPostModel.updateRecipientRelayStatus()
|
||||
LoadAddressableNote(
|
||||
ChatMessageRelayListEvent.createAddress(userHex),
|
||||
accountViewModel,
|
||||
) { note ->
|
||||
if (note != null) {
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+74
-35
@@ -73,6 +73,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
|
||||
@@ -93,10 +94,24 @@ 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
|
||||
@@ -125,10 +140,57 @@ class ChatNewMessageViewModel :
|
||||
}
|
||||
}
|
||||
|
||||
var room: ChatroomKey? by mutableStateOf(null)
|
||||
val room = MutableStateFlow<ChatroomKey?>(null)
|
||||
|
||||
/** Whether any recipients are missing DM relay lists */
|
||||
var recipientsMissingDmRelays by mutableStateOf(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)
|
||||
|
||||
@@ -180,9 +242,6 @@ class ChatNewMessageViewModel :
|
||||
var wantsZapraiser by mutableStateOf(false)
|
||||
override var zapRaiserAmount = mutableStateOf<Long?>(null)
|
||||
|
||||
// NIP17 is always enabled - NIP-04 is deprecated for sending
|
||||
val nip17: Boolean get() = true
|
||||
|
||||
fun lnAddress(): String? = account.userProfile().lnAddress()
|
||||
|
||||
fun hasLnAddress(): Boolean = account.userProfile().lnAddress() != null
|
||||
@@ -208,26 +267,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" },
|
||||
)
|
||||
|
||||
updateRecipientRelayStatus()
|
||||
}
|
||||
|
||||
fun updateRecipientRelayStatus() {
|
||||
val room = this.room
|
||||
if (room != null) {
|
||||
this.recipientsMissingDmRelays =
|
||||
room.users.any { hexKey ->
|
||||
val user = LocalCache.getOrCreateUser(hexKey)
|
||||
user.dmInboxRelays().isNullOrEmpty()
|
||||
}
|
||||
} else {
|
||||
this.recipientsMissingDmRelays = false
|
||||
}
|
||||
}
|
||||
|
||||
fun reply(replyNote: Note) {
|
||||
@@ -360,8 +404,6 @@ class ChatNewMessageViewModel :
|
||||
urlPreviews.update(message)
|
||||
|
||||
iMetaAttachments.addAll(draftEvent.imetas())
|
||||
|
||||
updateRecipientRelayStatus()
|
||||
}
|
||||
|
||||
suspend fun sendPostSync() {
|
||||
@@ -423,7 +465,7 @@ class ChatNewMessageViewModel :
|
||||
context: Context,
|
||||
onceUploaded: () -> Unit,
|
||||
) {
|
||||
val room = room ?: return
|
||||
val room = room.value ?: return
|
||||
val uploadState = uploadState ?: return
|
||||
|
||||
accountViewModel.launchSigner {
|
||||
@@ -471,12 +513,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
|
||||
|
||||
@@ -495,7 +537,6 @@ class ChatNewMessageViewModel :
|
||||
}
|
||||
|
||||
RetryMode.SEND -> {
|
||||
if (room == null) return@launchSigner
|
||||
ChatFileUploader(account).justUploadNIP17Unencrypted(
|
||||
uploadState,
|
||||
onError ?: accountViewModel.toastManager::toast,
|
||||
@@ -511,7 +552,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())
|
||||
@@ -661,12 +702,10 @@ class ChatNewMessageViewModel :
|
||||
|
||||
val users = toUsersTagger.pTags?.mapTo(mutableSetOf()) { it.pubkeyHex }
|
||||
if (users.isNullOrEmpty()) {
|
||||
room = null
|
||||
updateRecipientRelayStatus()
|
||||
room.emit(null)
|
||||
} else {
|
||||
if (users != room?.users) {
|
||||
room = ChatroomKey(users)
|
||||
updateRecipientRelayStatus()
|
||||
if (users != room.value?.users) {
|
||||
room.emit(ChatroomKey(users))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -743,7 +782,7 @@ class ChatNewMessageViewModel :
|
||||
(!wantsZapraiser || zapRaiserAmount.value != null) &&
|
||||
(toUsers.text.isNotBlank()) &&
|
||||
uploadState?.multiOrchestrator == null &&
|
||||
!recipientsMissingDmRelays
|
||||
recipientsMissingDmRelays.value.isEmpty()
|
||||
|
||||
fun insertAtCursor(newElement: String) {
|
||||
message = message.insertUrlAtCursor(newElement)
|
||||
|
||||
+9
-8
@@ -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,8 +332,9 @@ fun GroupDMScreenContent(
|
||||
)
|
||||
}
|
||||
|
||||
if (postViewModel.recipientsMissingDmRelays) {
|
||||
RecipientMissingRelaysWarning()
|
||||
val missingRelays by postViewModel.recipientsMissingDmRelays.collectAsStateWithLifecycle()
|
||||
if (missingRelays.isNotEmpty()) {
|
||||
RecipientMissingRelaysWarning(missingRelays, accountViewModel, nav)
|
||||
}
|
||||
|
||||
BottomRowActions(postViewModel, accountViewModel)
|
||||
@@ -397,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,
|
||||
@@ -428,7 +431,7 @@ private fun BottomRowActions(
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.room != null) {
|
||||
if (room != null) {
|
||||
TakePictureButton(
|
||||
onPictureTaken = { postViewModel.pickedMedia(it) },
|
||||
)
|
||||
@@ -447,7 +450,7 @@ private fun BottomRowActions(
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.room != null) {
|
||||
if (room != null) {
|
||||
TakeVideoButton(
|
||||
onVideoTaken = { postViewModel.pickedMedia(it) },
|
||||
)
|
||||
@@ -554,8 +557,6 @@ fun SendDirectMessageTo(
|
||||
focusedBorderColor = Color.Transparent,
|
||||
),
|
||||
)
|
||||
|
||||
Nip17Indicator(postViewModel)
|
||||
}
|
||||
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
|
||||
+123
-41
@@ -20,35 +20,50 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send
|
||||
|
||||
import android.R.attr.maxLines
|
||||
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.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 +76,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
|
||||
@@ -164,7 +183,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,55 +198,81 @@ fun EditField(
|
||||
onSendNewMessage: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
if (channelScreenModel.recipientsMissingDmRelays) {
|
||||
RecipientMissingRelaysWarning()
|
||||
} else {
|
||||
ThinPaddingTextField(
|
||||
value = channelScreenModel.message,
|
||||
onValueChange = { channelScreenModel.updateMessage(it) },
|
||||
keyboardOptions = PostKeyboard,
|
||||
shape = EditFieldBorder,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.reply_here),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
ThinSendButton(
|
||||
isActive = channelScreenModel.canPost(),
|
||||
modifier = EditFieldTrailingIconModifier,
|
||||
) {
|
||||
accountViewModel.launchSigner {
|
||||
channelScreenModel.sendPostSync()
|
||||
onSendNewMessage()
|
||||
}
|
||||
ThinPaddingTextField(
|
||||
value = channelScreenModel.message,
|
||||
onValueChange = { channelScreenModel.updateMessage(it) },
|
||||
keyboardOptions = PostKeyboard,
|
||||
shape = EditFieldBorder,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.reply_here),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
ThinSendButton(
|
||||
isActive = channelScreenModel.canPost(),
|
||||
modifier = EditFieldTrailingIconModifier,
|
||||
) {
|
||||
accountViewModel.launchSigner {
|
||||
channelScreenModel.sendPostSync()
|
||||
onSendNewMessage()
|
||||
}
|
||||
},
|
||||
leadingIcon = {
|
||||
KeyboardLeadingIcon(channelScreenModel, accountViewModel)
|
||||
},
|
||||
colors =
|
||||
TextFieldDefaults.colors(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
visualTransformation = UrlUserTagTransformation(MaterialTheme.colorScheme.primary),
|
||||
}
|
||||
},
|
||||
leadingIcon = {
|
||||
KeyboardLeadingIcon(channelScreenModel, accountViewModel)
|
||||
},
|
||||
colors =
|
||||
TextFieldDefaults.colors(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
visualTransformation = UrlUserTagTransformation(MaterialTheme.colorScheme.primary),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun RecipientMissingRelaysWarningPreview() {
|
||||
val user1 = LocalCache.getOrCreateUser("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c")
|
||||
val user2 = LocalCache.getOrCreateUser("ca89cb11f1c75d5b6622268ff43d2288ea8b2cb5b9aa996ff9ff704fc904b78b")
|
||||
|
||||
ThemeComparisonColumn {
|
||||
RecipientMissingRelaysWarning(
|
||||
persistentListOf(user1, user2),
|
||||
mockAccountViewModel(),
|
||||
EmptyNav(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RecipientMissingRelaysWarning() {
|
||||
fun RecipientMissingRelaysWarning(
|
||||
users: ImmutableList<User>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
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,
|
||||
@@ -233,6 +283,40 @@ fun RecipientMissingRelaysWarning() {
|
||||
}
|
||||
}
|
||||
|
||||
@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,
|
||||
@@ -240,7 +324,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,
|
||||
@@ -248,8 +332,6 @@ fun KeyboardLeadingIcon(
|
||||
modifier = Modifier,
|
||||
onImageChosen = channelScreenModel::pickedMedia,
|
||||
)
|
||||
|
||||
Nip17Indicator(channelScreenModel)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-35
@@ -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,40 +99,6 @@ 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>) {
|
||||
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(
|
||||
@@ -74,6 +76,6 @@ fun RoomChatFileUploadDialog(
|
||||
onCancel,
|
||||
accountViewModel,
|
||||
nav,
|
||||
isNip17 = channelScreenModel.nip17,
|
||||
isNip17 = true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1351,7 +1351,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">This user hasn\'t set up DM inbox relays. Messages cannot be delivered until they configure their relay list.</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