mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-08 23:54:39 +00:00
@@ -136,6 +136,7 @@ import com.vitorpamplona.quartz.nip30CustomEmoji.selection.EmojiPackSelectionEve
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.taggedEmojis
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
|
||||
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarning
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftBuilder
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
|
||||
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
@@ -2103,6 +2104,27 @@ class Account(
|
||||
Amethyst.instance.client.send(signedEvent, relayList = relayList)
|
||||
}
|
||||
|
||||
fun sendNip95Privately(
|
||||
data: FileStorageEvent,
|
||||
signedEvent: FileStorageHeaderEvent,
|
||||
relayList: List<String>,
|
||||
) {
|
||||
val connect =
|
||||
relayList.map {
|
||||
val normalizedUrl = RelayUrlFormatter.normalize(it)
|
||||
RelaySetupInfoToConnect(
|
||||
normalizedUrl,
|
||||
shouldUseTorForClean(normalizedUrl),
|
||||
true,
|
||||
true,
|
||||
setOf(FeedType.GLOBAL),
|
||||
)
|
||||
}
|
||||
|
||||
Amethyst.instance.client.sendPrivately(data, relayList = connect)
|
||||
Amethyst.instance.client.sendPrivately(signedEvent, relayList = connect)
|
||||
}
|
||||
|
||||
fun sendHeader(
|
||||
signedEvent: Event,
|
||||
relayList: List<RelaySetupInfo>,
|
||||
@@ -2286,6 +2308,29 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Event> signAndSendPrivately(
|
||||
template: EventTemplate<T>,
|
||||
relayList: List<String>,
|
||||
) {
|
||||
signer.sign(template) {
|
||||
LocalCache.justConsume(it, null)
|
||||
Amethyst.instance.client.sendPrivately(it, relayList = convertRelayList(relayList))
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Event> signAndSend(
|
||||
template: EventTemplate<T>,
|
||||
relayList: List<RelaySetupInfo>,
|
||||
broadcastNotes: Set<Note>,
|
||||
) {
|
||||
signer.sign(template) {
|
||||
LocalCache.justConsume(it, null)
|
||||
Amethyst.instance.client.send(it, relayList = relayList)
|
||||
|
||||
broadcastNotes.forEach { it.event?.let { Amethyst.instance.client.send(it, relayList = relayList) } }
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Event> signAndSend(
|
||||
draftTag: String?,
|
||||
template: EventTemplate<T>,
|
||||
@@ -2359,6 +2404,16 @@ class Account(
|
||||
signAndSend(draftTag, template, relayList, broadcastNotes)
|
||||
}
|
||||
|
||||
fun createAndSendDraft(
|
||||
draftTag: String,
|
||||
template: EventTemplate<out Event>,
|
||||
) {
|
||||
val rumor = signer.assembleRumor(template)
|
||||
DraftBuilder.encryptAndSign(draftTag, rumor, signer) { draftEvent ->
|
||||
sendDraftEvent(draftEvent)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteDraft(draftTag: String) {
|
||||
val key = DraftEvent.createAddressTag(userProfile().pubkeyHex, draftTag)
|
||||
LocalCache.getAddressableNoteIfExists(key)?.let { note ->
|
||||
@@ -2682,6 +2737,18 @@ class Account(
|
||||
LocalCache.justConsume(draftEvent, null)
|
||||
}
|
||||
|
||||
fun convertRelayList(broadcast: List<String>): List<RelaySetupInfoToConnect> =
|
||||
broadcast.map {
|
||||
val normalizedUrl = RelayUrlFormatter.normalize(it)
|
||||
RelaySetupInfoToConnect(
|
||||
normalizedUrl,
|
||||
shouldUseTorForClean(normalizedUrl),
|
||||
true,
|
||||
true,
|
||||
setOf(FeedType.GLOBAL),
|
||||
)
|
||||
}
|
||||
|
||||
fun broadcastPrivately(signedEvents: NIP17Factory.Result) {
|
||||
val mine = signedEvents.wraps.filter { (it.recipientPubKey() == signer.pubKey) }
|
||||
|
||||
|
||||
@@ -2412,9 +2412,12 @@ object LocalCache {
|
||||
}
|
||||
is ChannelMessageEvent -> {
|
||||
draft.channelId()?.let { channelId ->
|
||||
checkGetOrCreateChannel(channelId)?.let { channel ->
|
||||
channel.addNote(note, null)
|
||||
}
|
||||
checkGetOrCreateChannel(channelId)?.addNote(note, null)
|
||||
}
|
||||
}
|
||||
is LiveActivitiesChatMessageEvent -> {
|
||||
draft.activityAddress()?.let { channelId ->
|
||||
checkGetOrCreateChannel(channelId.toValue())?.addNote(note, null)
|
||||
}
|
||||
}
|
||||
is TextNoteEvent -> {
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ import com.vitorpamplona.amethyst.ui.note.SearchIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.ChannelName
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChannelName
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchBarViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.actions
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
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.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun NewChannelView(
|
||||
onClose: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
channel: PublicChatChannel? = null,
|
||||
) {
|
||||
val postViewModel: NewChannelViewModel = viewModel()
|
||||
postViewModel.load(accountViewModel.account, channel)
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = { onClose() },
|
||||
properties =
|
||||
DialogProperties(
|
||||
dismissOnClickOutside = false,
|
||||
),
|
||||
) {
|
||||
Surface {
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp).verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CloseButton(
|
||||
onPress = {
|
||||
postViewModel.clear()
|
||||
onClose()
|
||||
},
|
||||
)
|
||||
|
||||
PostButton(
|
||||
onPost = {
|
||||
postViewModel.create()
|
||||
onClose()
|
||||
},
|
||||
postViewModel.channelName.value.text
|
||||
.isNotBlank(),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(15.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringRes(R.string.channel_name)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.channelName.value,
|
||||
onValueChange = { postViewModel.channelName.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.my_awesome_group),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
),
|
||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(15.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringRes(R.string.picture_url)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.channelPicture.value,
|
||||
onValueChange = { postViewModel.channelPicture.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "http://mygroup.com/logo.jpg",
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(15.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringRes(R.string.description)) },
|
||||
modifier = Modifier.fillMaxWidth().height(100.dp),
|
||||
value = postViewModel.channelDescription.value,
|
||||
onValueChange = { postViewModel.channelDescription.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.about_us),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
),
|
||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
maxLines = 10,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -62,7 +62,7 @@ import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -94,7 +93,7 @@ import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.CashuCardBorders
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
|
||||
|
||||
@@ -41,7 +41,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.NewItemsBubble
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.NewItemsBubble
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
|
||||
@@ -60,10 +60,10 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadRedirectScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NewPostScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarks.BookmarkListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.MessagesScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomByAuthorScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.ChannelScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ChannelScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.MessagesScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.CommunityScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.DiscoverScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.drafts.DraftListScreen
|
||||
|
||||
@@ -78,11 +78,11 @@ import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.BannerImage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.ChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.EndedFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.LiveFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.OfflineFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.ScheduledFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.EndedFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.LiveFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.OfflineFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ScheduledFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.observeAppDefinition
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CheckIfVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
|
||||
|
||||
@@ -123,7 +123,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderTorrentComment
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderWikiContent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.VideoDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.RenderChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.RenderChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font12SP
|
||||
|
||||
@@ -44,7 +44,7 @@ import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
|
||||
|
||||
@@ -23,12 +23,18 @@ package com.vitorpamplona.amethyst.ui.note.elements
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgoShort
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
@@ -48,3 +54,21 @@ fun TimeAgo(time: Long) {
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NormalTimeAgo(
|
||||
baseNote: Note,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val nowStr = stringRes(id = R.string.now)
|
||||
|
||||
val time by
|
||||
remember(baseNote) { derivedStateOf { timeAgoShort(baseNote.createdAt() ?: 0, nowStr) } }
|
||||
|
||||
Text(
|
||||
text = time,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.ChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
|
||||
+1
-92
@@ -29,37 +29,21 @@ import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
|
||||
import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlImage
|
||||
import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlVideo
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderEncryptedFile
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.header.ChatroomHeader
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.encryption.AESGCM
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
fun RenderChatMessageEncryptedFile(
|
||||
@@ -99,78 +83,3 @@ fun RenderChatMessageEncryptedFile(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderEncryptedFile(
|
||||
note: Note,
|
||||
backgroundBubbleColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = note.event as? ChatMessageEncryptedFileHeaderEvent ?: return
|
||||
|
||||
val algo = noteEvent.algo()
|
||||
val key = noteEvent.key()
|
||||
val nonce = noteEvent.nonce()
|
||||
val mimeType = noteEvent.mimeType()
|
||||
|
||||
if (algo == AESGCM.NAME && key != null && nonce != null) {
|
||||
HttpClientManager.addCipherToCache(noteEvent.content, AESGCM(key, nonce), mimeType)
|
||||
|
||||
val content by remember(noteEvent) {
|
||||
val isImage = mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(noteEvent.content)
|
||||
|
||||
mutableStateOf<BaseMediaContent>(
|
||||
if (isImage) {
|
||||
EncryptedMediaUrlImage(
|
||||
url = noteEvent.content,
|
||||
description = noteEvent.alt(),
|
||||
hash = noteEvent.originalHash(),
|
||||
blurhash = noteEvent.blurhash(),
|
||||
dim = noteEvent.dimensions(),
|
||||
uri = note.toNostrUri(),
|
||||
mimeType = mimeType,
|
||||
encryptionAlgo = algo,
|
||||
encryptionKey = key,
|
||||
encryptionNonce = nonce,
|
||||
)
|
||||
} else {
|
||||
EncryptedMediaUrlVideo(
|
||||
url = noteEvent.content,
|
||||
description = noteEvent.alt(),
|
||||
hash = noteEvent.originalHash(),
|
||||
blurhash = noteEvent.blurhash(),
|
||||
dim = noteEvent.dimensions(),
|
||||
uri = note.toNostrUri(),
|
||||
authorName = note.author?.toBestDisplayName(),
|
||||
mimeType = mimeType,
|
||||
encryptionAlgo = algo,
|
||||
encryptionKey = key,
|
||||
encryptionNonce = nonce,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
ZoomableContentView(
|
||||
content,
|
||||
persistentListOf(content),
|
||||
roundedCorner = true,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
accountViewModel,
|
||||
)
|
||||
} else {
|
||||
TranslatableRichTextViewer(
|
||||
content = stringRes(id = R.string.could_not_decrypt_the_message),
|
||||
canPreview = true,
|
||||
quotesLeft = 0,
|
||||
modifier = Modifier,
|
||||
tags = EmptyTagList,
|
||||
backgroundColor = backgroundBubbleColor,
|
||||
id = note.idHex,
|
||||
callbackUri = note.toNostrUri(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -36,9 +38,11 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -60,12 +64,12 @@ import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.ZapReaction
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.NormalTimeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.JoinCommunityButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.LeaveCommunityButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.NormalTimeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
||||
@@ -82,6 +86,8 @@ import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefiniti
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.ModeratorTag
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
@@ -359,3 +365,47 @@ fun WatchAddressableNoteFollows(
|
||||
|
||||
onFollowChanges(state.addresses.contains(note.idHex))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun JoinCommunityButton(
|
||||
accountViewModel: AccountViewModel,
|
||||
note: AddressableNote,
|
||||
nav: INav,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = { scope.launch(Dispatchers.IO) { accountViewModel.account.follow(note) } },
|
||||
shape = ButtonBorder,
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
contentPadding = ButtonPadding,
|
||||
) {
|
||||
Text(text = stringRes(R.string.join), color = Color.White)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LeaveCommunityButton(
|
||||
accountViewModel: AccountViewModel,
|
||||
note: AddressableNote,
|
||||
nav: INav,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = { scope.launch(Dispatchers.IO) { accountViewModel.account.unfollow(note) } },
|
||||
shape = ButtonBorder,
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
contentPadding = ButtonPadding,
|
||||
) {
|
||||
Text(text = stringRes(R.string.leave), color = Color.White)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.LiveFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.ScheduledFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.LiveFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ScheduledFlag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CheckIfVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CrossfadeCheckIfVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.ChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
|
||||
@@ -80,7 +80,7 @@ fun DisplayPeopleList(
|
||||
members.take(3)
|
||||
}
|
||||
|
||||
val name by remember { derivedStateOf { "#${noteEvent.dTag()}" } }
|
||||
val name by remember { derivedStateOf { "#${noteEvent.nameOrTitle() ?: noteEvent.dTag()}" } }
|
||||
|
||||
Text(
|
||||
text = name,
|
||||
|
||||
+11
@@ -79,6 +79,7 @@ import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||
@@ -860,6 +861,14 @@ class AccountViewModel(
|
||||
account.decryptZapContentAuthor(note, onReady)
|
||||
}
|
||||
|
||||
fun follow(channel: Channel) {
|
||||
viewModelScope.launch(Dispatchers.IO) { account.follow(channel) }
|
||||
}
|
||||
|
||||
fun unfollow(channel: Channel) {
|
||||
viewModelScope.launch(Dispatchers.IO) { account.unfollow(channel) }
|
||||
}
|
||||
|
||||
fun follow(user: User) {
|
||||
viewModelScope.launch(Dispatchers.IO) { account.follow(user) }
|
||||
}
|
||||
@@ -1054,6 +1063,8 @@ class AccountViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Event> createRumor(template: EventTemplate<T>) = account.signer.assembleRumor<T>(template)
|
||||
|
||||
fun retrieveRelayDocument(
|
||||
dirtyUrl: String,
|
||||
onInfo: (Nip11RelayInformation) -> Unit,
|
||||
|
||||
+5
-39
@@ -18,7 +18,7 @@
|
||||
* 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.privateDM.feed
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed
|
||||
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -28,10 +28,8 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
||||
@@ -40,14 +38,10 @@ import com.vitorpamplona.amethyst.ui.feeds.FeedState
|
||||
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.dateFormatter
|
||||
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.messages.ChatroomMessageCompose
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.quartz.nip14Subject.subject
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
|
||||
|
||||
@Composable
|
||||
@@ -64,7 +58,7 @@ fun RefreshingChatroomFeedView(
|
||||
) {
|
||||
RefresheableBox(viewModel, enablePullRefresh) {
|
||||
SaveableFeedState(viewModel, scrollStateKey) { listState ->
|
||||
RenderChatroomFeedView(
|
||||
RenderChatFeedView(
|
||||
viewModel,
|
||||
accountViewModel,
|
||||
listState,
|
||||
@@ -79,7 +73,7 @@ fun RefreshingChatroomFeedView(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderChatroomFeedView(
|
||||
fun RenderChatFeedView(
|
||||
viewModel: FeedViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
listState: LazyListState,
|
||||
@@ -97,7 +91,7 @@ fun RenderChatroomFeedView(
|
||||
is FeedState.Empty -> FeedEmpty { viewModel.invalidateData() }
|
||||
is FeedState.FeedError -> FeedError(state.errorMessage) { viewModel.invalidateData() }
|
||||
is FeedState.Loaded ->
|
||||
ChatroomFeedLoaded(
|
||||
ChatFeedLoaded(
|
||||
state,
|
||||
accountViewModel,
|
||||
listState,
|
||||
@@ -112,7 +106,7 @@ fun RenderChatroomFeedView(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatroomFeedLoaded(
|
||||
fun ChatFeedLoaded(
|
||||
loaded: FeedState.Loaded,
|
||||
accountViewModel: AccountViewModel,
|
||||
listState: LazyListState,
|
||||
@@ -153,31 +147,3 @@ fun ChatroomFeedLoaded(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NewDateOrSubjectDivisor(
|
||||
previous: Note?,
|
||||
note: Note,
|
||||
) {
|
||||
if (previous == null) return
|
||||
|
||||
val never = stringRes(R.string.never)
|
||||
val today = stringRes(R.string.today)
|
||||
|
||||
val prevDate = remember(previous) { dateFormatter(previous.event?.createdAt, never, today) }
|
||||
val date = remember(note) { dateFormatter(note.event?.createdAt, never, today) }
|
||||
|
||||
val subject = remember(note) { note.event?.subject() }
|
||||
|
||||
if (prevDate != date) {
|
||||
if (subject != null) {
|
||||
ChatDivisor("$date - $subject")
|
||||
} else {
|
||||
ChatDivisor(date)
|
||||
}
|
||||
} else {
|
||||
if (subject != null) {
|
||||
ChatDivisor(subject)
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-304
@@ -18,70 +18,48 @@
|
||||
* 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.privateDM.messages
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.produceState
|
||||
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.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayDraftChat
|
||||
import com.vitorpamplona.amethyst.ui.note.FollowingIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.InnerUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.LikeReaction
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContentOrNull
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteQuickActionMenu
|
||||
import com.vitorpamplona.amethyst.ui.note.ObserveDraftEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.RelayBadgesHorizontal
|
||||
import com.vitorpamplona.amethyst.ui.note.ReplyReaction
|
||||
import com.vitorpamplona.amethyst.ui.note.WatchBlockAndReport
|
||||
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.WatchUserFollows
|
||||
import com.vitorpamplona.amethyst.ui.note.ZapReaction
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgoShort
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderEncryptedFile
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.layouts.ChatBubbleLayout
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderChangeChannelMetadataNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderCreateChannelNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderDraftEvent
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderEncryptedFile
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderRegularTextNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.IncognitoBadge
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font12SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size18Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.chatAuthorBox
|
||||
import com.vitorpamplona.amethyst.ui.theme.incognitoIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.NIP17Group
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
@@ -224,11 +202,11 @@ fun NormalChatNote(
|
||||
}
|
||||
}
|
||||
},
|
||||
) { backgroundBubbleColor ->
|
||||
) { bgColor ->
|
||||
MessageBubbleLines(
|
||||
note,
|
||||
innerQuote,
|
||||
backgroundBubbleColor,
|
||||
bgColor,
|
||||
onWantsToReply,
|
||||
onWantsToEditDraft,
|
||||
canPreview,
|
||||
@@ -242,7 +220,7 @@ fun NormalChatNote(
|
||||
private fun MessageBubbleLines(
|
||||
baseNote: Note,
|
||||
innerQuote: Boolean,
|
||||
backgroundBubbleColor: MutableState<Color>,
|
||||
bgColor: MutableState<Color>,
|
||||
onWantsToReply: (Note) -> Unit,
|
||||
onWantsToEditDraft: (Note) -> Unit,
|
||||
canPreview: Boolean,
|
||||
@@ -253,7 +231,7 @@ private fun MessageBubbleLines(
|
||||
RenderReplyRow(
|
||||
note = baseNote,
|
||||
innerQuote = innerQuote,
|
||||
backgroundBubbleColor = backgroundBubbleColor,
|
||||
bgColor = bgColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
onWantsToReply = onWantsToReply,
|
||||
@@ -267,31 +245,31 @@ private fun MessageBubbleLines(
|
||||
innerQuote = innerQuote,
|
||||
onWantsToReply = onWantsToReply,
|
||||
onWantsToEditDraft = onWantsToEditDraft,
|
||||
backgroundBubbleColor = backgroundBubbleColor,
|
||||
bgColor = bgColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderReplyRow(
|
||||
fun RenderReplyRow(
|
||||
note: Note,
|
||||
innerQuote: Boolean,
|
||||
backgroundBubbleColor: MutableState<Color>,
|
||||
bgColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
onWantsToReply: (Note) -> Unit,
|
||||
onWantsToEditDraft: (Note) -> Unit,
|
||||
) {
|
||||
if (!innerQuote && note.replyTo?.lastOrNull() != null) {
|
||||
RenderReply(note, backgroundBubbleColor, accountViewModel, nav, onWantsToReply, onWantsToEditDraft)
|
||||
RenderReply(note, bgColor, accountViewModel, nav, onWantsToReply, onWantsToEditDraft)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderReply(
|
||||
note: Note,
|
||||
backgroundBubbleColor: MutableState<Color>,
|
||||
bgColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
onWantsToReply: (Note) -> Unit,
|
||||
@@ -311,7 +289,7 @@ private fun RenderReply(
|
||||
baseNote = note,
|
||||
routeForLastRead = null,
|
||||
innerQuote = true,
|
||||
parentBackgroundColor = backgroundBubbleColor,
|
||||
parentBackgroundColor = bgColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
onWantsToReply = onWantsToReply,
|
||||
@@ -322,280 +300,23 @@ private fun RenderReply(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NoteRow(
|
||||
fun NoteRow(
|
||||
note: Note,
|
||||
canPreview: Boolean,
|
||||
innerQuote: Boolean,
|
||||
onWantsToReply: (Note) -> Unit,
|
||||
onWantsToEditDraft: (Note) -> Unit,
|
||||
backgroundBubbleColor: MutableState<Color>,
|
||||
bgColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
when (note.event) {
|
||||
is ChannelCreateEvent -> RenderCreateChannelNote(note)
|
||||
is ChannelMetadataEvent -> RenderChangeChannelMetadataNote(note)
|
||||
is DraftEvent ->
|
||||
RenderDraftEvent(
|
||||
note,
|
||||
canPreview,
|
||||
innerQuote,
|
||||
onWantsToReply,
|
||||
onWantsToEditDraft,
|
||||
backgroundBubbleColor,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
is ChatMessageEncryptedFileHeaderEvent ->
|
||||
RenderEncryptedFile(
|
||||
note,
|
||||
backgroundBubbleColor,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
else ->
|
||||
RenderRegularTextNote(
|
||||
note,
|
||||
canPreview,
|
||||
innerQuote,
|
||||
backgroundBubbleColor,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
is ChannelCreateEvent -> RenderCreateChannelNote(note, bgColor, accountViewModel, nav)
|
||||
is ChannelMetadataEvent -> RenderChangeChannelMetadataNote(note, bgColor, accountViewModel, nav)
|
||||
is DraftEvent -> RenderDraftEvent(note, canPreview, innerQuote, onWantsToReply, onWantsToEditDraft, bgColor, accountViewModel, nav)
|
||||
is ChatMessageEncryptedFileHeaderEvent -> RenderEncryptedFile(note, bgColor, accountViewModel, nav)
|
||||
else -> RenderRegularTextNote(note, canPreview, innerQuote, bgColor, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderDraftEvent(
|
||||
note: Note,
|
||||
canPreview: Boolean,
|
||||
innerQuote: Boolean,
|
||||
onWantsToReply: (Note) -> Unit,
|
||||
onWantsToEditDraft: (Note) -> Unit,
|
||||
backgroundBubbleColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
ObserveDraftEvent(note, accountViewModel) {
|
||||
Column {
|
||||
RenderReplyRow(
|
||||
note = it,
|
||||
innerQuote = innerQuote,
|
||||
backgroundBubbleColor = backgroundBubbleColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
onWantsToReply = onWantsToReply,
|
||||
onWantsToEditDraft = onWantsToEditDraft,
|
||||
)
|
||||
|
||||
NoteRow(
|
||||
note = it,
|
||||
canPreview = canPreview,
|
||||
innerQuote = innerQuote,
|
||||
onWantsToReply = onWantsToReply,
|
||||
onWantsToEditDraft = onWantsToEditDraft,
|
||||
backgroundBubbleColor = backgroundBubbleColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun IncognitoBadge(baseNote: Note) {
|
||||
if (baseNote.event is NIP17Group) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.incognito),
|
||||
null,
|
||||
modifier = incognitoIconModifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
} else if (baseNote.event is PrivateDmEvent) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.incognito_off),
|
||||
null,
|
||||
modifier = incognitoIconModifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatTimeAgo(baseNote: Note) {
|
||||
val nowStr = stringRes(id = R.string.now)
|
||||
val time = remember(baseNote) { timeAgoShort(baseNote.createdAt() ?: 0, nowStr) }
|
||||
|
||||
Text(
|
||||
text = time,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
fontSize = Font12SP,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderRegularTextNote(
|
||||
note: Note,
|
||||
canPreview: Boolean,
|
||||
innerQuote: Boolean,
|
||||
backgroundBubbleColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadDecryptedContentOrNull(note = note, accountViewModel = accountViewModel) { eventContent ->
|
||||
if (eventContent != null) {
|
||||
SensitivityWarning(
|
||||
note = note,
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
val tags = remember(note.event) { note.event?.tags?.toImmutableListOfLists() ?: EmptyTagList }
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = eventContent,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = if (innerQuote) 0 else 1,
|
||||
modifier = Modifier,
|
||||
tags = tags,
|
||||
backgroundColor = backgroundBubbleColor,
|
||||
id = note.idHex,
|
||||
callbackUri = note.toNostrUri(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
TranslatableRichTextViewer(
|
||||
content = stringRes(id = R.string.could_not_decrypt_the_message),
|
||||
canPreview = true,
|
||||
quotesLeft = 0,
|
||||
modifier = Modifier,
|
||||
tags = EmptyTagList,
|
||||
backgroundColor = backgroundBubbleColor,
|
||||
id = note.idHex,
|
||||
callbackUri = note.toNostrUri(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderChangeChannelMetadataNote(note: Note) {
|
||||
val noteEvent = note.event as? ChannelMetadataEvent ?: return
|
||||
|
||||
val channelInfo = noteEvent.channelInfo()
|
||||
val text =
|
||||
note.author?.toBestDisplayName().toString() +
|
||||
" ${stringRes(R.string.changed_chat_name_to)} '" +
|
||||
(channelInfo.name ?: "") +
|
||||
"', ${stringRes(R.string.description_to)} '" +
|
||||
(channelInfo.about ?: "") +
|
||||
"', ${stringRes(R.string.and_picture_to)} '" +
|
||||
(channelInfo.picture ?: "") +
|
||||
"'"
|
||||
|
||||
CreateTextWithEmoji(
|
||||
text = text,
|
||||
tags = note.author?.info?.tags,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderCreateChannelNote(note: Note) {
|
||||
val noteEvent = note.event as? ChannelCreateEvent ?: return
|
||||
val channelInfo = remember { noteEvent.channelInfo() }
|
||||
|
||||
val text =
|
||||
note.author?.toBestDisplayName().toString() +
|
||||
" ${stringRes(R.string.created)} " +
|
||||
(channelInfo.name ?: "") +
|
||||
" ${stringRes(R.string.with_description_of)} '" +
|
||||
(channelInfo.about ?: "") +
|
||||
"', ${stringRes(R.string.and_picture)} '" +
|
||||
(channelInfo.picture ?: "") +
|
||||
"'"
|
||||
|
||||
CreateTextWithEmoji(
|
||||
text = text,
|
||||
tags = note.author?.info?.tags,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DrawAuthorInfo(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
baseNote.author?.let {
|
||||
WatchAndDisplayUser(it, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserDisplayNameLayout(
|
||||
picture: @Composable () -> Unit,
|
||||
name: @Composable () -> Unit,
|
||||
) {
|
||||
Box(chatAuthorBox, contentAlignment = Alignment.TopEnd) {
|
||||
picture()
|
||||
}
|
||||
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
|
||||
name()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WatchAndDisplayUser(
|
||||
author: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val userState by author.live().userMetadataInfo.observeAsState()
|
||||
|
||||
UserDisplayNameLayout(
|
||||
picture = {
|
||||
InnerUserPicture(
|
||||
userHex = author.pubkeyHex,
|
||||
userPicture = userState?.picture,
|
||||
userName = userState?.bestName(),
|
||||
size = Size20dp,
|
||||
modifier = Modifier,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
|
||||
WatchUserFollows(author.pubkeyHex, accountViewModel) { newFollowingState ->
|
||||
if (newFollowingState) {
|
||||
FollowingIcon(Size5Modifier)
|
||||
}
|
||||
}
|
||||
},
|
||||
name = {
|
||||
if (userState != null) {
|
||||
DisplayMessageUsername(userState?.bestName() ?: author.pubkeyDisplayHex(), userState?.tags ?: EmptyTagList)
|
||||
} else {
|
||||
DisplayMessageUsername(author.pubkeyDisplayHex(), EmptyTagList)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DisplayMessageUsername(
|
||||
userDisplayName: String,
|
||||
userTags: ImmutableListOfLists<String>,
|
||||
) {
|
||||
CreateTextWithEmoji(
|
||||
text = userDisplayName,
|
||||
tags = userTags,
|
||||
maxLines = 1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.feed
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgoShort
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font12SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun ChatTimeAgo(baseNote: Note) {
|
||||
val nowStr = stringRes(id = R.string.now)
|
||||
val time = remember(baseNote) { timeAgoShort(baseNote.createdAt() ?: 0, nowStr) }
|
||||
|
||||
Text(
|
||||
text = time,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
fontSize = Font12SP,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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.feed
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.FollowingIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.InnerUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.WatchUserFollows
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.layouts.UserDisplayNameLayout
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5Modifier
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
|
||||
@Composable
|
||||
fun DrawAuthorInfo(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
baseNote.author?.let {
|
||||
WatchAndDisplayUser(it, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WatchAndDisplayUser(
|
||||
author: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val userState by author.live().userMetadataInfo.observeAsState()
|
||||
|
||||
UserDisplayNameLayout(
|
||||
picture = {
|
||||
InnerUserPicture(
|
||||
userHex = author.pubkeyHex,
|
||||
userPicture = userState?.picture,
|
||||
userName = userState?.bestName(),
|
||||
size = Size20dp,
|
||||
modifier = Modifier,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
|
||||
WatchUserFollows(author.pubkeyHex, accountViewModel) { newFollowingState ->
|
||||
if (newFollowingState) {
|
||||
FollowingIcon(Size5Modifier)
|
||||
}
|
||||
}
|
||||
},
|
||||
name = {
|
||||
if (userState != null) {
|
||||
CreateTextWithEmoji(
|
||||
text = userState?.bestName() ?: author.pubkeyDisplayHex(),
|
||||
tags = userState?.tags ?: EmptyTagList,
|
||||
maxLines = 1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
} else {
|
||||
CreateTextWithEmoji(
|
||||
text = author.pubkeyDisplayHex(),
|
||||
tags = EmptyTagList,
|
||||
maxLines = 1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.feed
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.note.dateFormatter
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.layouts.ChatDivisor
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip14Subject.subject
|
||||
|
||||
@Composable
|
||||
fun NewDateOrSubjectDivisor(
|
||||
previous: Note?,
|
||||
note: Note,
|
||||
) {
|
||||
if (previous == null) return
|
||||
|
||||
val never = stringRes(R.string.never)
|
||||
val today = stringRes(R.string.today)
|
||||
|
||||
val prevDate = remember(previous) { dateFormatter(previous.event?.createdAt, never, today) }
|
||||
val date = remember(note) { dateFormatter(note.event?.createdAt, never, today) }
|
||||
|
||||
val subject = remember(note) { note.event?.subject() }
|
||||
|
||||
if (prevDate != date) {
|
||||
if (subject != null) {
|
||||
ChatDivisor("$date - $subject")
|
||||
} else {
|
||||
ChatDivisor(date)
|
||||
}
|
||||
} else {
|
||||
if (subject != null) {
|
||||
ChatDivisor(subject)
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-13
@@ -18,7 +18,7 @@
|
||||
* 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.privateDM.messages
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.layouts
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
@@ -82,7 +82,7 @@ fun ChatBubbleLayout(
|
||||
val defaultBackground = MaterialTheme.colorScheme.background
|
||||
val draftColor = MaterialTheme.colorScheme.chatDraftBackground
|
||||
|
||||
val backgroundBubbleColor =
|
||||
val bgColor =
|
||||
remember {
|
||||
if (isLoggedInUser) {
|
||||
if (isDraft) {
|
||||
@@ -135,7 +135,7 @@ fun ChatBubbleLayout(
|
||||
modifier = if (innerQuote) Modifier else ChatBubbleMaxSizeModifier,
|
||||
) {
|
||||
Surface(
|
||||
color = backgroundBubbleColor.value,
|
||||
color = bgColor.value,
|
||||
shape = if (isLoggedInUser) ChatBubbleShapeMe else ChatBubbleShapeThem,
|
||||
modifier = clickableModifier,
|
||||
) {
|
||||
@@ -150,7 +150,7 @@ fun ChatBubbleLayout(
|
||||
}
|
||||
}
|
||||
|
||||
inner(backgroundBubbleColor)
|
||||
inner(bgColor)
|
||||
|
||||
if (showDetails.value) {
|
||||
Row(
|
||||
@@ -175,7 +175,7 @@ fun ChatBubbleLayout(
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BubblePreview() {
|
||||
val backgroundBubbleColor =
|
||||
val bgColor =
|
||||
remember {
|
||||
mutableStateOf<Color>(Color.Transparent)
|
||||
}
|
||||
@@ -188,7 +188,7 @@ private fun BubblePreview() {
|
||||
isComplete = true,
|
||||
hasDetailsToShow = true,
|
||||
drawAuthorInfo = true,
|
||||
parentBackgroundColor = backgroundBubbleColor,
|
||||
parentBackgroundColor = bgColor,
|
||||
onClick = { false },
|
||||
onAuthorClick = {},
|
||||
actionMenu = { onDismiss ->
|
||||
@@ -212,7 +212,7 @@ private fun BubblePreview() {
|
||||
)
|
||||
},
|
||||
detailRow = { Text("Relays and Actions") },
|
||||
) { backgroundBubbleColor ->
|
||||
) { bgColor ->
|
||||
Text("This is my note")
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ private fun BubblePreview() {
|
||||
isComplete = true,
|
||||
hasDetailsToShow = true,
|
||||
drawAuthorInfo = true,
|
||||
parentBackgroundColor = backgroundBubbleColor,
|
||||
parentBackgroundColor = bgColor,
|
||||
onClick = { false },
|
||||
onAuthorClick = {},
|
||||
actionMenu = { onDismiss ->
|
||||
@@ -246,7 +246,7 @@ private fun BubblePreview() {
|
||||
)
|
||||
},
|
||||
detailRow = { Text("Relays and Actions") },
|
||||
) { backgroundBubbleColor ->
|
||||
) { bgColor ->
|
||||
Text("This is a very long long loong note")
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ private fun BubblePreview() {
|
||||
isComplete = true,
|
||||
hasDetailsToShow = true,
|
||||
drawAuthorInfo = true,
|
||||
parentBackgroundColor = backgroundBubbleColor,
|
||||
parentBackgroundColor = bgColor,
|
||||
onClick = { false },
|
||||
onAuthorClick = {},
|
||||
actionMenu = { onDismiss ->
|
||||
@@ -280,7 +280,7 @@ private fun BubblePreview() {
|
||||
)
|
||||
},
|
||||
detailRow = { Text("Relays and Actions") },
|
||||
) { backgroundBubbleColor ->
|
||||
) { bgColor ->
|
||||
Text("This is a draft note")
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ private fun BubblePreview() {
|
||||
isComplete = false,
|
||||
hasDetailsToShow = false,
|
||||
drawAuthorInfo = false,
|
||||
parentBackgroundColor = backgroundBubbleColor,
|
||||
parentBackgroundColor = bgColor,
|
||||
onClick = { false },
|
||||
onAuthorClick = {},
|
||||
actionMenu = { onDismiss ->
|
||||
@@ -314,7 +314,7 @@ private fun BubblePreview() {
|
||||
)
|
||||
},
|
||||
detailRow = { Text("Relays and Actions") },
|
||||
) { backgroundBubbleColor ->
|
||||
) { bgColor ->
|
||||
Text("Short note")
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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.privateDM.feed
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.layouts
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.feed.layouts
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.chatAuthorBox
|
||||
|
||||
@Composable
|
||||
fun UserDisplayNameLayout(
|
||||
picture: @Composable () -> Unit,
|
||||
name: @Composable () -> Unit,
|
||||
) {
|
||||
Box(chatAuthorBox, contentAlignment = Alignment.TopEnd) {
|
||||
picture()
|
||||
}
|
||||
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
|
||||
name()
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.feed.types
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
|
||||
@Composable
|
||||
fun RenderChangeChannelMetadataNote(
|
||||
note: Note,
|
||||
bgColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = note.event as? ChannelMetadataEvent ?: return
|
||||
|
||||
val channelInfo = noteEvent.channelInfo()
|
||||
val text =
|
||||
note.author?.toBestDisplayName().toString() +
|
||||
" ${stringRes(R.string.changed_chat_name_to)} '" +
|
||||
(channelInfo.name ?: "") +
|
||||
"', ${stringRes(R.string.description_to)} '" +
|
||||
(channelInfo.about ?: "") +
|
||||
"' ${stringRes(R.string.and_picture_to)} " +
|
||||
(channelInfo.picture ?: "")
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = text,
|
||||
canPreview = true,
|
||||
quotesLeft = 0,
|
||||
modifier = Modifier,
|
||||
tags = note.author?.info?.tags ?: EmptyTagList,
|
||||
backgroundColor = bgColor,
|
||||
id = note.idHex,
|
||||
callbackUri = note.toNostrUri(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.feed.types
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
|
||||
@Composable
|
||||
fun RenderCreateChannelNote(
|
||||
note: Note,
|
||||
bgColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = note.event as? ChannelCreateEvent ?: return
|
||||
val channelInfo = remember { noteEvent.channelInfo() }
|
||||
|
||||
val text =
|
||||
note.author?.toBestDisplayName().toString() +
|
||||
" ${stringRes(R.string.created)} " +
|
||||
(channelInfo.name ?: "") +
|
||||
" ${stringRes(R.string.with_description_of)} '" +
|
||||
(channelInfo.about ?: "") +
|
||||
"' ${stringRes(R.string.and_picture_to)} " +
|
||||
(channelInfo.picture ?: "")
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = text,
|
||||
canPreview = true,
|
||||
quotesLeft = 0,
|
||||
modifier = Modifier,
|
||||
tags = note.author?.info?.tags ?: EmptyTagList,
|
||||
backgroundColor = bgColor,
|
||||
id = note.idHex,
|
||||
callbackUri = note.toNostrUri(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.feed.types
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.ObserveDraftEvent
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.NoteRow
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.RenderReplyRow
|
||||
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing5dp
|
||||
|
||||
@Composable
|
||||
fun RenderDraftEvent(
|
||||
note: Note,
|
||||
canPreview: Boolean,
|
||||
innerQuote: Boolean,
|
||||
onWantsToReply: (Note) -> Unit,
|
||||
onWantsToEditDraft: (Note) -> Unit,
|
||||
backgroundBubbleColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
ObserveDraftEvent(note, accountViewModel) {
|
||||
Column(verticalArrangement = RowColSpacing5dp) {
|
||||
RenderReplyRow(
|
||||
note = it,
|
||||
innerQuote = innerQuote,
|
||||
bgColor = backgroundBubbleColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
onWantsToReply = onWantsToReply,
|
||||
onWantsToEditDraft = onWantsToEditDraft,
|
||||
)
|
||||
|
||||
NoteRow(
|
||||
note = it,
|
||||
canPreview = canPreview,
|
||||
innerQuote = innerQuote,
|
||||
onWantsToReply = onWantsToReply,
|
||||
onWantsToEditDraft = onWantsToEditDraft,
|
||||
bgColor = backgroundBubbleColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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.feed.types
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
|
||||
import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlImage
|
||||
import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlVideo
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.encryption.AESGCM
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
fun RenderEncryptedFile(
|
||||
note: Note,
|
||||
bgColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = note.event as? ChatMessageEncryptedFileHeaderEvent ?: return
|
||||
|
||||
val algo = noteEvent.algo()
|
||||
val key = noteEvent.key()
|
||||
val nonce = noteEvent.nonce()
|
||||
val mimeType = noteEvent.mimeType()
|
||||
|
||||
if (algo == AESGCM.NAME && key != null && nonce != null) {
|
||||
HttpClientManager.addCipherToCache(noteEvent.content, AESGCM(key, nonce), mimeType)
|
||||
|
||||
val content by remember(noteEvent) {
|
||||
val isImage = mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(noteEvent.content)
|
||||
|
||||
mutableStateOf<BaseMediaContent>(
|
||||
if (isImage) {
|
||||
EncryptedMediaUrlImage(
|
||||
url = noteEvent.content,
|
||||
description = noteEvent.alt(),
|
||||
hash = noteEvent.originalHash(),
|
||||
blurhash = noteEvent.blurhash(),
|
||||
dim = noteEvent.dimensions(),
|
||||
uri = note.toNostrUri(),
|
||||
mimeType = mimeType,
|
||||
encryptionAlgo = algo,
|
||||
encryptionKey = key,
|
||||
encryptionNonce = nonce,
|
||||
)
|
||||
} else {
|
||||
EncryptedMediaUrlVideo(
|
||||
url = noteEvent.content,
|
||||
description = noteEvent.alt(),
|
||||
hash = noteEvent.originalHash(),
|
||||
blurhash = noteEvent.blurhash(),
|
||||
dim = noteEvent.dimensions(),
|
||||
uri = note.toNostrUri(),
|
||||
authorName = note.author?.toBestDisplayName(),
|
||||
mimeType = mimeType,
|
||||
encryptionAlgo = algo,
|
||||
encryptionKey = key,
|
||||
encryptionNonce = nonce,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
ZoomableContentView(
|
||||
content,
|
||||
persistentListOf(content),
|
||||
roundedCorner = true,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
accountViewModel,
|
||||
)
|
||||
} else {
|
||||
TranslatableRichTextViewer(
|
||||
content = stringRes(id = R.string.could_not_decrypt_the_message),
|
||||
canPreview = true,
|
||||
quotesLeft = 0,
|
||||
modifier = Modifier,
|
||||
tags = EmptyTagList,
|
||||
backgroundColor = bgColor,
|
||||
id = note.idHex,
|
||||
callbackUri = note.toNostrUri(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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.feed.types
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContentOrNull
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists
|
||||
|
||||
@Composable
|
||||
fun RenderRegularTextNote(
|
||||
note: Note,
|
||||
canPreview: Boolean,
|
||||
innerQuote: Boolean,
|
||||
bgColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadDecryptedContentOrNull(note = note, accountViewModel = accountViewModel) { eventContent ->
|
||||
if (eventContent != null) {
|
||||
SensitivityWarning(
|
||||
note = note,
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
val tags = remember(note.event) { note.event?.tags?.toImmutableListOfLists() ?: EmptyTagList }
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = eventContent,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = if (innerQuote) 0 else 1,
|
||||
modifier = Modifier,
|
||||
tags = tags,
|
||||
backgroundColor = bgColor,
|
||||
id = note.idHex,
|
||||
callbackUri = note.toNostrUri(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
TranslatableRichTextViewer(
|
||||
content = stringRes(id = R.string.could_not_decrypt_the_message),
|
||||
canPreview = true,
|
||||
quotesLeft = 0,
|
||||
modifier = Modifier,
|
||||
tags = EmptyTagList,
|
||||
backgroundColor = bgColor,
|
||||
id = note.idHex,
|
||||
callbackUri = note.toNostrUri(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
@@ -42,9 +41,10 @@ import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMs
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMsAndDisplayIfNotFound
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.feed.RefreshingChatroomFeedView
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.RefreshingChatroomFeedView
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.ChatNewMessageViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.PrivateMessageEditFieldRow
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -176,7 +176,7 @@ fun ChatroomViewUI(
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Spacer(modifier = DoubleVertSpacer)
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.privateDM
|
||||
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.incognitoIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.NIP17Group
|
||||
|
||||
@Composable
|
||||
fun IncognitoBadge(baseNote: Note) {
|
||||
if (baseNote.event is NIP17Group) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.incognito),
|
||||
null,
|
||||
modifier = incognitoIconModifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
} else if (baseNote.event is PrivateDmEvent) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.incognito_off),
|
||||
null,
|
||||
modifier = incognitoIconModifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
|
||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import kotlin.math.min
|
||||
|
||||
+27
-2
@@ -57,6 +57,7 @@ import com.vitorpamplona.quartz.nip14Subject.subject
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.BaseDMGroupEvent
|
||||
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.nip18Reposts.quotes.quotes
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||
@@ -179,6 +180,12 @@ open class ChatNewMessageViewModel : ViewModel() {
|
||||
|
||||
open fun reply(replyNote: Note) {
|
||||
replyTo.value = replyNote
|
||||
saveDraft()
|
||||
}
|
||||
|
||||
fun clearReply() {
|
||||
replyTo.value = null
|
||||
saveDraft()
|
||||
}
|
||||
|
||||
open fun quote(quote: Note) {
|
||||
@@ -224,8 +231,6 @@ open class ChatNewMessageViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
private fun loadFromDraft(draft: Note) {
|
||||
Log.d("draft", draft.event!!.toJson())
|
||||
|
||||
val draftEvent = draft.event ?: return
|
||||
val accountViewModel = accountViewModel ?: return
|
||||
|
||||
@@ -264,9 +269,29 @@ open class ChatNewMessageViewModel : ViewModel() {
|
||||
TextFieldValue(
|
||||
draftEvent.groupMembers().mapNotNull { runCatching { Hex.decode(it).toNpub() }.getOrNull() }.joinToString(", ") { "@$it" },
|
||||
)
|
||||
|
||||
val replyId =
|
||||
when (draftEvent) {
|
||||
is ChatMessageEvent -> draftEvent.replyTo().lastOrNull()
|
||||
is ChatMessageEncryptedFileHeaderEvent -> draftEvent.replyTo().lastOrNull()
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (replyId != null) {
|
||||
accountViewModel.checkGetOrCreateNote(replyId) {
|
||||
replyTo.value = it
|
||||
}
|
||||
}
|
||||
} else if (draftEvent is PrivateDmEvent) {
|
||||
val recepientNpub = draftEvent.verifiedRecipientPubKey()?.let { Hex.decode(it).toNpub() }
|
||||
toUsers = TextFieldValue("@$recepientNpub")
|
||||
|
||||
val replyId = draftEvent.replyTo()
|
||||
if (replyId != null) {
|
||||
accountViewModel.checkGetOrCreateNote(replyId) {
|
||||
replyTo.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message =
|
||||
|
||||
+30
-7
@@ -42,12 +42,15 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
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.components.ThinPaddingTextField
|
||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.IncognitoIconOff
|
||||
import com.vitorpamplona.amethyst.ui.note.IncognitoIconOn
|
||||
@@ -58,11 +61,14 @@ 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.utils.DisplayReplyingToNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ThinSendButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldLeadingIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@@ -70,6 +76,23 @@ import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PrivateMessageEditFieldRow() {
|
||||
val channelScreenModel: ChatNewMessageViewModel = viewModel()
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
channelScreenModel.init(accountViewModel)
|
||||
|
||||
ThemeComparisonRow {
|
||||
PrivateMessageEditFieldRow(
|
||||
channelScreenModel = channelScreenModel,
|
||||
accountViewModel = accountViewModel,
|
||||
onSendNewMessage = {},
|
||||
nav = EmptyNav,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PrivateMessageEditFieldRow(
|
||||
channelScreenModel: ChatNewMessageViewModel,
|
||||
@@ -77,7 +100,11 @@ fun PrivateMessageEditFieldRow(
|
||||
onSendNewMessage: () -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
channelScreenModel.replyTo.value?.let { DisplayReplyingToNote(it, accountViewModel, nav) { channelScreenModel.replyTo.value = null } }
|
||||
channelScreenModel.replyTo.value?.let {
|
||||
DisplayReplyingToNote(it, accountViewModel, nav) {
|
||||
channelScreenModel.clearReply()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = channelScreenModel.draftTag) {
|
||||
launch(Dispatchers.IO) {
|
||||
@@ -148,8 +175,7 @@ fun PrivateMessageEditFieldRow(
|
||||
},
|
||||
trailingIcon = {
|
||||
ThinSendButton(
|
||||
isActive =
|
||||
channelScreenModel.message.text.isNotBlank() && !channelScreenModel.isUploadingImage,
|
||||
isActive = channelScreenModel.message.text.isNotBlank() && !channelScreenModel.isUploadingImage,
|
||||
modifier = EditFieldTrailingIconModifier,
|
||||
) {
|
||||
channelScreenModel.sendPost(onSendNewMessage)
|
||||
@@ -163,10 +189,7 @@ fun PrivateMessageEditFieldRow(
|
||||
SelectFromGallery(
|
||||
isUploading = channelScreenModel.isUploadingImage,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
modifier =
|
||||
Modifier
|
||||
.size(30.dp)
|
||||
.padding(start = 2.dp),
|
||||
modifier = EditFieldLeadingIconModifier,
|
||||
onImageChosen = channelScreenModel::pickedMedia,
|
||||
)
|
||||
|
||||
|
||||
+2
-2
@@ -208,8 +208,8 @@ private fun ImageVideoPostChat(
|
||||
title = R.string.add_sensitive_content_label,
|
||||
description = R.string.add_sensitive_content_description,
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
checked = fileUploadState.sensitiveContent,
|
||||
onCheckedChange = { fileUploadState.sensitiveContent = it },
|
||||
checked = fileUploadState.contentWarning,
|
||||
onCheckedChange = fileUploadState::updateContentWarning,
|
||||
)
|
||||
|
||||
SettingsRow(R.string.file_server, R.string.file_server_description) {
|
||||
|
||||
+14
-1
@@ -40,7 +40,11 @@ class ChatFileUploadState(
|
||||
|
||||
var selectedServer by mutableStateOf(defaultServer)
|
||||
var caption by mutableStateOf("")
|
||||
var sensitiveContent by mutableStateOf(false)
|
||||
|
||||
var contentWarning by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var contentWarningReason by mutableStateOf<String?>(null)
|
||||
|
||||
// Images and Videos
|
||||
var multiOrchestrator by mutableStateOf<MultiOrchestrator?>(null)
|
||||
@@ -72,4 +76,13 @@ class ChatFileUploadState(
|
||||
fun canPost(): Boolean = !isUploadingImage && multiOrchestrator != null
|
||||
|
||||
fun hasPickedMedia() = multiOrchestrator != null
|
||||
|
||||
fun updateContentWarning(value: Boolean) {
|
||||
contentWarning = value
|
||||
if (value) {
|
||||
contentWarningReason = ""
|
||||
} else {
|
||||
contentWarningReason = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-6
@@ -59,7 +59,7 @@ class ChatFileUploader(
|
||||
orchestrator.uploadEncrypted(
|
||||
scope,
|
||||
viewState.caption,
|
||||
if (viewState.sensitiveContent) "" else null,
|
||||
viewState.contentWarningReason,
|
||||
MediaCompressor.intToCompressorQuality(viewState.mediaQualitySlider),
|
||||
cipher,
|
||||
viewState.selectedServer,
|
||||
@@ -88,9 +88,7 @@ class ChatFileUploader(
|
||||
alt(viewState.caption)
|
||||
}
|
||||
|
||||
if (viewState.sensitiveContent) {
|
||||
contentWarning("")
|
||||
}
|
||||
viewState.contentWarningReason?.let { contentWarning(it) }
|
||||
}
|
||||
|
||||
account.sendNIP17EncryptedFile(template)
|
||||
@@ -125,7 +123,7 @@ class ChatFileUploader(
|
||||
orchestrator.upload(
|
||||
scope,
|
||||
viewState.caption,
|
||||
if (viewState.sensitiveContent) "" else null,
|
||||
viewState.contentWarningReason,
|
||||
MediaCompressor.intToCompressorQuality(viewState.mediaQualitySlider),
|
||||
viewState.selectedServer,
|
||||
account,
|
||||
@@ -136,7 +134,7 @@ class ChatFileUploader(
|
||||
results.successful.forEach {
|
||||
if (it.result is UploadOrchestrator.OrchestratorResult.ServerResult) {
|
||||
val iMetaAttachments = IMetaAttachments()
|
||||
iMetaAttachments.add(it.result, viewState.caption, if (viewState.sensitiveContent) "" else null)
|
||||
iMetaAttachments.add(it.result, viewState.caption, viewState.contentWarningReason)
|
||||
|
||||
account.sendPrivateMessage(
|
||||
message = it.result.url,
|
||||
|
||||
-1275
File diff suppressed because it is too large
Load Diff
+91
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadChannel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.PublicChatChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.LiveActivitiesChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.innerPostModifier
|
||||
|
||||
@Composable
|
||||
fun RenderChannelHeader(
|
||||
channelNote: Note,
|
||||
showVideo: Boolean,
|
||||
sendToChannel: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
channelNote.channelHex()?.let {
|
||||
ChannelHeader(
|
||||
channelHex = it,
|
||||
showVideo = showVideo,
|
||||
sendToChannel = sendToChannel,
|
||||
modifier = MaterialTheme.colorScheme.innerPostModifier.padding(Size10dp),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChannelHeader(
|
||||
channelHex: String,
|
||||
showVideo: Boolean,
|
||||
showFlag: Boolean = true,
|
||||
sendToChannel: Boolean = false,
|
||||
modifier: Modifier = StdPadding,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadChannel(channelHex, accountViewModel) {
|
||||
when (it) {
|
||||
is LiveActivitiesChannel ->
|
||||
LiveActivitiesChannelHeader(
|
||||
it,
|
||||
showVideo,
|
||||
showFlag,
|
||||
sendToChannel,
|
||||
modifier,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
is PublicChatChannel ->
|
||||
PublicChatChannelHeader(
|
||||
it,
|
||||
sendToChannel,
|
||||
modifier,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadChannel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisappearingScaffold
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.PublicChatTopBar
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header.LiveActivityTopBar
|
||||
|
||||
@Composable
|
||||
fun ChannelScreen(
|
||||
channelId: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
if (channelId == null) return
|
||||
|
||||
DisappearingScaffold(
|
||||
isInvertedLayout = true,
|
||||
topBar = {
|
||||
LoadChannel(channelId, accountViewModel) {
|
||||
when (it) {
|
||||
is PublicChatChannel -> PublicChatTopBar(it, accountViewModel, nav)
|
||||
is LiveActivitiesChannel -> LiveActivityTopBar(it, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
},
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
Column(Modifier.padding(it)) {
|
||||
ChannelView(channelId, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.model.Channel
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrChannelDataSource.channel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadChannel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.RefreshingChatroomFeedView
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ShowVideoStreaming
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send.ChannelNewMessageViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send.EditFieldRow
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ChannelView(
|
||||
channelId: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
if (channelId == null) return
|
||||
|
||||
LoadChannel(channelId, accountViewModel) {
|
||||
PrepareChannelViewModels(
|
||||
baseChannel = it,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PrepareChannelViewModels(
|
||||
baseChannel: Channel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val feedViewModel: NostrChannelFeedViewModel =
|
||||
viewModel(
|
||||
key = baseChannel.idHex + "ChannelFeedViewModel",
|
||||
factory =
|
||||
NostrChannelFeedViewModel.Factory(
|
||||
baseChannel,
|
||||
accountViewModel.account,
|
||||
),
|
||||
)
|
||||
|
||||
val channelScreenModel: ChannelNewMessageViewModel = viewModel()
|
||||
channelScreenModel.init(accountViewModel)
|
||||
channelScreenModel.load(baseChannel)
|
||||
|
||||
ChannelView(
|
||||
channel = baseChannel,
|
||||
feedViewModel = feedViewModel,
|
||||
newPostModel = channelScreenModel,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChannelView(
|
||||
channel: Channel,
|
||||
feedViewModel: NostrChannelFeedViewModel,
|
||||
newPostModel: ChannelNewMessageViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
NostrChannelDataSource.loadMessagesBetween(accountViewModel.account, channel)
|
||||
|
||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||
|
||||
DisposableEffect(accountViewModel) {
|
||||
NostrChannelDataSource.loadMessagesBetween(accountViewModel.account, channel)
|
||||
NostrChannelDataSource.start()
|
||||
feedViewModel.invalidateData(true)
|
||||
|
||||
onDispose {
|
||||
NostrChannelDataSource.clear()
|
||||
NostrChannelDataSource.stop()
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(lifeCycleOwner) {
|
||||
val observer =
|
||||
LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_RESUME) {
|
||||
println("Channel Start")
|
||||
NostrChannelDataSource.start()
|
||||
feedViewModel.invalidateData(true)
|
||||
}
|
||||
if (event == Lifecycle.Event.ON_PAUSE) {
|
||||
println("Channel Stop")
|
||||
|
||||
NostrChannelDataSource.clear()
|
||||
NostrChannelDataSource.stop()
|
||||
}
|
||||
}
|
||||
|
||||
lifeCycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose { lifeCycleOwner.lifecycle.removeObserver(observer) }
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
Column(
|
||||
modifier =
|
||||
remember {
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(vertical = 0.dp)
|
||||
.weight(1f, true)
|
||||
},
|
||||
) {
|
||||
if (channel is LiveActivitiesChannel) {
|
||||
ShowVideoStreaming(channel, accountViewModel)
|
||||
}
|
||||
RefreshingChatroomFeedView(
|
||||
viewModel = feedViewModel,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
routeForLastRead = "Channel/${channel.idHex}",
|
||||
avoidDraft = newPostModel.draftTag,
|
||||
onWantsToReply = newPostModel::reply,
|
||||
onWantsToEditDraft = newPostModel::editFromDraft,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = DoubleVertSpacer)
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// LAST ROW
|
||||
EditFieldRow(
|
||||
newPostModel,
|
||||
accountViewModel,
|
||||
onSendNewMessage = {
|
||||
scope.launch {
|
||||
feedViewModel.sendToTop()
|
||||
}
|
||||
},
|
||||
nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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.nip28PublicChat.header
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.lifecycle.distinctUntilChanged
|
||||
import androidx.lifecycle.map
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LikeReaction
|
||||
import com.vitorpamplona.amethyst.ui.note.ZapReaction
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.EditButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.JoinChatButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.LeaveChatButton
|
||||
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.isTaggedEvent
|
||||
|
||||
@Composable
|
||||
fun ShortChannelActionOptions(
|
||||
channel: PublicChatChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadNote(baseNoteHex = channel.idHex, accountViewModel) {
|
||||
it?.let {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = RowColSpacing) {
|
||||
LikeReaction(
|
||||
baseNote = it,
|
||||
grayTint = MaterialTheme.colorScheme.onSurface,
|
||||
accountViewModel = accountViewModel,
|
||||
nav,
|
||||
)
|
||||
ZapReaction(
|
||||
baseNote = it,
|
||||
grayTint = MaterialTheme.colorScheme.onSurface,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WatchChannelFollows(channel, accountViewModel) { isFollowing ->
|
||||
if (!isFollowing) {
|
||||
JoinChatButton(channel, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LongChannelActionOptions(
|
||||
channel: PublicChatChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val isMe by
|
||||
remember(accountViewModel) {
|
||||
derivedStateOf { channel.creator == accountViewModel.account.userProfile() }
|
||||
}
|
||||
|
||||
if (isMe) {
|
||||
EditButton(channel, accountViewModel)
|
||||
}
|
||||
|
||||
WatchChannelFollows(channel, accountViewModel) { isFollowing ->
|
||||
if (isFollowing) {
|
||||
LeaveChatButton(channel, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WatchChannelFollows(
|
||||
channel: PublicChatChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
content: @Composable (Boolean) -> Unit,
|
||||
) {
|
||||
val isFollowing by
|
||||
accountViewModel
|
||||
.userProfile()
|
||||
.live()
|
||||
.follows
|
||||
.map { it.user.latestContactList?.isTaggedEvent(channel.idHex) ?: false }
|
||||
.distinctUntilChanged()
|
||||
.observeAsState(
|
||||
accountViewModel.userProfile().latestContactList?.isTaggedEvent(channel.idHex) ?: false,
|
||||
)
|
||||
|
||||
content(isFollowing)
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.NormalTimeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
|
||||
@Composable
|
||||
fun LongPublicChatChannelHeader(
|
||||
baseChannel: PublicChatChannel,
|
||||
lineModifier: Modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp),
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val channelState by baseChannel.live.observeAsState()
|
||||
val channel = channelState?.channel as? PublicChatChannel ?: return
|
||||
|
||||
Row(lineModifier) {
|
||||
val summary = remember(channelState) { channel.summary()?.ifBlank { null } }
|
||||
|
||||
Column(Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val defaultBackground = MaterialTheme.colorScheme.background
|
||||
val background = remember { mutableStateOf(defaultBackground) }
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = summary ?: stringRes(id = R.string.groups_no_descriptor),
|
||||
canPreview = false,
|
||||
quotesLeft = 1,
|
||||
tags = EmptyTagList,
|
||||
backgroundColor = background,
|
||||
id = baseChannel.idHex,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(DoubleHorzSpacer)
|
||||
LongChannelActionOptions(channel, accountViewModel, nav)
|
||||
}
|
||||
|
||||
LoadNote(baseNoteHex = channel.idHex, accountViewModel) { loadingNote ->
|
||||
loadingNote?.let { note ->
|
||||
Row(
|
||||
lineModifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(id = R.string.owner),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.width(75.dp),
|
||||
)
|
||||
Spacer(DoubleHorzSpacer)
|
||||
NoteAuthorPicture(note, nav, accountViewModel, Size25dp)
|
||||
Spacer(DoubleHorzSpacer)
|
||||
NoteUsernameDisplay(note, Modifier.weight(1f), accountViewModel = accountViewModel)
|
||||
}
|
||||
|
||||
Row(
|
||||
lineModifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(id = R.string.created_at),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.width(75.dp),
|
||||
)
|
||||
Spacer(DoubleHorzSpacer)
|
||||
NormalTimeAgo(note, remember { Modifier.weight(1f) })
|
||||
MoreOptionsButton(note, null, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.nip28PublicChat.header
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
|
||||
@Composable
|
||||
fun PublicChatChannelHeader(
|
||||
baseChannel: PublicChatChannel,
|
||||
sendToChannel: Boolean = false,
|
||||
modifier: Modifier = StdPadding,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier =
|
||||
modifier.clickable {
|
||||
if (sendToChannel) {
|
||||
nav.nav(routeFor(baseChannel))
|
||||
} else {
|
||||
expanded.value = !expanded.value
|
||||
}
|
||||
},
|
||||
) {
|
||||
ShortPublicChatChannelHeader(
|
||||
baseChannel = baseChannel,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
if (expanded.value) {
|
||||
LongPublicChatChannelHeader(baseChannel = baseChannel, accountViewModel = accountViewModel, nav = nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.nip28PublicChat.header
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.TopBarExtensibleWithBackButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
fun PublicChatTopBar(
|
||||
baseChannel: PublicChatChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
TopBarExtensibleWithBackButton(
|
||||
title = {
|
||||
ShortPublicChatChannelHeader(
|
||||
baseChannel = baseChannel,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
extendableRow = {
|
||||
LongPublicChatChannelHeader(baseChannel = baseChannel, accountViewModel = accountViewModel, nav = nav)
|
||||
},
|
||||
popBack = nav::popBack,
|
||||
)
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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.nip28PublicChat.header
|
||||
|
||||
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.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
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.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||
|
||||
@Composable
|
||||
fun ShortPublicChatChannelHeader(
|
||||
baseChannel: PublicChatChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val channelState by baseChannel.live.observeAsState()
|
||||
val channel = channelState?.channel as? PublicChatChannel ?: return
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
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 = remember(channelState) { channel.toBestDisplayName() },
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.height(Size35dp)
|
||||
.padding(start = 5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
ShortChannelActionOptions(channel, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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.nip28PublicChat.header.actions
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.EditNote
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata.ChannelMetadataDialog
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
||||
|
||||
@Composable
|
||||
fun EditButton(
|
||||
channel: PublicChatChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
var wantsToPost by remember { mutableStateOf(false) }
|
||||
|
||||
if (wantsToPost) {
|
||||
ChannelMetadataDialog({ wantsToPost = false }, accountViewModel, channel)
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = { wantsToPost = true },
|
||||
contentPadding = ZeroPadding,
|
||||
) {
|
||||
Icon(
|
||||
tint = Color.White,
|
||||
imageVector = Icons.Default.EditNote,
|
||||
contentDescription = stringRes(R.string.edits_the_channel_metadata),
|
||||
)
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.nip28PublicChat.header.actions
|
||||
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfHalfHorzModifier
|
||||
|
||||
@Composable
|
||||
fun JoinChatButton(
|
||||
channel: PublicChatChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
Button(
|
||||
modifier = HalfHalfHorzModifier,
|
||||
onClick = { accountViewModel.follow(channel) },
|
||||
contentPadding = ButtonPadding,
|
||||
) {
|
||||
Text(text = stringRes(R.string.join), color = Color.White)
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.nip28PublicChat.header.actions
|
||||
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfHalfHorzModifier
|
||||
|
||||
@Composable
|
||||
fun LeaveChatButton(
|
||||
channel: PublicChatChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
Button(
|
||||
modifier = HalfHalfHorzModifier,
|
||||
onClick = { accountViewModel.unfollow(channel) },
|
||||
contentPadding = ButtonPadding,
|
||||
) {
|
||||
Text(text = stringRes(R.string.leave), color = Color.White)
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* 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.nip28PublicChat.metadata
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
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.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun ChannelMetadataDialog(
|
||||
onClose: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
channel: PublicChatChannel? = null,
|
||||
) {
|
||||
val postViewModel: ChannelMetadataViewModel = viewModel()
|
||||
postViewModel.load(accountViewModel.account, channel)
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = { onClose() },
|
||||
properties =
|
||||
DialogProperties(
|
||||
dismissOnClickOutside = false,
|
||||
),
|
||||
) {
|
||||
DialogContent(postViewModel, onClose)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DialogContent(
|
||||
postViewModel: ChannelMetadataViewModel,
|
||||
onClose: () -> Unit,
|
||||
) {
|
||||
Surface {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(10.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CloseButton(
|
||||
onPress = {
|
||||
postViewModel.clear()
|
||||
onClose()
|
||||
},
|
||||
)
|
||||
|
||||
PostButton(
|
||||
onPost = {
|
||||
postViewModel.create()
|
||||
onClose()
|
||||
},
|
||||
postViewModel.channelName.value.text
|
||||
.isNotBlank(),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(15.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringRes(R.string.channel_name)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.channelName.value,
|
||||
onValueChange = { postViewModel.channelName.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.my_awesome_group),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
),
|
||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(15.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringRes(R.string.picture_url)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.channelPicture.value,
|
||||
onValueChange = { postViewModel.channelPicture.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "http://mygroup.com/logo.jpg",
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(15.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringRes(R.string.description)) },
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(100.dp),
|
||||
value = postViewModel.channelDescription.value,
|
||||
onValueChange = { postViewModel.channelDescription.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.about_us),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
),
|
||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
maxLines = 10,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.actions
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
@@ -33,7 +33,7 @@ import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class NewChannelViewModel : ViewModel() {
|
||||
class ChannelMetadataViewModel : ViewModel() {
|
||||
private var account: Account? = null
|
||||
private var originalChannel: PublicChatChannel? = null
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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.nip53LiveActivities
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
|
||||
@Composable
|
||||
fun LiveActivitiesChannelHeader(
|
||||
baseChannel: LiveActivitiesChannel,
|
||||
showVideo: Boolean,
|
||||
showFlag: Boolean = true,
|
||||
sendToChannel: Boolean = false,
|
||||
modifier: Modifier = StdPadding,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
if (showVideo) {
|
||||
ShowVideoStreaming(baseChannel, accountViewModel)
|
||||
}
|
||||
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier =
|
||||
modifier.clickable {
|
||||
if (sendToChannel) {
|
||||
nav.nav(routeFor(baseChannel))
|
||||
} else {
|
||||
expanded.value = !expanded.value
|
||||
}
|
||||
},
|
||||
) {
|
||||
ShortLiveActivityChannelHeader(
|
||||
baseChannel = baseChannel,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
showFlag = showFlag,
|
||||
)
|
||||
|
||||
if (expanded.value) {
|
||||
LongLiveActivityChannelHeader(baseChannel, accountViewModel = accountViewModel, nav = nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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.nip53LiveActivities
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LikeReaction
|
||||
import com.vitorpamplona.amethyst.ui.note.ZapReaction
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag
|
||||
|
||||
@Composable
|
||||
fun LiveChannelActionOptions(
|
||||
channel: LiveActivitiesChannel,
|
||||
showFlag: Boolean = true,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val isLive by remember(channel) { derivedStateOf { channel.info?.status() == StatusTag.STATUS.LIVE.code } }
|
||||
|
||||
val note = remember(channel.idHex) { LocalCache.getNoteIfExists(channel.idHex) }
|
||||
|
||||
note?.let {
|
||||
if (showFlag && isLive) {
|
||||
LiveFlag()
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = RowColSpacing,
|
||||
) {
|
||||
LikeReaction(
|
||||
baseNote = it,
|
||||
grayTint = MaterialTheme.colorScheme.onSurface,
|
||||
accountViewModel = accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
ZapReaction(
|
||||
baseNote = it,
|
||||
grayTint = MaterialTheme.colorScheme.onSurface,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* 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.nip53LiveActivities
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.NormalTimeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTag
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun LongLiveActivityChannelHeader(
|
||||
baseChannel: LiveActivitiesChannel,
|
||||
lineModifier: Modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp),
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val channelState by baseChannel.live.observeAsState()
|
||||
val channel = channelState?.channel as? LiveActivitiesChannel ?: return
|
||||
|
||||
Row(
|
||||
lineModifier,
|
||||
) {
|
||||
val summary = remember(channelState) { channel.summary()?.ifBlank { null } }
|
||||
|
||||
Column(
|
||||
Modifier.weight(1f),
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val defaultBackground = MaterialTheme.colorScheme.background
|
||||
val background = remember { mutableStateOf(defaultBackground) }
|
||||
|
||||
val tags = remember(channelState) { baseChannel.info?.tags?.toImmutableListOfLists() ?: EmptyTagList }
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = summary ?: stringRes(id = R.string.groups_no_descriptor),
|
||||
canPreview = false,
|
||||
quotesLeft = 1,
|
||||
tags = tags,
|
||||
backgroundColor = background,
|
||||
id = baseChannel.idHex,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
if (summary != null) {
|
||||
baseChannel.info?.let {
|
||||
if (it.hasHashtags()) {
|
||||
DisplayUncitedHashtags(it, summary, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadNote(baseNoteHex = channel.idHex, accountViewModel) { loadingNote ->
|
||||
loadingNote?.let { note ->
|
||||
Row(
|
||||
lineModifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(id = R.string.owner),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.width(75.dp),
|
||||
)
|
||||
Spacer(DoubleHorzSpacer)
|
||||
NoteAuthorPicture(note, nav, accountViewModel, Size25dp)
|
||||
Spacer(DoubleHorzSpacer)
|
||||
NoteUsernameDisplay(note, Modifier.weight(1f), accountViewModel = accountViewModel)
|
||||
}
|
||||
|
||||
Row(
|
||||
lineModifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(id = R.string.created_at),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.width(75.dp),
|
||||
)
|
||||
Spacer(DoubleHorzSpacer)
|
||||
NormalTimeAgo(note, remember { Modifier.weight(1f) })
|
||||
MoreOptionsButton(note, null, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var participantUsers by remember(baseChannel) {
|
||||
mutableStateOf<ImmutableList<Pair<ParticipantTag, User>>>(
|
||||
persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = channelState) {
|
||||
launch(Dispatchers.IO) {
|
||||
val newParticipantUsers =
|
||||
channel.info
|
||||
?.participants()
|
||||
?.mapNotNull { part ->
|
||||
LocalCache.checkGetOrCreateUser(part.pubKey)?.let { Pair(part, it) }
|
||||
}?.toImmutableList()
|
||||
|
||||
if (
|
||||
newParticipantUsers != null && !equalImmutableLists(newParticipantUsers, participantUsers)
|
||||
) {
|
||||
participantUsers = newParticipantUsers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
participantUsers.forEach {
|
||||
Row(
|
||||
lineModifier.clickable { nav.nav("User/${it.second.pubkeyHex}") },
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
it.first.role?.let { it1 ->
|
||||
Text(
|
||||
text = it1.capitalize(Locale.ROOT),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.width(55.dp),
|
||||
)
|
||||
}
|
||||
Spacer(DoubleHorzSpacer)
|
||||
ClickableUserPicture(it.second, Size25dp, accountViewModel)
|
||||
Spacer(DoubleHorzSpacer)
|
||||
UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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.nip53LiveActivities
|
||||
|
||||
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.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
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.theme.Size34dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||
|
||||
@Composable
|
||||
fun ShortLiveActivityChannelHeader(
|
||||
baseChannel: LiveActivitiesChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
showFlag: Boolean,
|
||||
) {
|
||||
val channelState by baseChannel.live.observeAsState()
|
||||
val channel = channelState?.channel as? LiveActivitiesChannel ?: return
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
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 = remember(channelState) { channel.toBestDisplayName() },
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.height(Size35dp)
|
||||
.padding(start = 5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
LiveChannelActionOptions(channel, showFlag, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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.nip53LiveActivities
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.lifecycle.distinctUntilChanged
|
||||
import androidx.lifecycle.map
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CrossfadeCheckIfVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.ui.theme.StreamingHeaderModifier
|
||||
|
||||
@Composable
|
||||
fun ShowVideoStreaming(
|
||||
baseChannel: LiveActivitiesChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
baseChannel.info?.let {
|
||||
SensitivityWarning(
|
||||
event = it,
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
val streamingInfoEvent by
|
||||
baseChannel.live
|
||||
.map {
|
||||
(it.channel as? LiveActivitiesChannel)?.info
|
||||
}.distinctUntilChanged()
|
||||
.observeAsState(baseChannel.info)
|
||||
|
||||
streamingInfoEvent?.let { event ->
|
||||
event.streaming()?.let { url ->
|
||||
CrossfadeCheckIfVideoIsOnline(url, accountViewModel) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = StreamingHeaderModifier,
|
||||
) {
|
||||
val zoomableUrlVideo =
|
||||
remember(streamingInfoEvent) {
|
||||
MediaUrlVideo(
|
||||
url = url,
|
||||
description = baseChannel.toBestDisplayName(),
|
||||
artworkUri = event.image(),
|
||||
authorName = baseChannel.creatorName(),
|
||||
uri = baseChannel.toNAddr(),
|
||||
)
|
||||
}
|
||||
|
||||
ZoomableContentView(
|
||||
content = zoomableUrlVideo,
|
||||
roundedCorner = false,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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.nip53LiveActivities
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.SmallBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.liveStreamTag
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
@Composable
|
||||
fun LiveFlag() {
|
||||
Text(
|
||||
text = stringRes(id = R.string.live_stream_live_tag),
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp,
|
||||
modifier =
|
||||
remember {
|
||||
Modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Red)
|
||||
.padding(horizontal = 5.dp)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EndedFlag() {
|
||||
Text(
|
||||
text = stringRes(id = R.string.live_stream_ended_tag),
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier =
|
||||
remember {
|
||||
Modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Black)
|
||||
.padding(horizontal = 5.dp)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OfflineFlag() {
|
||||
Text(
|
||||
text = stringRes(id = R.string.live_stream_offline_tag),
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier =
|
||||
remember {
|
||||
Modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Black)
|
||||
.padding(horizontal = 5.dp)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScheduledFlag(starts: Long?) {
|
||||
val startsIn =
|
||||
starts?.let {
|
||||
SimpleDateFormat
|
||||
.getDateTimeInstance(
|
||||
DateFormat.SHORT,
|
||||
DateFormat.SHORT,
|
||||
).format(Date(starts * 1000))
|
||||
}
|
||||
|
||||
Text(
|
||||
text = startsIn ?: stringRes(id = R.string.live_stream_planned_tag),
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = liveStreamTag,
|
||||
)
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.nip53LiveActivities.header
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.TopBarExtensibleWithBackButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.LongLiveActivityChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ShortLiveActivityChannelHeader
|
||||
|
||||
@Composable
|
||||
fun LiveActivityTopBar(
|
||||
baseChannel: LiveActivitiesChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
TopBarExtensibleWithBackButton(
|
||||
title = {
|
||||
ShortLiveActivityChannelHeader(
|
||||
baseChannel = baseChannel,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
showFlag = true,
|
||||
)
|
||||
},
|
||||
extendableRow = {
|
||||
LongLiveActivityChannelHeader(baseChannel = baseChannel, accountViewModel = accountViewModel, nav = nav)
|
||||
},
|
||||
popBack = nav::popBack,
|
||||
)
|
||||
}
|
||||
+653
@@ -0,0 +1,653 @@
|
||||
/**
|
||||
* 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 android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.compose.currentWord
|
||||
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
|
||||
import com.vitorpamplona.amethyst.commons.compose.replaceCurrentWord
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Channel
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.LocationState
|
||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
||||
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
|
||||
import com.vitorpamplona.amethyst.ui.actions.UserSuggestionAnchor
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.ui.components.Split
|
||||
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.UserSuggestions
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.ChatFileUploadState
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohash
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.getGeoHash
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.references.references
|
||||
import com.vitorpamplona.quartz.nip10Notes.content.findHashtags
|
||||
import com.vitorpamplona.quartz.nip10Notes.content.findNostrUris
|
||||
import com.vitorpamplona.quartz.nip10Notes.content.findURLs
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.quotes
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.base.notify
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.emojis
|
||||
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitive
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.notify
|
||||
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup
|
||||
import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplitSetup
|
||||
import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiserAmount
|
||||
import com.vitorpamplona.quartz.nip92IMeta.imetas
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
@Stable
|
||||
open class ChannelNewMessageViewModel : ViewModel() {
|
||||
var draftTag: String by mutableStateOf(UUID.randomUUID().toString())
|
||||
|
||||
var accountViewModel: AccountViewModel? = null
|
||||
var account: Account? = null
|
||||
var channel: Channel? = null
|
||||
|
||||
val replyTo = mutableStateOf<Note?>(null)
|
||||
|
||||
var uploadState by mutableStateOf<ChatFileUploadState?>(null)
|
||||
val iMetaAttachments = IMetaAttachments()
|
||||
var nip95attachments by mutableStateOf<List<Pair<FileStorageEvent, FileStorageHeaderEvent>>>(emptyList())
|
||||
|
||||
var message by mutableStateOf(TextFieldValue(""))
|
||||
var urlPreview by mutableStateOf<String?>(null)
|
||||
var isUploadingImage by mutableStateOf(false)
|
||||
|
||||
val userSuggestions = UserSuggestions()
|
||||
var userSuggestionsMainMessage: UserSuggestionAnchor? = null
|
||||
|
||||
val emojiSearch: MutableStateFlow<String> = MutableStateFlow("")
|
||||
val emojiSuggestions: StateFlow<List<Account.EmojiMedia>> by lazy {
|
||||
account!!
|
||||
.myEmojis
|
||||
.combine(emojiSearch) { list, search ->
|
||||
if (search.length == 1) {
|
||||
list
|
||||
} else if (search.isNotEmpty()) {
|
||||
val code = search.removePrefix(":")
|
||||
list.filter { it.code.startsWith(code) }
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}.flowOn(Dispatchers.Default)
|
||||
.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
// Invoices
|
||||
var canAddInvoice by mutableStateOf(false)
|
||||
var wantsInvoice by mutableStateOf(false)
|
||||
|
||||
// Forward Zap to
|
||||
var wantsForwardZapTo by mutableStateOf(false)
|
||||
var forwardZapTo by mutableStateOf<Split<User>>(Split())
|
||||
var forwardZapToEditting by mutableStateOf(TextFieldValue(""))
|
||||
|
||||
// NSFW, Sensitive
|
||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||
|
||||
// GeoHash
|
||||
var wantsToAddGeoHash by mutableStateOf(false)
|
||||
var location: StateFlow<LocationState.LocationResult>? = null
|
||||
var wantsExclusiveGeoPost by mutableStateOf(false)
|
||||
|
||||
// ZapRaiser
|
||||
var canAddZapRaiser by mutableStateOf(false)
|
||||
var wantsZapraiser by mutableStateOf(false)
|
||||
var zapRaiserAmount by mutableStateOf<Long?>(null)
|
||||
|
||||
val draftTextChanges = kotlinx.coroutines.channels.Channel<String>(kotlinx.coroutines.channels.Channel.CONFLATED)
|
||||
|
||||
fun lnAddress(): String? = account?.userProfile()?.info?.lnAddress()
|
||||
|
||||
fun hasLnAddress(): Boolean = account?.userProfile()?.info?.lnAddress() != null
|
||||
|
||||
fun user(): User? = account?.userProfile()
|
||||
|
||||
open fun init(accountVM: AccountViewModel) {
|
||||
this.accountViewModel = accountVM
|
||||
this.account = accountVM.account
|
||||
this.canAddInvoice = hasLnAddress()
|
||||
this.canAddZapRaiser = hasLnAddress()
|
||||
|
||||
this.uploadState =
|
||||
ChatFileUploadState(
|
||||
account?.settings?.defaultFileServer ?: DEFAULT_MEDIA_SERVERS[0],
|
||||
)
|
||||
}
|
||||
|
||||
open fun load(channel: Channel) {
|
||||
this.channel = channel
|
||||
}
|
||||
|
||||
open fun reply(replyNote: Note) {
|
||||
replyTo.value = replyNote
|
||||
saveDraft()
|
||||
}
|
||||
|
||||
fun clearReply() {
|
||||
replyTo.value = null
|
||||
saveDraft()
|
||||
}
|
||||
|
||||
open fun editFromDraft(draft: Note) {
|
||||
val noteEvent = draft.event
|
||||
val noteAuthor = draft.author
|
||||
|
||||
if (noteEvent is DraftEvent && noteAuthor != null) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
accountViewModel?.createTempDraftNote(noteEvent) { innerNote ->
|
||||
if (innerNote != null) {
|
||||
val oldTag = (draft.event as? AddressableEvent)?.dTag()
|
||||
if (oldTag != null) {
|
||||
draftTag = oldTag
|
||||
}
|
||||
loadFromDraft(innerNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadFromDraft(draft: Note) {
|
||||
val draftEvent = draft.event ?: return
|
||||
|
||||
val localfowardZapTo = draftEvent.tags.zapSplitSetup()
|
||||
val totalWeight = localfowardZapTo.sumOf { it.weight }
|
||||
forwardZapTo = Split()
|
||||
localfowardZapTo.forEach {
|
||||
if (it is ZapSplitSetup) {
|
||||
val user = LocalCache.getOrCreateUser(it.pubKeyHex)
|
||||
forwardZapTo.addItem(user, (it.weight / totalWeight).toFloat())
|
||||
}
|
||||
// don't support edditing old-style splits.
|
||||
}
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
wantsForwardZapTo = localfowardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
|
||||
val geohash = draftEvent.getGeoHash()
|
||||
wantsToAddGeoHash = geohash != null
|
||||
|
||||
val zapraiser = draftEvent.zapraiserAmount()
|
||||
wantsZapraiser = zapraiser != null
|
||||
zapRaiserAmount = null
|
||||
if (zapraiser != null) {
|
||||
zapRaiserAmount = zapraiser
|
||||
}
|
||||
|
||||
if (forwardZapTo.items.isNotEmpty()) {
|
||||
wantsForwardZapTo = true
|
||||
}
|
||||
|
||||
if (draftEvent as? ChannelMessageEvent != null) {
|
||||
val replyId = draftEvent.reply()?.eventId
|
||||
if (replyId != null) {
|
||||
accountViewModel?.checkGetOrCreateNote(replyId) {
|
||||
replyTo.value = it
|
||||
}
|
||||
}
|
||||
} else if (draftEvent as? LiveActivitiesChatMessageEvent != null) {
|
||||
val replyId = draftEvent.reply()?.eventId
|
||||
if (replyId != null) {
|
||||
accountViewModel?.checkGetOrCreateNote(replyId) {
|
||||
replyTo.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message = TextFieldValue(draftEvent.content)
|
||||
|
||||
urlPreview = findUrlInMessage()
|
||||
}
|
||||
|
||||
fun sendPost(onDone: () -> Unit) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
sendPostSync()
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sendPostSync() {
|
||||
val template = createTemplate() ?: return
|
||||
val channelRelays = channel?.relays() ?: emptyList()
|
||||
|
||||
accountViewModel?.account?.signAndSendPrivately(template, channelRelays)
|
||||
|
||||
accountViewModel?.deleteDraft(draftTag)
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
fun sendDraft() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
sendDraftSync()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sendDraftSync() {
|
||||
val accountViewModel = accountViewModel ?: return
|
||||
|
||||
if (message.text.isBlank()) {
|
||||
account?.deleteDraft(draftTag)
|
||||
} else {
|
||||
val template = createTemplate() ?: return
|
||||
accountViewModel.account.createAndSendDraft(draftTag, template)
|
||||
}
|
||||
}
|
||||
|
||||
fun pickedMedia(list: ImmutableList<SelectedMedia>) {
|
||||
uploadState?.load(list)
|
||||
}
|
||||
|
||||
fun upload(
|
||||
onError: (title: String, message: String) -> Unit,
|
||||
context: Context,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
val myAccount = account ?: return@launch
|
||||
val uploadState = uploadState ?: return@launch
|
||||
|
||||
val myMultiOrchestrator = uploadState.multiOrchestrator ?: return@launch
|
||||
|
||||
isUploadingImage = true
|
||||
|
||||
val results =
|
||||
myMultiOrchestrator.upload(
|
||||
viewModelScope,
|
||||
uploadState.caption,
|
||||
uploadState.contentWarningReason,
|
||||
MediaCompressor.intToCompressorQuality(uploadState.mediaQualitySlider),
|
||||
uploadState.selectedServer,
|
||||
myAccount,
|
||||
context,
|
||||
)
|
||||
|
||||
if (results.allGood) {
|
||||
results.successful.forEach {
|
||||
if (it.result is UploadOrchestrator.OrchestratorResult.NIP95Result) {
|
||||
account?.createNip95(it.result.bytes, headerInfo = it.result.fileHeader, uploadState.caption, uploadState.contentWarningReason) { nip95 ->
|
||||
nip95attachments = nip95attachments + nip95
|
||||
val note = nip95.let { it1 -> account?.consumeNip95(it1.first, it1.second) }
|
||||
|
||||
note?.let {
|
||||
message = message.insertUrlAtCursor(it.toNostrUri())
|
||||
}
|
||||
|
||||
urlPreview = findUrlInMessage()
|
||||
}
|
||||
} else if (it.result is UploadOrchestrator.OrchestratorResult.ServerResult) {
|
||||
iMetaAttachments.add(it.result, uploadState.caption, uploadState.contentWarningReason)
|
||||
|
||||
message = message.insertUrlAtCursor(it.result.url)
|
||||
urlPreview = findUrlInMessage()
|
||||
}
|
||||
}
|
||||
|
||||
uploadState.reset()
|
||||
} else {
|
||||
val errorMessages = results.errors.map { stringRes(context, it.errorResource, *it.params) }.distinct()
|
||||
|
||||
onError(stringRes(context, R.string.failed_to_upload_media_no_details), errorMessages.joinToString(".\n"))
|
||||
}
|
||||
|
||||
isUploadingImage = false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createTemplate(): EventTemplate<out Event>? {
|
||||
val channel = channel ?: return null
|
||||
val accountViewModel = accountViewModel ?: return null
|
||||
|
||||
val tagger =
|
||||
NewMessageTagger(
|
||||
message = message.text,
|
||||
pTags = listOfNotNull(replyTo.value?.author),
|
||||
eTags = listOfNotNull(replyTo.value),
|
||||
channelHex = channel.idHex,
|
||||
dao = accountViewModel,
|
||||
)
|
||||
tagger.run()
|
||||
|
||||
val urls = findURLs(message.text)
|
||||
val usedAttachments = iMetaAttachments.filterIsIn(urls.toSet())
|
||||
val emojis = findEmoji(message.text, accountViewModel.account.myEmojis.value)
|
||||
|
||||
val channelRelays = channel.relays()
|
||||
val geoHash = (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString()
|
||||
|
||||
return if (channel is PublicChatChannel) {
|
||||
val replyingToEvent = replyTo.value?.toEventHint<ChannelMessageEvent>()
|
||||
val channelEvent = channel.event
|
||||
|
||||
if (replyingToEvent != null) {
|
||||
ChannelMessageEvent.reply(tagger.message, replyingToEvent) {
|
||||
notify(replyingToEvent.toPTag())
|
||||
|
||||
hashtags(findHashtags(tagger.message))
|
||||
references(findURLs(tagger.message))
|
||||
quotes(findNostrUris(tagger.message))
|
||||
|
||||
geoHash?.let { geohash(it) }
|
||||
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
} else if (channelEvent != null) {
|
||||
val hint = EventHintBundle(channelEvent, channelRelays.firstOrNull())
|
||||
ChannelMessageEvent.message(tagger.message, hint) {
|
||||
hashtags(findHashtags(tagger.message))
|
||||
references(findURLs(tagger.message))
|
||||
quotes(findNostrUris(tagger.message))
|
||||
|
||||
geoHash?.let { geohash(it) }
|
||||
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
} else {
|
||||
ChannelMessageEvent.message(tagger.message, ETag(channel.idHex, channelRelays.firstOrNull())) {
|
||||
hashtags(findHashtags(tagger.message))
|
||||
references(findURLs(tagger.message))
|
||||
quotes(findNostrUris(tagger.message))
|
||||
|
||||
geoHash?.let { geohash(it) }
|
||||
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
}
|
||||
} else if (channel is LiveActivitiesChannel) {
|
||||
val replyingToEvent = replyTo.value?.toEventHint<LiveActivitiesChatMessageEvent>()
|
||||
val activity = channel.info
|
||||
|
||||
if (replyingToEvent != null) {
|
||||
LiveActivitiesChatMessageEvent.reply(tagger.message, replyingToEvent) {
|
||||
notify(replyingToEvent.toPTag())
|
||||
|
||||
hashtags(findHashtags(tagger.message))
|
||||
references(findURLs(tagger.message))
|
||||
quotes(findNostrUris(tagger.message))
|
||||
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
} else if (activity != null) {
|
||||
val hint = EventHintBundle(activity, channelRelays.firstOrNull() ?: replyingToEvent?.relay)
|
||||
|
||||
LiveActivitiesChatMessageEvent.message(tagger.message, hint) {
|
||||
hashtags(findHashtags(tagger.message))
|
||||
references(findURLs(tagger.message))
|
||||
quotes(findNostrUris(tagger.message))
|
||||
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
} else {
|
||||
LiveActivitiesChatMessageEvent.message(tagger.message, channel.toATag()) {
|
||||
hashtags(findHashtags(tagger.message))
|
||||
references(findURLs(tagger.message))
|
||||
quotes(findNostrUris(tagger.message))
|
||||
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun findEmoji(
|
||||
message: String,
|
||||
myEmojiSet: List<Account.EmojiMedia>?,
|
||||
): List<EmojiUrlTag> {
|
||||
if (myEmojiSet == null) return emptyList()
|
||||
return CustomEmoji.findAllEmojiCodes(message).mapNotNull { possibleEmoji ->
|
||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.url.url) }
|
||||
}
|
||||
}
|
||||
|
||||
open fun cancel() {
|
||||
message = TextFieldValue("")
|
||||
|
||||
replyTo.value = null
|
||||
|
||||
urlPreview = null
|
||||
|
||||
wantsInvoice = false
|
||||
wantsZapraiser = false
|
||||
zapRaiserAmount = null
|
||||
|
||||
wantsForwardZapTo = false
|
||||
wantsToMarkAsSensitive = false
|
||||
|
||||
wantsToAddGeoHash = false
|
||||
|
||||
forwardZapTo = Split()
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
|
||||
userSuggestions.reset()
|
||||
userSuggestionsMainMessage = null
|
||||
|
||||
if (emojiSearch.value.isNotEmpty()) {
|
||||
emojiSearch.tryEmit("")
|
||||
}
|
||||
|
||||
draftTag = UUID.randomUUID().toString()
|
||||
|
||||
NostrSearchEventOrUserDataSource.clear()
|
||||
}
|
||||
|
||||
fun deleteDraft() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
accountViewModel?.deleteDraft(draftTag)
|
||||
}
|
||||
}
|
||||
|
||||
open fun findUrlInMessage(): String? = RichTextParser().parseValidUrls(message.text).firstOrNull()
|
||||
|
||||
private fun saveDraft() {
|
||||
draftTextChanges.trySend("")
|
||||
}
|
||||
|
||||
open fun addToMessage(it: String) {
|
||||
updateMessage(TextFieldValue(message.text + " " + it))
|
||||
}
|
||||
|
||||
open fun updateMessage(newMessage: TextFieldValue) {
|
||||
message = newMessage
|
||||
urlPreview = findUrlInMessage()
|
||||
|
||||
if (newMessage.selection.collapsed) {
|
||||
val lastWord = newMessage.currentWord()
|
||||
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.MAIN_MESSAGE
|
||||
|
||||
accountViewModel?.let {
|
||||
userSuggestions.processCurrentWord(lastWord, it)
|
||||
}
|
||||
|
||||
if (lastWord.startsWith(":")) {
|
||||
emojiSearch.tryEmit(lastWord)
|
||||
} else {
|
||||
if (emojiSearch.value.isNotBlank()) {
|
||||
emojiSearch.tryEmit("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
saveDraft()
|
||||
}
|
||||
|
||||
open fun updateZapForwardTo(newZapForwardTo: TextFieldValue) {
|
||||
forwardZapToEditting = newZapForwardTo
|
||||
if (newZapForwardTo.selection.collapsed) {
|
||||
val lastWord = newZapForwardTo.text
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.FORWARD_ZAPS
|
||||
accountViewModel?.let {
|
||||
userSuggestions.processCurrentWord(lastWord, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun autocompleteWithUser(item: User) {
|
||||
if (userSuggestionsMainMessage == UserSuggestionAnchor.MAIN_MESSAGE) {
|
||||
val lastWord = message.currentWord()
|
||||
message = userSuggestions.replaceCurrentWord(message, lastWord, item)
|
||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
||||
forwardZapTo.addItem(item)
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
}
|
||||
|
||||
userSuggestionsMainMessage = null
|
||||
userSuggestions.reset()
|
||||
|
||||
saveDraft()
|
||||
}
|
||||
|
||||
open fun autocompleteWithEmoji(item: Account.EmojiMedia) {
|
||||
val wordToInsert = ":${item.code}:"
|
||||
message = message.replaceCurrentWord(wordToInsert)
|
||||
|
||||
emojiSearch.tryEmit("")
|
||||
|
||||
saveDraft()
|
||||
}
|
||||
|
||||
open fun autocompleteWithEmojiUrl(item: Account.EmojiMedia) {
|
||||
val wordToInsert = item.url.url + " "
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
iMetaAttachments.downloadAndPrepare(
|
||||
item.url.url,
|
||||
accountViewModel?.account?.shouldUseTorForImageDownload() ?: false,
|
||||
)
|
||||
}
|
||||
|
||||
message = message.replaceCurrentWord(wordToInsert)
|
||||
|
||||
emojiSearch.tryEmit("")
|
||||
|
||||
urlPreview = findUrlInMessage()
|
||||
|
||||
saveDraft()
|
||||
}
|
||||
|
||||
fun canPost(): Boolean =
|
||||
message.text.isNotBlank() &&
|
||||
uploadState?.isUploadingImage != true &&
|
||||
!wantsInvoice &&
|
||||
(!wantsZapraiser || zapRaiserAmount != null) &&
|
||||
uploadState?.multiOrchestrator == null
|
||||
|
||||
fun insertAtCursor(newElement: String) {
|
||||
message = message.insertUrlAtCursor(newElement)
|
||||
}
|
||||
|
||||
fun locationFlow(): StateFlow<LocationState.LocationResult> {
|
||||
if (location == null) {
|
||||
location = Amethyst.instance.locationManager.geohashStateFlow
|
||||
}
|
||||
|
||||
return location!!
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
Log.d("Init", "OnCleared: ${this.javaClass.simpleName}")
|
||||
}
|
||||
|
||||
fun updateZapPercentage(
|
||||
index: Int,
|
||||
sliderValue: Float,
|
||||
) {
|
||||
forwardZapTo.updatePercentage(index, sliderValue)
|
||||
}
|
||||
|
||||
fun updateZapFromText() {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
val tagger = NewMessageTagger(message.text, emptyList(), emptyList(), null, accountViewModel!!)
|
||||
tagger.run()
|
||||
tagger.pTags?.forEach { taggedUser ->
|
||||
if (!forwardZapTo.items.any { it.key == taggedUser }) {
|
||||
forwardZapTo.addItem(taggedUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateZapRaiserAmount(newAmount: Long?) {
|
||||
zapRaiserAmount = newAmount
|
||||
saveDraft()
|
||||
}
|
||||
|
||||
fun toggleMarkAsSensitive() {
|
||||
wantsToMarkAsSensitive = !wantsToMarkAsSensitive
|
||||
saveDraft()
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 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.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import com.vitorpamplona.amethyst.R
|
||||
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.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.ShowEmojiSuggestionList
|
||||
import com.vitorpamplona.amethyst.ui.note.ShowUserSuggestionList
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
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.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldLeadingIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
@Composable
|
||||
fun EditFieldRow(
|
||||
channelScreenModel: ChannelNewMessageViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onSendNewMessage: () -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
channelScreenModel.replyTo.value?.let {
|
||||
DisplayReplyingToNote(it, accountViewModel, nav) {
|
||||
channelScreenModel.clearReply()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
launch(Dispatchers.IO) {
|
||||
channelScreenModel.draftTextChanges
|
||||
.receiveAsFlow()
|
||||
.debounce(1000)
|
||||
.collectLatest {
|
||||
channelScreenModel.sendDraft()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = EditFieldModifier,
|
||||
) {
|
||||
ShowUserSuggestionList(
|
||||
channelScreenModel.userSuggestions.userSuggestions,
|
||||
channelScreenModel::autocompleteWithUser,
|
||||
accountViewModel,
|
||||
)
|
||||
|
||||
ShowEmojiSuggestionList(
|
||||
channelScreenModel.emojiSuggestions,
|
||||
channelScreenModel::autocompleteWithEmoji,
|
||||
channelScreenModel::autocompleteWithEmojiUrl,
|
||||
accountViewModel,
|
||||
)
|
||||
|
||||
ThinPaddingTextField(
|
||||
value = channelScreenModel.message,
|
||||
onValueChange = { channelScreenModel.updateMessage(it) },
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
),
|
||||
shape = EditFieldBorder,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.reply_here),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
trailingIcon = {
|
||||
ThinSendButton(
|
||||
isActive =
|
||||
channelScreenModel.message.text.isNotBlank() && !channelScreenModel.isUploadingImage,
|
||||
modifier = EditFieldTrailingIconModifier,
|
||||
) {
|
||||
channelScreenModel.sendPost(onSendNewMessage)
|
||||
}
|
||||
},
|
||||
leadingIcon = {
|
||||
SelectFromGallery(
|
||||
isUploading = channelScreenModel.isUploadingImage,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
modifier = EditFieldLeadingIconModifier,
|
||||
onImageChosen = channelScreenModel::pickedMedia,
|
||||
)
|
||||
},
|
||||
colors =
|
||||
TextFieldDefaults.colors(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
visualTransformation = UrlUserTagTransformation(MaterialTheme.colorScheme.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -18,7 +18,7 @@
|
||||
* 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.list
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
@@ -48,10 +48,10 @@ import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.buildNewPostRoute
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata.ChannelMetadataDialog
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font12SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
|
||||
@@ -66,7 +66,7 @@ fun ChannelFabColumn(
|
||||
var wantsToCreateChannel by remember { mutableStateOf(false) }
|
||||
|
||||
if (wantsToCreateChannel) {
|
||||
NewChannelView({ wantsToCreateChannel = false }, accountViewModel = accountViewModel)
|
||||
ChannelMetadataDialog({ wantsToCreateChannel = false }, accountViewModel = accountViewModel)
|
||||
}
|
||||
|
||||
Column {
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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.list
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
+3
-3
@@ -18,7 +18,7 @@
|
||||
* 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.list
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms
|
||||
|
||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -27,8 +27,8 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.singlepane.MessagesSinglePane
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.twopane.MessagesTwoPane
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.singlepane.MessagesSinglePane
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.twopane.MessagesTwoPane
|
||||
|
||||
@Composable
|
||||
fun MessagesScreen(
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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.list
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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.list
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
+2
-2
@@ -18,7 +18,7 @@
|
||||
* 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.list.feed
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.feed
|
||||
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -42,7 +42,7 @@ import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.ChatroomHeaderCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChatroomHeaderCompose
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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.list.feed
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.feed
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
+7
-7
@@ -18,7 +18,7 @@
|
||||
* 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.list.singlepane
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.singlepane
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
@@ -35,12 +35,12 @@ import com.vitorpamplona.amethyst.ui.navigation.MainTopBar
|
||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisappearingScaffold
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.ChannelFabColumn
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.WatchAccountForListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.WatchLifecycleAndRefreshDataSource
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.feed.MessagesPager
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.feed.MessagesTabHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.feed.MessagesTabItem
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChannelFabColumn
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.WatchAccountForListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.WatchLifecycleAndRefreshDataSource
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.feed.MessagesPager
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.feed.MessagesTabHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.feed.MessagesTabItem
|
||||
|
||||
@Composable
|
||||
fun MessagesSinglePane(
|
||||
+6
-6
@@ -18,7 +18,7 @@
|
||||
* 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.list.twopane
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.twopane
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -33,11 +33,11 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.WatchAccountForListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.WatchLifecycleAndRefreshDataSource
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.feed.MessagesPager
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.feed.MessagesTabHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.feed.MessagesTabItem
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.WatchAccountForListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.WatchLifecycleAndRefreshDataSource
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.feed.MessagesPager
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.feed.MessagesTabHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.feed.MessagesTabItem
|
||||
|
||||
@Composable
|
||||
fun ChatroomList(
|
||||
+4
-4
@@ -18,7 +18,7 @@
|
||||
* 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.list.twopane
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.twopane
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -42,9 +42,9 @@ import com.vitorpamplona.amethyst.ui.navigation.MainTopBar
|
||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisappearingScaffold
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.ChannelFabColumn
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.Chatroom
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.Channel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ChannelView
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChannelFabColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||
|
||||
@Composable
|
||||
@@ -113,7 +113,7 @@ fun MessagesTwoPane(
|
||||
}
|
||||
|
||||
if (it.route == "Channel") {
|
||||
Channel(
|
||||
ChannelView(
|
||||
channelId = it.id,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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.list.twopane
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.twopane
|
||||
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
+1
-1
@@ -39,7 +39,7 @@ import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.messages.ChatroomMessageCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.ChatroomMessageCompose
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ fun ThinSendButton(
|
||||
) {
|
||||
IconButton(
|
||||
enabled = isActive,
|
||||
modifier = modifier,
|
||||
// modifier = modifier,
|
||||
onClick = onClick,
|
||||
) {
|
||||
Icon(
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForSearchAndDisplayIfNotFound
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisappearingScaffold
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.ChannelName
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChannelName
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
|
||||
+1
-1
@@ -147,7 +147,7 @@ import com.vitorpamplona.amethyst.ui.note.types.VideoDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.LevelFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.RenderFeedState
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.public.ChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ThinSendButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
@@ -116,7 +116,9 @@ val HalfStartPadding = Modifier.padding(start = 5.dp)
|
||||
val StdStartPadding = Modifier.padding(start = 10.dp)
|
||||
val StdTopPadding = Modifier.padding(top = 10.dp)
|
||||
val HalfTopPadding = Modifier.padding(top = 5.dp)
|
||||
|
||||
val HalfHalfVertPadding = Modifier.padding(vertical = 3.dp)
|
||||
val HalfHalfHorzModifier = Modifier.padding(horizontal = 3.dp)
|
||||
|
||||
val HalfPadding = Modifier.padding(5.dp)
|
||||
val StdPadding = Modifier.padding(10.dp)
|
||||
@@ -209,7 +211,7 @@ val CashuCardBorders = Modifier.fillMaxWidth().padding(10.dp).clip(shape = Quote
|
||||
|
||||
val EditFieldModifier =
|
||||
Modifier.padding(start = 10.dp, end = 10.dp, bottom = 10.dp, top = 5.dp).fillMaxWidth()
|
||||
val EditFieldTrailingIconModifier = Modifier.height(26.dp).padding(start = 5.dp, end = 0.dp)
|
||||
val EditFieldTrailingIconModifier = Modifier.padding(start = 5.dp, end = 0.dp)
|
||||
val EditFieldLeadingIconModifier = Modifier.height(32.dp).padding(start = 2.dp)
|
||||
|
||||
val ZeroPadding = PaddingValues(0.dp)
|
||||
@@ -301,3 +303,8 @@ val ripple24dp = ripple(bounded = false, radius = Size24dp)
|
||||
val defaultTweenDuration = 100
|
||||
val defaultTweenFloatSpec = tween<Float>(durationMillis = defaultTweenDuration)
|
||||
val defaultTweenIntOffsetSpec = tween<IntOffset>(durationMillis = defaultTweenDuration)
|
||||
|
||||
val StreamingHeaderModifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 50.dp, max = 300.dp)
|
||||
|
||||
@@ -17,21 +17,21 @@
|
||||
<string name="explicit_content">محتوى فاضح</string>
|
||||
<string name="spam">محتوى عشوائي</string>
|
||||
<string name="spam_description">عدد الأحداث المزعجة من هذا المرحل</string>
|
||||
<string name="impersonation">التمثيل</string>
|
||||
<string name="impersonation">أنتحال</string>
|
||||
<string name="illegal_behavior">تصرف غير قانوني</string>
|
||||
<string name="other">أخرى</string>
|
||||
<string name="unknown">غير معروف</string>
|
||||
<string name="relay_icon">أيقونة الخادم</string>
|
||||
<string name="relay_icon">أيقونة المرحل</string>
|
||||
<string name="unknown_author">كاتب غير معروف</string>
|
||||
<string name="copy_text">نسخ النص</string>
|
||||
<string name="copy_user_pubkey">نسخ المفتاح العام</string>
|
||||
<string name="copy_note_id">نسخ معرف </string>
|
||||
<string name="copy_note_id">نسخ معرف الملاحظة</string>
|
||||
<string name="broadcast">بث</string>
|
||||
<string name="request_deletion">حذف الطلب</string>
|
||||
<string name="request_deletion">طلب حذفها</string>
|
||||
<string name="block_report">حظر / تبليغ</string>
|
||||
<string name="block_hide_user"><![CDATA[حظر و إخفاء المستخدم]]></string>
|
||||
<string name="report_spam_scam">الابلاغ عن البريد العشوائي / الاحتيال</string>
|
||||
<string name="report_impersonation">الإبلاغ عن التمثيل</string>
|
||||
<string name="report_spam_scam">الإبلاغ عن إزعاج / احتيال</string>
|
||||
<string name="report_impersonation">الإبلاغ عن انتحال الشخصية</string>
|
||||
<string name="report_explicit_content">الإبلاغ عن المحتوى الصريح / الفاضح</string>
|
||||
<string name="report_illegal_behaviour">الإبلاغ عن سلوك غير قانوني</string>
|
||||
<string name="report_malware">الإبلاغ عن برمجيات خبيثة</string>
|
||||
@@ -270,15 +270,15 @@
|
||||
<string name="quick_action_share_browser_link">شارك رابط المتصفح</string>
|
||||
<string name="quick_action_share">مشاركة</string>
|
||||
<string name="quick_action_copy_user_id">معرف المؤلف (ID)</string>
|
||||
<string name="quick_action_copy_note_id">رمز/معرف الملاحظة (Note ID)</string>
|
||||
<string name="quick_action_copy_note_id">معرف الملاحظة</string>
|
||||
<string name="quick_action_copy_text">نسخ النص</string>
|
||||
<string name="quick_action_delete">حذف</string>
|
||||
<string name="quick_action_unfollow">الغاء المتابعة</string>
|
||||
<string name="quick_action_follow">متابعة</string>
|
||||
<string name="quick_action_request_deletion_gallery_title">إزالته من المعرض</string>
|
||||
<string name="quick_action_request_deletion_gallery_alert_body">إزالة هذه الوسيطة من المعرض.</string>
|
||||
<string name="quick_action_request_deletion_alert_title">طلب الحذف</string>
|
||||
<string name="quick_action_request_deletion_alert_body">سيطلب Amethyst حذف ملاحظتك من الخوادم (relays) الذي أنت متصل به حاليا. لا يوجد ضمان لحذف ملاحظتك بشكل دائم من تلك الخوادم أو من الخوادم الأخرى التي ربما حفظت فيها.</string>
|
||||
<string name="quick_action_request_deletion_alert_title">طلب حذفها</string>
|
||||
<string name="quick_action_request_deletion_alert_body">سيطلب Amethyst حذف ملاحظتك من المرحلات المتصل بها حاليا، لا يوجد ضمان لحذفها من هذه المرحلات أو من المرحلات الأخرى التي ربما حفظت فيها.</string>
|
||||
<string name="quick_action_block_dialog_btn">حظر </string>
|
||||
<string name="quick_action_delete_dialog_btn">حذف</string>
|
||||
<string name="quick_action_block">حظر</string>
|
||||
@@ -287,7 +287,7 @@
|
||||
<string name="quick_action_dont_show_again_button">لا تظهر مرة أخرى</string>
|
||||
<string name="report_dialog_spam">رسائل مزعجة أو رسائل خداع</string>
|
||||
<string name="report_dialog_profanity">سلوك بغيض</string>
|
||||
<string name="report_dialog_impersonation">انتحال الشخصية </string>
|
||||
<string name="report_dialog_impersonation">انتحال الشخصية</string>
|
||||
<string name="report_dialog_nudity">محتوى دموي أو خادش للحياء</string>
|
||||
<string name="report_dialog_illegal">تصرف غير قانوني</string>
|
||||
<string name="report_dialog_malware">برمجية خبيثة</string>
|
||||
@@ -368,6 +368,7 @@
|
||||
<string name="zap_forward_lnAddress"> user@ او Lnaddres </string>
|
||||
<string name="media_servers">خوادم الوسائط</string>
|
||||
<string name="upload_server_relays_nip95">المرحلات الخاصة بك (NIP-95)</string>
|
||||
<string name="upload_server_relays_nip95_explainer">الملفات مُستضافة في مرحلاتك. الـNIP جديد، تأكد من دعمهم له</string>
|
||||
<string name="connect_via_tor_short">إعداد Tor/Orbot</string>
|
||||
<string name="connect_via_tor">الاتصال من خلال إعدادات Orbot الخاص بك</string>
|
||||
<string name="do_you_really_want_to_disable_tor_title">قطع الاتصال بTor/Orbot؟</string>
|
||||
@@ -476,8 +477,7 @@
|
||||
<string name="live_stream_offline_tag">غير متصل</string>
|
||||
<string name="live_stream_ended_tag">انتهى</string>
|
||||
<string name="live_stream_planned_tag">مجدولة</string>
|
||||
<string name="are_you_sure_you_want_to_log_out">تسجيل الخروج سوف يؤدي الى جميع المعلومات المحلية الخاصة بك. تأكد من وجود نسخة احتياطية لمفاتحك السرية لتجنب فقدان حسابك.
|
||||
هل تريد الاستمرار؟</string>
|
||||
<string name="are_you_sure_you_want_to_log_out">تسجيل الخروج سوف يحذف معلوماتك من على الجهاز. تأكد من نسخ مفتاحك الخاص لتجنب فقدان الحساب. هل تريد ألاستمرار؟</string>
|
||||
<string name="followed_tags">العلامات المُتابعة</string>
|
||||
<string name="relay_setup">مرحلات</string>
|
||||
<string name="discover_community">مجتمع</string>
|
||||
@@ -545,6 +545,7 @@
|
||||
<string name="login_with_external_signer">تسجيل الدخول باستخدام Amber</string>
|
||||
<string name="status_update">تحديث الحالة</string>
|
||||
<string name="lightning_wallets_not_found">خطأ أثناء تحليل رسالة المشكل</string>
|
||||
<string name="poll_zap_value_min_max_explainer">تُحسب الاصوات بكمية الزاب، يمكنك وضع حد أعلى لتجنب انحياز التصويت وحد ادنى لتجنب المزعجين. يمكنك استخدام نفس الكمية للحدين للتأكد من ان جميع الاصوات متساوية. لا تضع أي حدود لإستقبال أي كمية.</string>
|
||||
<string name="error_dialog_talk_to_user">رسالة إلى المستخدم</string>
|
||||
<string name="error_dialog_button_ok">موافق</string>
|
||||
<string name="relay_information_document_error_assemble_url">فشل الوصول إلى
|
||||
@@ -554,6 +555,7 @@
|
||||
<string name="relay_information_document_error_failed_to_parse_response">فشل تحليل الرد من %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_failed_with_http">المرحل رفض الطلب %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_reach_server">فشل الوصول إلى %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_parse_result">فشل تحليل مخرجات %1$s: %2$s</string>
|
||||
<string name="relay_information_document_error_http_status">فشل %1$s مع الرمز %2$s</string>
|
||||
<string name="active_for">نشط لـ: </string>
|
||||
<string name="active_for_home">الصفحة الرئيسية</string>
|
||||
@@ -643,7 +645,27 @@
|
||||
<string name="reply_description">الرد</string>
|
||||
<string name="like_description">إعجاب</string>
|
||||
<string name="change_reaction">تغيير ردود الفعل السريعة</string>
|
||||
<string name="relay_info">المرحل %1$s</string>
|
||||
<string name="expand_relay_list">توسعة قائمة المرحلات</string>
|
||||
<string name="relay_list_selector">منتقي قائمة المرحلات</string>
|
||||
<string name="dm_relays_title">مرحلات الرسائل الخاصة</string>
|
||||
<string name="dm_relays_through">مرحلات: %1$s</string>
|
||||
<string name="dm_relays_regular">أستخدام المرحلات العادية</string>
|
||||
<string name="dm_relays_not_found">أعد مرحلات رسائلك الخاصة</string>
|
||||
<string name="dm_relays_not_found_description">هذا الإعداد يوضح للعملاء أين يرسلون الرسائل الخاصة لك، بدونه قد تفقد بعضهم.</string>
|
||||
<string name="dm_relays_not_found_examples">الخيارات الجيدة هي:\n - inbox.nostr.wine (مدفوع)\n - auth.nostr1.com (مجاني)\n - you.nostr1.com (مرحلات خاصة - مدفوع)</string>
|
||||
<string name="dm_relays_not_found_examples2">الخيارات الجيدة هي:\n - auth.nostr1.com (مجاني)\n - inbox.nostr.wine (مدفوع)\n - relay.0xchat.com (مجاني)</string>
|
||||
<string name="dm_relays_not_found_editing">اختر من واحد إلى ثلاث مرحلات لاستقبال رسائلك الخاصة بهم. مرحلات الرسائل الخاصة يجب أن تستقبل الرسائل من أي شخص وتسمح لك فقط بقراءتهم.</string>
|
||||
<string name="dm_relays_not_found_create_now">الإعداد الآن</string>
|
||||
<string name="search_relays_title">مرحلات البحث</string>
|
||||
<string name="search_relays_not_found">أعد مرحلات البحث</string>
|
||||
<string name="search_relays_not_found_description">إنشاء قائمة مرحلات مخصصة للبحث سوف يحسن من نتائج البحث.</string>
|
||||
<string name="search_relays_not_found_editing">أدخل من واحد إلى ثلاث مرحلات لاستخدامهم عند البحث عن محتوى أو اشخاص، تأكد من أن هذا المرحلات تدعم NIP-50</string>
|
||||
<string name="search_relays_not_found_examples">بعض المرحلات الجيدة:\n - nostr.wine\n - relay.nostr.band\n - relay.noswhere.com</string>
|
||||
<string name="relay_settings">إعدادات المرحل</string>
|
||||
<string name="edit_post">تعديل المنشور</string>
|
||||
<string name="accessibility_scan_qr_code">مسح رمز ال QR</string>
|
||||
<string name="dvm_looking_for_app">البحث عن تطبيق</string>
|
||||
<string name="delete_all">حذف الكل</string>
|
||||
<string name="delete_all_drafts_confirmation">متأكد من حذف جميع المسودات؟</string>
|
||||
</resources>
|
||||
|
||||
@@ -81,6 +81,13 @@
|
||||
<string name="thank_you_so_much">Merci beaucoup !</string>
|
||||
<string name="amount_in_sats">Montant en Sats</string>
|
||||
<string name="send_sats">Envoyer des Sats</string>
|
||||
<string name="secret_emoji_maker">Créateur d\'émojis secrets</string>
|
||||
<string name="secret_emoji_maker_explainer">Ajouter un émoji avec un message caché à la publication</string>
|
||||
<string name="secret_note_to_receiver">Note secrète pour le destinataire</string>
|
||||
<string name="secret_note_to_receiver_placeholder">Mon message caché</string>
|
||||
<string name="secret_visible_text">Préfixe visible</string>
|
||||
<string name="secret_visible_text_placeholder">😎</string>
|
||||
<string name="secret_add_to_text">Ajouter au message</string>
|
||||
<string name="error_parsing_preview_for">"Erreur d'analyse de l'aperçu pour %1$s : %2$s"</string>
|
||||
<string name="preview_card_image_for">"Aperçu de l'image pour %1$s"</string>
|
||||
<string name="new_channel">Nouveau canal</string>
|
||||
@@ -106,6 +113,7 @@
|
||||
<string name="global_feed">Flux mondial</string>
|
||||
<string name="search_feed">Flux de recherche</string>
|
||||
<string name="add_a_relay">Ajouter un relais</string>
|
||||
<string name="profile_name">Nom</string>
|
||||
<string name="display_name">Nom visible du public</string>
|
||||
<string name="my_display_name">Mon nom visible du public</string>
|
||||
<string name="my_awesome_name">Ostrich McGénial</string>
|
||||
|
||||
@@ -81,6 +81,13 @@
|
||||
<string name="thank_you_so_much">बहुत धन्यवाद!</string>
|
||||
<string name="amount_in_sats">मात्रा साट्स में</string>
|
||||
<string name="send_sats">साट्स भेजें</string>
|
||||
<string name="secret_emoji_maker">रहस्य चित्राक्षर कर्ता</string>
|
||||
<string name="secret_emoji_maker_explainer">गुप्त सन्देश युक्त चित्राक्षर जोडें पत्र के साथ</string>
|
||||
<string name="secret_note_to_receiver">गुप्त टीका ग्राहक के लिए</string>
|
||||
<string name="secret_note_to_receiver_placeholder">मेरा गुप्त सन्देश</string>
|
||||
<string name="secret_visible_text">दृश्यमान उपसर्ग</string>
|
||||
<string name="secret_visible_text_placeholder">😎</string>
|
||||
<string name="secret_add_to_text">पत्र के साथ जोडें</string>
|
||||
<string name="error_parsing_preview_for">"%1$s का पूर्वीक्षण परखने में अपक्रम : %2$s"</string>
|
||||
<string name="preview_card_image_for">"%1$s के लिए पूर्वीक्षण पत्ता चित्र"</string>
|
||||
<string name="new_channel">नयी प्रणाली</string>
|
||||
|
||||
@@ -81,6 +81,13 @@
|
||||
<string name="thank_you_so_much">Nagyon szépen köszönöm!</string>
|
||||
<string name="amount_in_sats">Összeg satoshiban</string>
|
||||
<string name="send_sats">Satoshi küldése</string>
|
||||
<string name="secret_emoji_maker">Titkos emodzsikészítő</string>
|
||||
<string name="secret_emoji_maker_explainer">Emodzsi hozzáadása rejtett üzenettel a bejegyzéshez</string>
|
||||
<string name="secret_note_to_receiver">Titkos jegyzet a kedvezményezettnek</string>
|
||||
<string name="secret_note_to_receiver_placeholder">Saját rejtett üzenet</string>
|
||||
<string name="secret_visible_text">Látható előtag</string>
|
||||
<string name="secret_visible_text_placeholder">😎</string>
|
||||
<string name="secret_add_to_text">Hozzáadás a bejegyzéshez</string>
|
||||
<string name="error_parsing_preview_for">"Hiba a következő előnézet elemzésekor %1$s : %2$s"</string>
|
||||
<string name="preview_card_image_for">"Kártyakép előnézete a következőhöz: %1$s"</string>
|
||||
<string name="new_channel">Új csatorna</string>
|
||||
|
||||
@@ -81,6 +81,13 @@
|
||||
<string name="thank_you_so_much">Dziękuję bardzo!</string>
|
||||
<string name="amount_in_sats">Kwota w Satsach</string>
|
||||
<string name="send_sats">Wyślij</string>
|
||||
<string name="secret_emoji_maker">Kreator tajnych emoji</string>
|
||||
<string name="secret_emoji_maker_explainer">Dodaj emoji z ukrytą wiadomością do wpisu</string>
|
||||
<string name="secret_note_to_receiver">Tajna notatka dla odbiorcy</string>
|
||||
<string name="secret_note_to_receiver_placeholder">Treść tajnej notatki</string>
|
||||
<string name="secret_visible_text">Widoczny prefiks</string>
|
||||
<string name="secret_visible_text_placeholder">😎</string>
|
||||
<string name="secret_add_to_text">Dodaj do wpisu</string>
|
||||
<string name="error_parsing_preview_for">"Błąd analizowania podglądu dla %1$s : %2$s"</string>
|
||||
<string name="preview_card_image_for">"Podgląd obrazu karty dla %1$s"</string>
|
||||
<string name="new_channel">Nowy kanał</string>
|
||||
|
||||
@@ -81,6 +81,13 @@
|
||||
<string name="thank_you_so_much">非常感谢!</string>
|
||||
<string name="amount_in_sats">聪金额</string>
|
||||
<string name="send_sats">发送聪</string>
|
||||
<string name="secret_emoji_maker">私密 Emoji 生成器</string>
|
||||
<string name="secret_emoji_maker_explainer">在帖子中添加 Emoji 到隐藏消息的帖子中</string>
|
||||
<string name="secret_note_to_receiver">发送私密笔记给收件人</string>
|
||||
<string name="secret_note_to_receiver_placeholder">我的隐藏消息</string>
|
||||
<string name="secret_visible_text">可见前缀</string>
|
||||
<string name="secret_visible_text_placeholder">😎</string>
|
||||
<string name="secret_add_to_text">添加到帖子</string>
|
||||
<string name="error_parsing_preview_for">"解析 %1$s 预览出错:%2$s"</string>
|
||||
<string name="preview_card_image_for">"预览 %1$s 卡片图片"</string>
|
||||
<string name="new_channel">新频道</string>
|
||||
@@ -106,6 +113,7 @@
|
||||
<string name="global_feed">全球</string>
|
||||
<string name="search_feed">搜索</string>
|
||||
<string name="add_a_relay">添加中继器</string>
|
||||
<string name="profile_name">名称</string>
|
||||
<string name="display_name">显示名称</string>
|
||||
<string name="my_display_name">我的显示名称</string>
|
||||
<string name="my_awesome_name">Nostr 太酷了</string>
|
||||
@@ -141,6 +149,7 @@
|
||||
<string name="conversations">会话</string>
|
||||
<string name="notes">动态</string>
|
||||
<string name="replies">回复</string>
|
||||
<string name="mutual">你的</string>
|
||||
<string name="gallery">相册</string>
|
||||
<string name="follows">"关注"</string>
|
||||
<string name="reports">"举报"</string>
|
||||
|
||||
@@ -109,4 +109,21 @@ abstract class NostrSigner(
|
||||
) as T,
|
||||
)
|
||||
}
|
||||
|
||||
fun <T : Event> assembleRumor(ev: EventTemplate<T>) = assembleRumor<T>(ev.createdAt, ev.kind, ev.tags, ev.content)
|
||||
|
||||
fun <T : Event> assembleRumor(
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
) = EventFactory.create(
|
||||
id = EventHasher.hashId(pubKey, createdAt, kind, tags, content),
|
||||
pubKey = pubKey,
|
||||
createdAt = createdAt,
|
||||
kind = kind,
|
||||
tags = tags,
|
||||
content = content,
|
||||
sig = "",
|
||||
) as T
|
||||
}
|
||||
|
||||
+11
-1
@@ -23,14 +23,17 @@ package com.vitorpamplona.quartz.nip28PublicChat.message
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.core.tagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.markedETag
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.base.IsInPublicChatChannel
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.base.channel
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.base.reply
|
||||
import com.vitorpamplona.quartz.nip37Drafts.ExposeInDraft
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
@@ -42,7 +45,8 @@ class ChannelMessageEvent(
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : BaseThreadedEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||
IsInPublicChatChannel {
|
||||
IsInPublicChatChannel,
|
||||
ExposeInDraft {
|
||||
override fun channel() = markedRoot() ?: unmarkedRoot()
|
||||
|
||||
override fun channelId() = channel()?.eventId
|
||||
@@ -51,6 +55,12 @@ class ChannelMessageEvent(
|
||||
|
||||
override fun unmarkedReplyTos() = super.unmarkedReplyTos().filter { it != channelId() }
|
||||
|
||||
override fun exposeInDraft() =
|
||||
tagArray<ChannelMessageEvent> {
|
||||
channel()?.let { markedETag(it) }
|
||||
reply()?.let { markedETag(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 42
|
||||
const val ALT = "Public chat message"
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.quartz.nip37Drafts
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.kinds.kind
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent.Companion.ALT_DESCRIPTION
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent.Companion.KIND
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
class DraftBuilder {
|
||||
companion object {
|
||||
fun <T : Event> encryptAndSign(
|
||||
dTag: String,
|
||||
draft: T,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (DraftEvent) -> Unit,
|
||||
) {
|
||||
signer.nip44Encrypt(draft.toJson(), signer.pubKey) { encryptedContent ->
|
||||
val template =
|
||||
eventTemplate<DraftEvent>(KIND, encryptedContent, createdAt) {
|
||||
alt(ALT_DESCRIPTION)
|
||||
dTag(dTag)
|
||||
kind(draft.kind)
|
||||
|
||||
if (draft is ExposeInDraft) {
|
||||
addAll(draft.exposeInDraft())
|
||||
}
|
||||
}
|
||||
|
||||
signer.sign(template) {
|
||||
it.addToCache(signer.pubKey, draft)
|
||||
onReady(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,8 @@ import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.kinds.kind
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
@@ -121,6 +123,7 @@ class DraftEvent(
|
||||
|
||||
companion object {
|
||||
const val KIND = 31234
|
||||
const val ALT_DESCRIPTION = "Draft Event"
|
||||
|
||||
fun createAddressTag(
|
||||
pubKey: HexKey,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.quartz.nip37Drafts
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
|
||||
interface ExposeInDraft {
|
||||
fun exposeInDraft(): TagArray
|
||||
}
|
||||
+12
-1
@@ -23,16 +23,20 @@ package com.vitorpamplona.quartz.nip53LiveActivities.chat
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.core.tagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.aTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.markedETag
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QTag
|
||||
import com.vitorpamplona.quartz.nip37Drafts.ExposeInDraft
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@@ -47,7 +51,8 @@ class LiveActivitiesChatMessageEvent(
|
||||
) : BaseThreadedEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||
EventHintProvider,
|
||||
PubKeyHintProvider,
|
||||
AddressHintProvider {
|
||||
AddressHintProvider,
|
||||
ExposeInDraft {
|
||||
override fun pubKeyHints() = tags.mapNotNull(PTag::parseAsHint)
|
||||
|
||||
override fun eventHints() = tags.mapNotNull(ETag::parseAsHint) + tags.mapNotNull(QTag::parseEventAsHint)
|
||||
@@ -64,6 +69,12 @@ class LiveActivitiesChatMessageEvent(
|
||||
|
||||
override fun unmarkedReplyTos() = super.markedReplyTos().minus(activityHex() ?: "")
|
||||
|
||||
override fun exposeInDraft() =
|
||||
tagArray<LiveActivitiesChatMessageEvent> {
|
||||
activity()?.let { aTag(it) }
|
||||
reply()?.let { markedETag(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 1311
|
||||
const val ALT = "Live activity chat message"
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds
|
||||
|
||||
import com.vitorpamplona.quartz.utils.toStringNoFragment
|
||||
import com.vitorpamplona.quartz.utils.toStringSchemeHost
|
||||
import org.czeal.rfc3986.URIReference
|
||||
|
||||
class UrlId(
|
||||
@@ -37,6 +36,6 @@ class UrlId(
|
||||
companion object {
|
||||
fun toScope(url: String) = URIReference.parse(url).normalize().toStringNoFragment()
|
||||
|
||||
fun toKind(url: String) = URIReference.parse(url).normalize().toStringSchemeHost()
|
||||
fun toKind(url: String) = "web"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user