Merge branch 'vitorpamplona:main' into profiles-list-management
This commit is contained in:
+16
-6
@@ -44,14 +44,14 @@ import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
|
|||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||||
import com.vitorpamplona.amethyst.ui.components.Split
|
import com.vitorpamplona.amethyst.ui.components.Split
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.ChatFileUploadState
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.ChatFileUploader
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.ChatFileUploader
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
|
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.references.references
|
import com.vitorpamplona.quartz.nip01Core.tags.references.references
|
||||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||||
import com.vitorpamplona.quartz.nip10Notes.content.findHashtags
|
import com.vitorpamplona.quartz.nip10Notes.content.findHashtags
|
||||||
import com.vitorpamplona.quartz.nip10Notes.content.findNostrUris
|
import com.vitorpamplona.quartz.nip10Notes.content.findNostrEventUris
|
||||||
import com.vitorpamplona.quartz.nip10Notes.content.findURLs
|
import com.vitorpamplona.quartz.nip10Notes.content.findURLs
|
||||||
import com.vitorpamplona.quartz.nip14Subject.subject
|
import com.vitorpamplona.quartz.nip14Subject.subject
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.BaseDMGroupEvent
|
import com.vitorpamplona.quartz.nip17Dm.base.BaseDMGroupEvent
|
||||||
@@ -301,6 +301,8 @@ open class ChatNewMessageViewModel : ViewModel() {
|
|||||||
TextFieldValue(draftEvent.content)
|
TextFieldValue(draftEvent.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iMetaAttachments.addAll(draftEvent.imetas())
|
||||||
|
|
||||||
requiresNIP17 = draftEvent is NIP17Group
|
requiresNIP17 = draftEvent is NIP17Group
|
||||||
nip17 = draftEvent is NIP17Group
|
nip17 = draftEvent is NIP17Group
|
||||||
|
|
||||||
@@ -348,9 +350,15 @@ open class ChatNewMessageViewModel : ViewModel() {
|
|||||||
val uploadState = uploadState ?: return
|
val uploadState = uploadState ?: return
|
||||||
|
|
||||||
if (nip17) {
|
if (nip17) {
|
||||||
ChatFileUploader(room, account).uploadNIP17(uploadState, viewModelScope, onError, context, onceUploaded)
|
ChatFileUploader(room, account).uploadNIP17(uploadState, viewModelScope, onError, context) {
|
||||||
|
saveDraft()
|
||||||
|
onceUploaded()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ChatFileUploader(room, account).uploadNIP04(uploadState, viewModelScope, onError, context, onceUploaded)
|
ChatFileUploader(room, account).uploadNIP04(uploadState, viewModelScope, onError, context) {
|
||||||
|
saveDraft()
|
||||||
|
onceUploaded()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +380,7 @@ open class ChatNewMessageViewModel : ViewModel() {
|
|||||||
ChatMessageEvent.build(message, room.users.map { LocalCache.getOrCreateUser(it).toPTag() }) {
|
ChatMessageEvent.build(message, room.users.map { LocalCache.getOrCreateUser(it).toPTag() }) {
|
||||||
hashtags(findHashtags(message))
|
hashtags(findHashtags(message))
|
||||||
references(findURLs(message))
|
references(findURLs(message))
|
||||||
quotes(findNostrUris(message))
|
quotes(findNostrEventUris(message))
|
||||||
|
|
||||||
emojis(emojis)
|
emojis(emojis)
|
||||||
imetas(usedAttachments)
|
imetas(usedAttachments)
|
||||||
@@ -381,7 +389,7 @@ open class ChatNewMessageViewModel : ViewModel() {
|
|||||||
ChatMessageEvent.reply(message, replyHint) {
|
ChatMessageEvent.reply(message, replyHint) {
|
||||||
hashtags(findHashtags(message))
|
hashtags(findHashtags(message))
|
||||||
references(findURLs(message))
|
references(findURLs(message))
|
||||||
quotes(findNostrUris(message))
|
quotes(findNostrEventUris(message))
|
||||||
|
|
||||||
emojis(emojis)
|
emojis(emojis)
|
||||||
imetas(usedAttachments)
|
imetas(usedAttachments)
|
||||||
@@ -433,6 +441,8 @@ open class ChatNewMessageViewModel : ViewModel() {
|
|||||||
userSuggestions.reset()
|
userSuggestions.reset()
|
||||||
userSuggestionsMainMessage = null
|
userSuggestionsMainMessage = null
|
||||||
|
|
||||||
|
iMetaAttachments.reset()
|
||||||
|
|
||||||
if (emojiSearch.value.isNotEmpty()) {
|
if (emojiSearch.value.isNotEmpty()) {
|
||||||
emojiSearch.tryEmit("")
|
emojiSearch.tryEmit("")
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-1
@@ -67,6 +67,12 @@ class IMetaAttachments {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addAll(imetas: List<IMetaTag>) {
|
||||||
|
imetas.forEach {
|
||||||
|
replace(it.url, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun remove(url: String) {
|
fun remove(url: String) {
|
||||||
iMetaAttachments = iMetaAttachments.filter { it.url != url }
|
iMetaAttachments = iMetaAttachments.filter { it.url != url }
|
||||||
}
|
}
|
||||||
@@ -100,5 +106,9 @@ class IMetaAttachments {
|
|||||||
replace(iMeta.url, iMeta)
|
replace(iMeta.url, iMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun filterIsIn(urls: Set<String>) = iMetaAttachments.filter { it.url !in urls }
|
fun filterIsIn(urls: Set<String>) = iMetaAttachments.filter { it.url in urls }
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
iMetaAttachments = emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-18
@@ -40,14 +40,12 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
|
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
|
||||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
|
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
|
||||||
import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField
|
import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||||
@@ -58,7 +56,7 @@ import com.vitorpamplona.amethyst.ui.note.QuickActionAlertDialog
|
|||||||
import com.vitorpamplona.amethyst.ui.note.ShowEmojiSuggestionList
|
import com.vitorpamplona.amethyst.ui.note.ShowEmojiSuggestionList
|
||||||
import com.vitorpamplona.amethyst.ui.note.ShowUserSuggestionList
|
import com.vitorpamplona.amethyst.ui.note.ShowUserSuggestionList
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.ChatFileUploadDialog
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.RoomChatFileUploadDialog
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.DisplayReplyingToNote
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.DisplayReplyingToNote
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ThinSendButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ThinSendButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
@@ -119,22 +117,10 @@ fun PrivateMessageEditFieldRow(
|
|||||||
|
|
||||||
channelScreenModel.uploadState?.let { uploading ->
|
channelScreenModel.uploadState?.let { uploading ->
|
||||||
uploading.multiOrchestrator?.let { selectedFiles ->
|
uploading.multiOrchestrator?.let { selectedFiles ->
|
||||||
val context = LocalContext.current
|
RoomChatFileUploadDialog(
|
||||||
|
channelScreenModel = channelScreenModel,
|
||||||
ChatFileUploadDialog(
|
|
||||||
room = channelScreenModel.room!!,
|
|
||||||
state = uploading,
|
state = uploading,
|
||||||
upload = {
|
onUpload = onSendNewMessage,
|
||||||
channelScreenModel.upload(
|
|
||||||
onError = accountViewModel::toast,
|
|
||||||
context = context,
|
|
||||||
onceUploaded = onSendNewMessage,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (uploading.selectedServer.type != ServerType.NIP95) {
|
|
||||||
accountViewModel.account.settings.changeDefaultFileServer(uploading.selectedServer)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onCancel = uploading::reset,
|
onCancel = uploading::reset,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
|
|||||||
+32
-234
@@ -20,264 +20,62 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload
|
||||||
|
|
||||||
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.Row
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.imePadding
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Slider
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import com.vitorpamplona.amethyst.R
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.TitleIconModifier
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.rememberHeightDecreaser
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
|
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SettingSwitchItem
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.header.RoomNameOnlyDisplay
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.header.RoomNameOnlyDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.ChatNewMessageViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadDialog
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatFileUploadDialog(
|
fun RoomChatFileUploadDialog(
|
||||||
room: ChatroomKey,
|
channelScreenModel: ChatNewMessageViewModel,
|
||||||
state: ChatFileUploadState,
|
state: ChatFileUploadState,
|
||||||
upload: () -> Unit,
|
onUpload: () -> Unit,
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val scrollState = rememberScrollState()
|
val room = channelScreenModel.room ?: return
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
Dialog(
|
ChatFileUploadDialog(
|
||||||
onDismissRequest = { onCancel() },
|
state,
|
||||||
properties =
|
title = {
|
||||||
DialogProperties(
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
usePlatformDefaultWidth = false,
|
NonClickableUserPictures(
|
||||||
dismissOnClickOutside = false,
|
room = room,
|
||||||
decorFitsSystemWindows = false,
|
accountViewModel = accountViewModel,
|
||||||
),
|
size = Size34dp,
|
||||||
) {
|
|
||||||
SetDialogToEdgeToEdge()
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopAppBar(
|
|
||||||
scrollBehavior = rememberHeightDecreaser(),
|
|
||||||
modifier = Modifier,
|
|
||||||
title = {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
NonClickableUserPictures(
|
|
||||||
room = room,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
size = Size34dp,
|
|
||||||
)
|
|
||||||
|
|
||||||
RoomNameOnlyDisplay(room, Modifier.padding(start = 10.dp), FontWeight.Normal, accountViewModel)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
modifier = TitleIconModifier,
|
|
||||||
onClick = {
|
|
||||||
state.reset()
|
|
||||||
onCancel()
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
ArrowBackIcon()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
SendButton(
|
|
||||||
modifier = Modifier.padding(end = 5.dp),
|
|
||||||
onPost = upload,
|
|
||||||
isActive = state.canPost(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
colors =
|
|
||||||
TopAppBarDefaults.topAppBarColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
},
|
|
||||||
) { pad ->
|
RoomNameOnlyDisplay(room, Modifier.padding(start = 10.dp), FontWeight.Normal, accountViewModel)
|
||||||
Surface(
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(pad)
|
|
||||||
.consumeWindowInsets(pad)
|
|
||||||
.imePadding(),
|
|
||||||
) {
|
|
||||||
Column(Modifier.fillMaxSize().padding(start = 10.dp, end = 10.dp, bottom = 10.dp)) {
|
|
||||||
Column(Modifier.fillMaxWidth().verticalScroll(scrollState)) {
|
|
||||||
ImageVideoPostChat(state, accountViewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ImageVideoPostChat(
|
|
||||||
fileUploadState: ChatFileUploadState,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
) {
|
|
||||||
val fileServers by accountViewModel.account.liveServerList.collectAsState()
|
|
||||||
|
|
||||||
val fileServerOptions =
|
|
||||||
remember(fileServers) {
|
|
||||||
fileServers
|
|
||||||
.mapNotNull {
|
|
||||||
if (it.type != ServerType.NIP95) {
|
|
||||||
TitleExplainer(it.name, it.baseUrl)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}.toImmutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fileUploadState.multiOrchestrator?.let {
|
|
||||||
ShowImageUploadGallery(
|
|
||||||
it,
|
|
||||||
fileUploadState::deleteMediaToUpload,
|
|
||||||
accountViewModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
label = { Text(text = stringRes(R.string.content_description)) },
|
|
||||||
modifier = Modifier.fillMaxWidth().padding(top = 3.dp).height(150.dp),
|
|
||||||
maxLines = 10,
|
|
||||||
value = fileUploadState.caption,
|
|
||||||
onValueChange = { fileUploadState.caption = it },
|
|
||||||
placeholder = {
|
|
||||||
Text(
|
|
||||||
text = stringRes(R.string.content_description_example),
|
|
||||||
color = MaterialTheme.colorScheme.placeholderText,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
keyboardOptions =
|
upload = {
|
||||||
KeyboardOptions.Default.copy(
|
channelScreenModel.upload(
|
||||||
capitalization = KeyboardCapitalization.Sentences,
|
onError = accountViewModel::toast,
|
||||||
),
|
context = context,
|
||||||
)
|
onceUploaded = onUpload,
|
||||||
|
|
||||||
SettingSwitchItem(
|
|
||||||
title = R.string.add_sensitive_content_label,
|
|
||||||
description = R.string.add_sensitive_content_description,
|
|
||||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
|
||||||
checked = fileUploadState.contentWarning,
|
|
||||||
onCheckedChange = fileUploadState::updateContentWarning,
|
|
||||||
)
|
|
||||||
|
|
||||||
SettingsRow(R.string.file_server, R.string.file_server_description) {
|
|
||||||
TextSpinner(
|
|
||||||
label = "",
|
|
||||||
placeholder =
|
|
||||||
fileServers
|
|
||||||
.firstOrNull { it == accountViewModel.account.settings.defaultFileServer }
|
|
||||||
?.name
|
|
||||||
?: fileServers[0].name,
|
|
||||||
options = fileServerOptions,
|
|
||||||
onSelect = { fileUploadState.selectedServer = fileServers[it] },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(Size5dp),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringRes(R.string.media_compression_quality_label),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringRes(R.string.media_compression_quality_explainer),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = Color.Gray,
|
|
||||||
maxLines = 5,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Box(modifier = Modifier.fillMaxWidth()) {
|
|
||||||
Text(
|
|
||||||
text =
|
|
||||||
when (fileUploadState.mediaQualitySlider) {
|
|
||||||
0 -> stringRes(R.string.media_compression_quality_low)
|
|
||||||
1 -> stringRes(R.string.media_compression_quality_medium)
|
|
||||||
2 -> stringRes(R.string.media_compression_quality_high)
|
|
||||||
3 -> stringRes(R.string.media_compression_quality_uncompressed)
|
|
||||||
else -> stringRes(R.string.media_compression_quality_medium)
|
|
||||||
},
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
Slider(
|
if (state.selectedServer.type != ServerType.NIP95) {
|
||||||
value = fileUploadState.mediaQualitySlider.toFloat(),
|
accountViewModel.account.settings.changeDefaultFileServer(state.selectedServer)
|
||||||
onValueChange = { fileUploadState.mediaQualitySlider = it.toInt() },
|
}
|
||||||
valueRange = 0f..3f,
|
},
|
||||||
steps = 2,
|
onCancel,
|
||||||
)
|
accountViewModel,
|
||||||
}
|
nav,
|
||||||
}
|
)
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SendButton(
|
|
||||||
onPost: () -> Unit = {},
|
|
||||||
isActive: Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
modifier = modifier,
|
|
||||||
enabled = isActive,
|
|
||||||
onClick = onPost,
|
|
||||||
) {
|
|
||||||
Text(text = stringRes(R.string.accessibility_send))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
|||||||
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
||||||
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
|
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||||
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
|
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
|
||||||
|
|||||||
+142
@@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* 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.screen.loggedIn.chats.publicChannels.send
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
|
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||||
|
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadDialog
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChannelFileUploadDialog(
|
||||||
|
channelScreenModel: ChannelNewMessageViewModel,
|
||||||
|
state: ChatFileUploadState,
|
||||||
|
onUpload: () -> Unit,
|
||||||
|
onCancel: () -> Unit,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
val channel = channelScreenModel.channel ?: return
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
ChatFileUploadDialog(
|
||||||
|
state,
|
||||||
|
title = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
when (channel) {
|
||||||
|
is PublicChatChannel -> {
|
||||||
|
channel.profilePicture()?.let {
|
||||||
|
RobohashFallbackAsyncImage(
|
||||||
|
robot = channel.idHex,
|
||||||
|
model = it,
|
||||||
|
contentDescription = stringRes(R.string.profile_image),
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = HeaderPictureModifier,
|
||||||
|
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
||||||
|
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.padding(start = 10.dp)
|
||||||
|
.height(35.dp)
|
||||||
|
.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
text = channel.toBestDisplayName(),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is LiveActivitiesChannel -> {
|
||||||
|
channel.creator?.let {
|
||||||
|
UserPicture(
|
||||||
|
user = it,
|
||||||
|
size = Size34dp,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(start = 10.dp)
|
||||||
|
.height(35.dp)
|
||||||
|
.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
text = channel.toBestDisplayName(),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload = {
|
||||||
|
channelScreenModel.upload(
|
||||||
|
onError = accountViewModel::toast,
|
||||||
|
context = context,
|
||||||
|
onceUploaded = onUpload,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (state.selectedServer.type != ServerType.NIP95) {
|
||||||
|
accountViewModel.account.settings.changeDefaultFileServer(state.selectedServer)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel,
|
||||||
|
accountViewModel,
|
||||||
|
nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
+8
-1
@@ -54,7 +54,7 @@ import com.vitorpamplona.amethyst.ui.components.Split
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.UserSuggestions
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.UserSuggestions
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.ChatFileUploadState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
|
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
|
||||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||||
@@ -262,6 +262,8 @@ open class ChannelNewMessageViewModel : ViewModel() {
|
|||||||
|
|
||||||
message = TextFieldValue(draftEvent.content)
|
message = TextFieldValue(draftEvent.content)
|
||||||
|
|
||||||
|
iMetaAttachments.addAll(draftEvent.imetas())
|
||||||
|
|
||||||
urlPreview = findUrlInMessage()
|
urlPreview = findUrlInMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,6 +309,7 @@ open class ChannelNewMessageViewModel : ViewModel() {
|
|||||||
fun upload(
|
fun upload(
|
||||||
onError: (title: String, message: String) -> Unit,
|
onError: (title: String, message: String) -> Unit,
|
||||||
context: Context,
|
context: Context,
|
||||||
|
onceUploaded: () -> Unit,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.Default) {
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
val myAccount = account ?: return@launch
|
val myAccount = account ?: return@launch
|
||||||
@@ -349,6 +352,8 @@ open class ChannelNewMessageViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uploadState.reset()
|
uploadState.reset()
|
||||||
|
onceUploaded()
|
||||||
|
saveDraft()
|
||||||
} else {
|
} else {
|
||||||
val errorMessages = results.errors.map { stringRes(context, it.errorResource, *it.params) }.distinct()
|
val errorMessages = results.errors.map { stringRes(context, it.errorResource, *it.params) }.distinct()
|
||||||
|
|
||||||
@@ -494,6 +499,8 @@ open class ChannelNewMessageViewModel : ViewModel() {
|
|||||||
userSuggestions.reset()
|
userSuggestions.reset()
|
||||||
userSuggestionsMainMessage = null
|
userSuggestionsMainMessage = null
|
||||||
|
|
||||||
|
iMetaAttachments.reset()
|
||||||
|
|
||||||
if (emojiSearch.value.isNotEmpty()) {
|
if (emojiSearch.value.isNotEmpty()) {
|
||||||
emojiSearch.tryEmit("")
|
emojiSearch.tryEmit("")
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -81,6 +81,19 @@ fun EditFieldRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channelScreenModel.uploadState?.let { uploading ->
|
||||||
|
uploading.multiOrchestrator?.let { selectedFiles ->
|
||||||
|
ChannelFileUploadDialog(
|
||||||
|
channelScreenModel = channelScreenModel,
|
||||||
|
state = uploading,
|
||||||
|
onUpload = onSendNewMessage,
|
||||||
|
onCancel = uploading::reset,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = EditFieldModifier,
|
modifier = EditFieldModifier,
|
||||||
) {
|
) {
|
||||||
|
|||||||
+267
@@ -0,0 +1,267 @@
|
|||||||
|
/**
|
||||||
|
* 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.screen.loggedIn.chats.utils
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.TitleIconModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.rememberHeightDecreaser
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SettingSwitchItem
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ChatFileUploadDialog(
|
||||||
|
state: ChatFileUploadState,
|
||||||
|
title: @Composable () -> Unit,
|
||||||
|
upload: () -> Unit,
|
||||||
|
onCancel: () -> Unit,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = { onCancel() },
|
||||||
|
properties =
|
||||||
|
DialogProperties(
|
||||||
|
usePlatformDefaultWidth = false,
|
||||||
|
dismissOnClickOutside = false,
|
||||||
|
decorFitsSystemWindows = false,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
SetDialogToEdgeToEdge()
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
scrollBehavior = rememberHeightDecreaser(),
|
||||||
|
modifier = Modifier,
|
||||||
|
title = title,
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(
|
||||||
|
modifier = TitleIconModifier,
|
||||||
|
onClick = {
|
||||||
|
state.reset()
|
||||||
|
onCancel()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ArrowBackIcon()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
SendButton(
|
||||||
|
modifier = Modifier.padding(end = 5.dp),
|
||||||
|
onPost = upload,
|
||||||
|
isActive = state.canPost(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors =
|
||||||
|
TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { pad ->
|
||||||
|
Surface(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(pad)
|
||||||
|
.consumeWindowInsets(pad)
|
||||||
|
.imePadding(),
|
||||||
|
) {
|
||||||
|
Column(Modifier.fillMaxSize().padding(start = 10.dp, end = 10.dp, bottom = 10.dp)) {
|
||||||
|
Column(Modifier.fillMaxWidth().verticalScroll(scrollState)) {
|
||||||
|
ImageVideoPostChat(state, accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ImageVideoPostChat(
|
||||||
|
fileUploadState: ChatFileUploadState,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) {
|
||||||
|
val fileServers by accountViewModel.account.liveServerList.collectAsState()
|
||||||
|
|
||||||
|
val fileServerOptions =
|
||||||
|
remember(fileServers) {
|
||||||
|
fileServers
|
||||||
|
.mapNotNull {
|
||||||
|
if (it.type != ServerType.NIP95) {
|
||||||
|
TitleExplainer(it.name, it.baseUrl)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.toImmutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fileUploadState.multiOrchestrator?.let {
|
||||||
|
ShowImageUploadGallery(
|
||||||
|
it,
|
||||||
|
fileUploadState::deleteMediaToUpload,
|
||||||
|
accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
label = { Text(text = stringRes(R.string.content_description)) },
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(top = 3.dp).height(150.dp),
|
||||||
|
maxLines = 10,
|
||||||
|
value = fileUploadState.caption,
|
||||||
|
onValueChange = { fileUploadState.caption = it },
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.content_description_example),
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
keyboardOptions =
|
||||||
|
KeyboardOptions.Default.copy(
|
||||||
|
capitalization = KeyboardCapitalization.Sentences,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingSwitchItem(
|
||||||
|
title = R.string.add_sensitive_content_label,
|
||||||
|
description = R.string.add_sensitive_content_description,
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||||
|
checked = fileUploadState.contentWarning,
|
||||||
|
onCheckedChange = fileUploadState::updateContentWarning,
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsRow(R.string.file_server, R.string.file_server_description) {
|
||||||
|
TextSpinner(
|
||||||
|
label = "",
|
||||||
|
placeholder =
|
||||||
|
fileServers
|
||||||
|
.firstOrNull { it == accountViewModel.account.settings.defaultFileServer }
|
||||||
|
?.name
|
||||||
|
?: fileServers[0].name,
|
||||||
|
options = fileServerOptions,
|
||||||
|
onSelect = { fileUploadState.selectedServer = fileServers[it] },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Size5dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.media_compression_quality_label),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.media_compression_quality_explainer),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray,
|
||||||
|
maxLines = 5,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Text(
|
||||||
|
text =
|
||||||
|
when (fileUploadState.mediaQualitySlider) {
|
||||||
|
0 -> stringRes(R.string.media_compression_quality_low)
|
||||||
|
1 -> stringRes(R.string.media_compression_quality_medium)
|
||||||
|
2 -> stringRes(R.string.media_compression_quality_high)
|
||||||
|
3 -> stringRes(R.string.media_compression_quality_uncompressed)
|
||||||
|
else -> stringRes(R.string.media_compression_quality_medium)
|
||||||
|
},
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider(
|
||||||
|
value = fileUploadState.mediaQualitySlider.toFloat(),
|
||||||
|
onValueChange = { fileUploadState.mediaQualitySlider = it.toInt() },
|
||||||
|
valueRange = 0f..3f,
|
||||||
|
steps = 2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SendButton(
|
||||||
|
onPost: () -> Unit = {},
|
||||||
|
isActive: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
modifier = modifier,
|
||||||
|
enabled = isActive,
|
||||||
|
onClick = onPost,
|
||||||
|
) {
|
||||||
|
Text(text = stringRes(R.string.accessibility_send))
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* 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.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -81,6 +81,13 @@
|
|||||||
<string name="thank_you_so_much">Děkuji moc!</string>
|
<string name="thank_you_so_much">Děkuji moc!</string>
|
||||||
<string name="amount_in_sats">Částka v sats</string>
|
<string name="amount_in_sats">Částka v sats</string>
|
||||||
<string name="send_sats">Poslat sats</string>
|
<string name="send_sats">Poslat sats</string>
|
||||||
|
<string name="secret_emoji_maker">Tajný tvůrce emoji</string>
|
||||||
|
<string name="secret_emoji_maker_explainer">Přidejte emoji se skrytou zprávou</string>
|
||||||
|
<string name="secret_note_to_receiver">Tajná poznámka pro příjemce</string>
|
||||||
|
<string name="secret_note_to_receiver_placeholder">Moje skrytá zpráva</string>
|
||||||
|
<string name="secret_visible_text">Viditelná předpona</string>
|
||||||
|
<string name="secret_visible_text_placeholder">😎</string>
|
||||||
|
<string name="secret_add_to_text">Přidat k příspěvku</string>
|
||||||
<string name="error_parsing_preview_for">"Chyba při zpracování náhledu pro %1$s : %2$s"</string>
|
<string name="error_parsing_preview_for">"Chyba při zpracování náhledu pro %1$s : %2$s"</string>
|
||||||
<string name="preview_card_image_for">"Náhled obrázku karty pro %1$s"</string>
|
<string name="preview_card_image_for">"Náhled obrázku karty pro %1$s"</string>
|
||||||
<string name="new_channel">Nový kanál</string>
|
<string name="new_channel">Nový kanál</string>
|
||||||
|
|||||||
@@ -81,6 +81,13 @@
|
|||||||
<string name="thank_you_so_much">Vielen Dank!</string>
|
<string name="thank_you_so_much">Vielen Dank!</string>
|
||||||
<string name="amount_in_sats">Betrag in Sats</string>
|
<string name="amount_in_sats">Betrag in Sats</string>
|
||||||
<string name="send_sats">Sats senden</string>
|
<string name="send_sats">Sats senden</string>
|
||||||
|
<string name="secret_emoji_maker">Geheimer Emoji-Maker</string>
|
||||||
|
<string name="secret_emoji_maker_explainer">Füge einen Emoji mit versteckter Nachricht hinzu</string>
|
||||||
|
<string name="secret_note_to_receiver">Geheime Notiz an Empfänger</string>
|
||||||
|
<string name="secret_note_to_receiver_placeholder">Meine versteckte Nachricht</string>
|
||||||
|
<string name="secret_visible_text">Sichtbares Präfix</string>
|
||||||
|
<string name="secret_visible_text_placeholder">😎</string>
|
||||||
|
<string name="secret_add_to_text">Zum Beitrag hinzufügen</string>
|
||||||
<string name="error_parsing_preview_for">"Fehler beim Parsen der Vorschau für %1$s: %2$s"</string>
|
<string name="error_parsing_preview_for">"Fehler beim Parsen der Vorschau für %1$s: %2$s"</string>
|
||||||
<string name="preview_card_image_for">"Vorschaukartenbild für %1$s"</string>
|
<string name="preview_card_image_for">"Vorschaukartenbild für %1$s"</string>
|
||||||
<string name="new_channel">Neuer Kanal</string>
|
<string name="new_channel">Neuer Kanal</string>
|
||||||
|
|||||||
@@ -81,6 +81,13 @@
|
|||||||
<string name="thank_you_so_much">Obrigado!</string>
|
<string name="thank_you_so_much">Obrigado!</string>
|
||||||
<string name="amount_in_sats">Quantidade em Sats</string>
|
<string name="amount_in_sats">Quantidade em Sats</string>
|
||||||
<string name="send_sats">Enviar Sats</string>
|
<string name="send_sats">Enviar Sats</string>
|
||||||
|
<string name="secret_emoji_maker">Criador de Emoji Secreto</string>
|
||||||
|
<string name="secret_emoji_maker_explainer">Adicionar um emoji com mensagem oculta na postagem</string>
|
||||||
|
<string name="secret_note_to_receiver">Nota Secreta para o Receptor</string>
|
||||||
|
<string name="secret_note_to_receiver_placeholder">Minha mensagem oculta</string>
|
||||||
|
<string name="secret_visible_text">Prefixo Visível</string>
|
||||||
|
<string name="secret_visible_text_placeholder">😎</string>
|
||||||
|
<string name="secret_add_to_text">Adicionar à postagem</string>
|
||||||
<string name="error_parsing_preview_for">"Erro ao analisar a visualização para %1$s: %2$s"</string>
|
<string name="error_parsing_preview_for">"Erro ao analisar a visualização para %1$s: %2$s"</string>
|
||||||
<string name="preview_card_image_for">"Visualize a imagem do cartão para %1$s"</string>
|
<string name="preview_card_image_for">"Visualize a imagem do cartão para %1$s"</string>
|
||||||
<string name="new_channel">Novo Canal</string>
|
<string name="new_channel">Novo Canal</string>
|
||||||
|
|||||||
@@ -81,6 +81,13 @@
|
|||||||
<string name="thank_you_so_much">Tack så mycket!</string>
|
<string name="thank_you_so_much">Tack så mycket!</string>
|
||||||
<string name="amount_in_sats">Summa i Sats</string>
|
<string name="amount_in_sats">Summa i Sats</string>
|
||||||
<string name="send_sats">Skicka Sats</string>
|
<string name="send_sats">Skicka Sats</string>
|
||||||
|
<string name="secret_emoji_maker">Hemlig emoji-skapare</string>
|
||||||
|
<string name="secret_emoji_maker_explainer">Lägg till en emoji med dolt meddelande</string>
|
||||||
|
<string name="secret_note_to_receiver">Hemlig anteckning till mottagaren</string>
|
||||||
|
<string name="secret_note_to_receiver_placeholder">Mitt dolda inlägg</string>
|
||||||
|
<string name="secret_visible_text">Synligt Prefix</string>
|
||||||
|
<string name="secret_visible_text_placeholder">😎</string>
|
||||||
|
<string name="secret_add_to_text">Lägg till inlägg</string>
|
||||||
<string name="error_parsing_preview_for">"Fel vid analys av förhandsvisning för %1$s : %2$s"</string>
|
<string name="error_parsing_preview_for">"Fel vid analys av förhandsvisning för %1$s : %2$s"</string>
|
||||||
<string name="preview_card_image_for">"Förhandsgranska kortbild för %1$s"</string>
|
<string name="preview_card_image_for">"Förhandsgranska kortbild för %1$s"</string>
|
||||||
<string name="new_channel">Ny kanal</string>
|
<string name="new_channel">Ny kanal</string>
|
||||||
|
|||||||
@@ -23,3 +23,5 @@ package com.vitorpamplona.quartz.nip10Notes.content
|
|||||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||||
|
|
||||||
fun findNostrUris(content: String) = Nip19Parser.parseAll(content)
|
fun findNostrUris(content: String) = Nip19Parser.parseAll(content)
|
||||||
|
|
||||||
|
fun findNostrEventUris(content: String) = Nip19Parser.parseAllEvents(content)
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ object Nip19Parser {
|
|||||||
Pattern.CASE_INSENSITIVE,
|
Pattern.CASE_INSENSITIVE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val nip19regexEvents: Pattern =
|
||||||
|
Pattern.compile(
|
||||||
|
"(nostr:)?@?(nevent1|naddr1|note1|nrelay1|nembed1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)([\\S]*)",
|
||||||
|
Pattern.CASE_INSENSITIVE,
|
||||||
|
)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ParseReturn(
|
data class ParseReturn(
|
||||||
val entity: Entity,
|
val entity: Entity,
|
||||||
@@ -130,8 +136,11 @@ object Nip19Parser {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseAll(content: String): List<Entity> {
|
fun parseAll(
|
||||||
val matcher2 = nip19regex.matcher(content)
|
content: String,
|
||||||
|
regex: Pattern,
|
||||||
|
): List<Entity> {
|
||||||
|
val matcher2 = regex.matcher(content)
|
||||||
val returningList = mutableListOf<Entity>()
|
val returningList = mutableListOf<Entity>()
|
||||||
while (matcher2.find()) {
|
while (matcher2.find()) {
|
||||||
val type = matcher2.group(2) // npub1
|
val type = matcher2.group(2) // npub1
|
||||||
@@ -139,7 +148,7 @@ object Nip19Parser {
|
|||||||
val additionalChars = matcher2.group(4) // additional chars
|
val additionalChars = matcher2.group(4) // additional chars
|
||||||
|
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
val parsed = Nip19Parser.parseComponents(type, key, additionalChars)?.entity
|
val parsed = parseComponents(type, key, additionalChars)?.entity
|
||||||
|
|
||||||
if (parsed != null) {
|
if (parsed != null) {
|
||||||
returningList.add(parsed)
|
returningList.add(parsed)
|
||||||
@@ -148,6 +157,10 @@ object Nip19Parser {
|
|||||||
}
|
}
|
||||||
return returningList
|
return returningList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun parseAll(content: String): List<Entity> = parseAll(content, nip19regex)
|
||||||
|
|
||||||
|
fun parseAllEvents(content: String): List<Entity> = parseAll(content, nip19regexEvents)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decodePublicKey(key: String): ByteArray =
|
fun decodePublicKey(key: String): ByteArray =
|
||||||
|
|||||||
+1
-1
@@ -81,7 +81,7 @@ class BloomFilterMurMur3Test {
|
|||||||
failureCounter++
|
failureCounter++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertEquals(0, failureCounter)
|
assertTrue(failureCounter <= 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user