2023-01-11 18:31:20 +00:00
|
|
|
package com.vitorpamplona.amethyst.ui.note
|
|
|
|
|
2023-03-09 14:34:25 +00:00
|
|
|
import android.content.Intent
|
2023-03-16 20:32:42 +00:00
|
|
|
import android.graphics.Bitmap
|
2023-03-28 21:11:38 +00:00
|
|
|
import android.util.Log
|
2023-05-07 12:53:52 +00:00
|
|
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
|
|
import androidx.compose.foundation.Image
|
|
|
|
import androidx.compose.foundation.background
|
|
|
|
import androidx.compose.foundation.border
|
|
|
|
import androidx.compose.foundation.clickable
|
|
|
|
import androidx.compose.foundation.combinedClickable
|
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
|
|
import androidx.compose.foundation.layout.Box
|
|
|
|
import androidx.compose.foundation.layout.Column
|
2023-06-03 00:30:49 +00:00
|
|
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
|
|
import androidx.compose.foundation.layout.FlowRow
|
2023-05-11 18:39:05 +00:00
|
|
|
import androidx.compose.foundation.layout.PaddingValues
|
2023-05-07 12:53:52 +00:00
|
|
|
import androidx.compose.foundation.layout.Row
|
|
|
|
import androidx.compose.foundation.layout.Spacer
|
|
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
import androidx.compose.foundation.layout.height
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
import androidx.compose.foundation.layout.size
|
|
|
|
import androidx.compose.foundation.layout.width
|
2023-01-11 18:31:20 +00:00
|
|
|
import androidx.compose.foundation.shape.CircleShape
|
2023-03-05 23:34:11 +00:00
|
|
|
import androidx.compose.foundation.shape.CutCornerShape
|
2023-03-16 18:20:30 +00:00
|
|
|
import androidx.compose.foundation.text.ClickableText
|
2023-05-11 18:39:05 +00:00
|
|
|
import androidx.compose.material.Button
|
|
|
|
import androidx.compose.material.ButtonDefaults
|
2023-05-07 12:53:52 +00:00
|
|
|
import androidx.compose.material.Divider
|
|
|
|
import androidx.compose.material.DropdownMenu
|
|
|
|
import androidx.compose.material.DropdownMenuItem
|
|
|
|
import androidx.compose.material.Icon
|
|
|
|
import androidx.compose.material.IconButton
|
|
|
|
import androidx.compose.material.LocalTextStyle
|
|
|
|
import androidx.compose.material.MaterialTheme
|
|
|
|
import androidx.compose.material.Text
|
|
|
|
import androidx.compose.material.darkColors
|
2023-02-06 22:16:27 +00:00
|
|
|
import androidx.compose.material.icons.Icons
|
2023-03-23 14:49:01 +00:00
|
|
|
import androidx.compose.material.icons.filled.Bolt
|
2023-02-06 22:16:27 +00:00
|
|
|
import androidx.compose.material.icons.filled.ExpandMore
|
2023-06-03 16:39:06 +00:00
|
|
|
import androidx.compose.material.icons.filled.Link
|
2023-02-06 22:16:27 +00:00
|
|
|
import androidx.compose.material.icons.filled.MoreVert
|
2023-05-16 02:18:12 +00:00
|
|
|
import androidx.compose.material.icons.filled.PushPin
|
2023-05-07 12:53:52 +00:00
|
|
|
import androidx.compose.material.lightColors
|
|
|
|
import androidx.compose.runtime.Composable
|
2023-05-30 16:03:33 +00:00
|
|
|
import androidx.compose.runtime.Immutable
|
2023-05-07 12:53:52 +00:00
|
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
|
|
import androidx.compose.runtime.Stable
|
2023-05-30 16:03:33 +00:00
|
|
|
import androidx.compose.runtime.derivedStateOf
|
2023-05-07 12:53:52 +00:00
|
|
|
import androidx.compose.runtime.getValue
|
2023-01-11 18:31:20 +00:00
|
|
|
import androidx.compose.runtime.livedata.observeAsState
|
2023-05-07 12:53:52 +00:00
|
|
|
import androidx.compose.runtime.mutableStateOf
|
|
|
|
import androidx.compose.runtime.remember
|
|
|
|
import androidx.compose.runtime.rememberCoroutineScope
|
|
|
|
import androidx.compose.runtime.setValue
|
2023-01-11 18:31:20 +00:00
|
|
|
import androidx.compose.ui.Alignment
|
2023-05-16 02:18:12 +00:00
|
|
|
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
2023-06-01 19:16:03 +00:00
|
|
|
import androidx.compose.ui.Alignment.Companion.TopEnd
|
2023-01-11 18:31:20 +00:00
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
import androidx.compose.ui.draw.clip
|
2023-05-11 18:39:05 +00:00
|
|
|
import androidx.compose.ui.graphics.Brush
|
2023-02-25 23:00:18 +00:00
|
|
|
import androidx.compose.ui.graphics.Color
|
|
|
|
import androidx.compose.ui.graphics.compositeOver
|
2023-03-16 20:32:42 +00:00
|
|
|
import androidx.compose.ui.graphics.luminance
|
2023-03-02 00:18:07 +00:00
|
|
|
import androidx.compose.ui.layout.ContentScale
|
2023-01-13 15:20:54 +00:00
|
|
|
import androidx.compose.ui.platform.LocalClipboardManager
|
2023-01-24 19:59:21 +00:00
|
|
|
import androidx.compose.ui.platform.LocalContext
|
2023-06-03 16:39:06 +00:00
|
|
|
import androidx.compose.ui.platform.LocalUriHandler
|
2023-01-26 16:16:57 +00:00
|
|
|
import androidx.compose.ui.res.painterResource
|
2023-02-28 18:20:49 +00:00
|
|
|
import androidx.compose.ui.res.stringResource
|
2023-03-09 14:34:25 +00:00
|
|
|
import androidx.compose.ui.text.AnnotatedString
|
2023-01-11 18:31:20 +00:00
|
|
|
import androidx.compose.ui.text.font.FontWeight
|
2023-03-05 23:34:11 +00:00
|
|
|
import androidx.compose.ui.text.style.TextAlign
|
2023-03-02 00:18:07 +00:00
|
|
|
import androidx.compose.ui.text.style.TextOverflow
|
2023-01-26 16:16:57 +00:00
|
|
|
import androidx.compose.ui.unit.Dp
|
2023-01-11 18:31:20 +00:00
|
|
|
import androidx.compose.ui.unit.dp
|
2023-03-28 12:46:07 +00:00
|
|
|
import androidx.compose.ui.unit.sp
|
2023-03-09 14:34:25 +00:00
|
|
|
import androidx.core.content.ContextCompat
|
2023-03-16 20:32:42 +00:00
|
|
|
import androidx.core.graphics.drawable.toBitmap
|
|
|
|
import androidx.core.graphics.get
|
2023-01-11 18:31:20 +00:00
|
|
|
import coil.compose.AsyncImage
|
2023-05-06 23:19:57 +00:00
|
|
|
import coil.compose.AsyncImagePainter
|
2023-06-01 00:08:30 +00:00
|
|
|
import coil.request.SuccessResult
|
2023-01-27 01:09:56 +00:00
|
|
|
import com.vitorpamplona.amethyst.NotificationCache
|
2023-01-26 16:16:57 +00:00
|
|
|
import com.vitorpamplona.amethyst.R
|
2023-05-05 01:57:19 +00:00
|
|
|
import com.vitorpamplona.amethyst.model.Channel
|
2023-03-08 22:18:25 +00:00
|
|
|
import com.vitorpamplona.amethyst.model.LocalCache
|
2023-01-11 18:31:20 +00:00
|
|
|
import com.vitorpamplona.amethyst.model.Note
|
2023-01-26 16:16:57 +00:00
|
|
|
import com.vitorpamplona.amethyst.model.User
|
2023-06-03 16:39:06 +00:00
|
|
|
import com.vitorpamplona.amethyst.model.UserMetadata
|
|
|
|
import com.vitorpamplona.amethyst.service.model.AppDefinitionEvent
|
2023-05-15 15:15:32 +00:00
|
|
|
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
|
2023-05-07 12:53:52 +00:00
|
|
|
import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.EventInterface
|
|
|
|
import com.vitorpamplona.amethyst.service.model.FileHeaderEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.FileStorageHeaderEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
2023-05-15 15:15:32 +00:00
|
|
|
import com.vitorpamplona.amethyst.service.model.Participant
|
2023-05-11 18:39:05 +00:00
|
|
|
import com.vitorpamplona.amethyst.service.model.PeopleListEvent
|
2023-05-16 02:18:12 +00:00
|
|
|
import com.vitorpamplona.amethyst.service.model.PinListEvent
|
2023-05-07 12:53:52 +00:00
|
|
|
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
2023-06-07 21:16:48 +00:00
|
|
|
import com.vitorpamplona.amethyst.service.model.RelaySetEvent
|
2023-05-07 12:53:52 +00:00
|
|
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
|
|
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
2023-06-06 19:51:43 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
2023-06-07 21:16:48 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
2023-06-06 19:51:43 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
2023-05-07 12:53:52 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
2023-05-16 01:26:59 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
2023-06-03 16:39:06 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
2023-05-28 21:35:57 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.LoadThumbAndThenVideoView
|
2023-05-07 12:53:52 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
|
|
|
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
|
|
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
|
|
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
2023-05-26 16:20:17 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
2023-06-07 21:16:48 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
|
2023-05-07 12:53:52 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
2023-05-15 15:15:32 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.VideoView
|
2023-05-07 12:53:52 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.ZoomableContent
|
|
|
|
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
2023-06-03 16:39:06 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog
|
2023-05-07 12:53:52 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.ZoomableLocalImage
|
|
|
|
import com.vitorpamplona.amethyst.ui.components.ZoomableLocalVideo
|
|
|
|
import com.vitorpamplona.amethyst.ui.components.ZoomableUrlImage
|
|
|
|
import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo
|
2023-06-03 16:39:06 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.figureOutMimeType
|
2023-05-07 12:53:52 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.components.imageExtensions
|
2023-06-07 22:50:29 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
|
2023-01-11 18:31:20 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
2023-03-03 21:00:47 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
|
2023-03-14 17:41:39 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
|
2023-03-23 14:49:01 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
2023-06-09 17:45:46 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
2023-03-08 22:07:56 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.theme.Following
|
2023-06-09 17:45:46 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
2023-05-29 18:58:10 +00:00
|
|
|
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
2023-06-04 01:46:26 +00:00
|
|
|
import kotlinx.collections.immutable.ImmutableList
|
2023-06-06 19:51:43 +00:00
|
|
|
import kotlinx.collections.immutable.ImmutableSet
|
2023-06-05 19:33:16 +00:00
|
|
|
import kotlinx.collections.immutable.persistentListOf
|
2023-06-06 19:51:43 +00:00
|
|
|
import kotlinx.collections.immutable.persistentSetOf
|
2023-06-04 01:46:26 +00:00
|
|
|
import kotlinx.collections.immutable.toImmutableList
|
2023-06-06 19:51:43 +00:00
|
|
|
import kotlinx.collections.immutable.toImmutableSet
|
2023-03-10 14:37:17 +00:00
|
|
|
import kotlinx.coroutines.Dispatchers
|
2023-05-07 01:58:25 +00:00
|
|
|
import kotlinx.coroutines.launch
|
2023-03-10 14:37:17 +00:00
|
|
|
import kotlinx.coroutines.withContext
|
2023-05-07 16:51:12 +00:00
|
|
|
import nostr.postr.toNpub
|
2023-05-01 20:42:58 +00:00
|
|
|
import java.io.File
|
2023-03-23 14:49:01 +00:00
|
|
|
import java.math.BigDecimal
|
2023-04-28 22:40:12 +00:00
|
|
|
import java.net.URL
|
2023-05-15 15:15:32 +00:00
|
|
|
import java.util.Locale
|
2023-01-11 18:31:20 +00:00
|
|
|
|
2023-05-22 18:55:06 +00:00
|
|
|
@OptIn(ExperimentalFoundationApi::class)
|
2023-01-11 18:31:20 +00:00
|
|
|
@Composable
|
2023-01-27 01:09:56 +00:00
|
|
|
fun NoteCompose(
|
|
|
|
baseNote: Note,
|
|
|
|
routeForLastRead: String? = null,
|
2023-06-05 19:33:16 +00:00
|
|
|
modifier: Modifier = remember { Modifier },
|
2023-02-20 23:09:57 +00:00
|
|
|
isBoostedNote: Boolean = false,
|
|
|
|
isQuotedNote: Boolean = false,
|
2023-02-27 22:14:15 +00:00
|
|
|
unPackReply: Boolean = true,
|
|
|
|
makeItShort: Boolean = false,
|
2023-03-09 23:55:57 +00:00
|
|
|
addMarginTop: Boolean = true,
|
2023-02-25 23:00:18 +00:00
|
|
|
parentBackgroundColor: Color? = null,
|
2023-01-27 01:09:56 +00:00
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-01-27 01:09:56 +00:00
|
|
|
) {
|
2023-02-19 16:22:01 +00:00
|
|
|
val noteState by baseNote.live().metadata.observeAsState()
|
2023-05-30 16:03:33 +00:00
|
|
|
val noteEvent = remember(noteState) { noteState?.note?.event }
|
2023-01-11 18:31:20 +00:00
|
|
|
|
2023-05-30 16:03:33 +00:00
|
|
|
if (noteEvent == null) {
|
2023-05-28 19:34:13 +00:00
|
|
|
var popupExpanded by remember { mutableStateOf(false) }
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-03-08 22:07:56 +00:00
|
|
|
BlankNote(
|
2023-05-10 23:08:56 +00:00
|
|
|
remember {
|
|
|
|
modifier.combinedClickable(
|
|
|
|
onClick = { },
|
|
|
|
onLongClick = { popupExpanded = true }
|
|
|
|
)
|
|
|
|
},
|
2023-06-09 14:25:06 +00:00
|
|
|
isBoostedNote || isQuotedNote
|
2023-03-08 22:07:56 +00:00
|
|
|
)
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-05-30 16:03:33 +00:00
|
|
|
NoteQuickActionMenu(baseNote, popupExpanded, { popupExpanded = false }, accountViewModel)
|
2023-01-11 18:31:20 +00:00
|
|
|
} else {
|
2023-05-30 16:03:33 +00:00
|
|
|
CheckHiddenNoteCompose(
|
|
|
|
baseNote,
|
|
|
|
routeForLastRead,
|
|
|
|
modifier,
|
|
|
|
isBoostedNote,
|
|
|
|
isQuotedNote,
|
|
|
|
unPackReply,
|
|
|
|
makeItShort,
|
|
|
|
addMarginTop,
|
|
|
|
parentBackgroundColor,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
fun CheckHiddenNoteCompose(
|
|
|
|
note: Note,
|
|
|
|
routeForLastRead: String? = null,
|
|
|
|
modifier: Modifier = Modifier,
|
|
|
|
isBoostedNote: Boolean = false,
|
|
|
|
isQuotedNote: Boolean = false,
|
|
|
|
unPackReply: Boolean = true,
|
|
|
|
makeItShort: Boolean = false,
|
|
|
|
addMarginTop: Boolean = true,
|
|
|
|
parentBackgroundColor: Color? = null,
|
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
nav: (String) -> Unit
|
|
|
|
) {
|
|
|
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
2023-01-27 01:09:56 +00:00
|
|
|
|
2023-05-30 16:03:33 +00:00
|
|
|
val isHidden by remember(accountState) {
|
|
|
|
derivedStateOf {
|
|
|
|
val isSensitive = note.event?.isSensitive() ?: false
|
2023-05-07 01:58:25 +00:00
|
|
|
|
2023-06-05 19:33:16 +00:00
|
|
|
accountState?.account?.isHidden(note.author!!) == true || (isSensitive && accountState?.account?.showSensitiveContent == false)
|
2023-05-30 16:03:33 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-18 18:06:53 +00:00
|
|
|
|
2023-05-30 16:03:33 +00:00
|
|
|
if (!isHidden) {
|
|
|
|
LoadedNoteCompose(
|
|
|
|
note,
|
|
|
|
routeForLastRead,
|
|
|
|
modifier,
|
|
|
|
isBoostedNote,
|
|
|
|
isQuotedNote,
|
|
|
|
unPackReply,
|
|
|
|
makeItShort,
|
|
|
|
addMarginTop,
|
|
|
|
parentBackgroundColor,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Immutable
|
|
|
|
data class NoteComposeReportState(
|
|
|
|
val isAcceptable: Boolean,
|
|
|
|
val canPreview: Boolean,
|
2023-06-06 19:51:43 +00:00
|
|
|
val relevantReports: ImmutableSet<Note>
|
2023-05-30 16:03:33 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
fun LoadedNoteCompose(
|
|
|
|
note: Note,
|
|
|
|
routeForLastRead: String? = null,
|
|
|
|
modifier: Modifier = Modifier,
|
|
|
|
isBoostedNote: Boolean = false,
|
|
|
|
isQuotedNote: Boolean = false,
|
|
|
|
unPackReply: Boolean = true,
|
|
|
|
makeItShort: Boolean = false,
|
|
|
|
addMarginTop: Boolean = true,
|
|
|
|
parentBackgroundColor: Color? = null,
|
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
nav: (String) -> Unit
|
|
|
|
) {
|
2023-06-01 23:56:31 +00:00
|
|
|
var state by remember {
|
|
|
|
mutableStateOf(
|
|
|
|
NoteComposeReportState(
|
|
|
|
isAcceptable = true,
|
|
|
|
canPreview = true,
|
2023-06-06 19:51:43 +00:00
|
|
|
relevantReports = persistentSetOf()
|
2023-06-01 23:56:31 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
2023-05-30 16:03:33 +00:00
|
|
|
|
2023-06-01 23:56:31 +00:00
|
|
|
WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports ->
|
|
|
|
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
|
2023-06-06 19:51:43 +00:00
|
|
|
state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports.toImmutableSet())
|
2023-03-08 22:07:56 +00:00
|
|
|
}
|
2023-05-30 16:03:33 +00:00
|
|
|
}
|
2023-03-08 22:07:56 +00:00
|
|
|
|
2023-06-01 23:56:31 +00:00
|
|
|
var showReportedNote by remember { mutableStateOf(false) }
|
|
|
|
|
2023-05-30 16:03:33 +00:00
|
|
|
val showHiddenNote by remember(state, showReportedNote) {
|
|
|
|
derivedStateOf {
|
|
|
|
!state.isAcceptable && !showReportedNote
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (showHiddenNote) {
|
|
|
|
HiddenNote(
|
|
|
|
state.relevantReports,
|
2023-06-01 19:16:03 +00:00
|
|
|
accountViewModel,
|
2023-05-30 16:03:33 +00:00
|
|
|
modifier,
|
|
|
|
isBoostedNote,
|
|
|
|
nav,
|
|
|
|
onClick = { showReportedNote = true }
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
val canPreview by remember(state, showReportedNote) {
|
|
|
|
derivedStateOf {
|
|
|
|
(!state.isAcceptable && showReportedNote) || state.canPreview
|
|
|
|
}
|
2023-05-28 19:34:13 +00:00
|
|
|
}
|
2023-05-30 16:03:33 +00:00
|
|
|
|
|
|
|
NormalNote(
|
|
|
|
note,
|
|
|
|
routeForLastRead,
|
|
|
|
modifier,
|
|
|
|
isBoostedNote,
|
|
|
|
isQuotedNote,
|
|
|
|
unPackReply,
|
|
|
|
makeItShort,
|
|
|
|
addMarginTop,
|
|
|
|
canPreview,
|
|
|
|
parentBackgroundColor,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
2023-05-28 19:34:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-01 23:56:31 +00:00
|
|
|
@Composable
|
|
|
|
private fun WatchForReports(
|
|
|
|
note: Note,
|
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
onChange: (Boolean, Boolean, Set<Note>) -> Unit
|
|
|
|
) {
|
|
|
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
|
|
val noteReportsState by note.live().reports.observeAsState()
|
|
|
|
|
|
|
|
LaunchedEffect(key1 = noteReportsState, key2 = accountState) {
|
|
|
|
launch(Dispatchers.Default) {
|
2023-06-07 16:26:32 +00:00
|
|
|
accountState?.account?.let { loggedIn ->
|
|
|
|
val newCanPreview = note.author?.pubkeyHex == loggedIn.userProfile().pubkeyHex ||
|
|
|
|
(note.author?.let { loggedIn.userProfile().isFollowingCached(it) } ?: true) ||
|
|
|
|
noteReportsState?.note?.hasAnyReports() != true
|
|
|
|
|
|
|
|
val newIsAcceptable = noteReportsState?.note?.let {
|
|
|
|
loggedIn.isAcceptable(it)
|
|
|
|
} ?: true
|
|
|
|
val newRelevantReports = noteReportsState?.note?.let {
|
|
|
|
loggedIn.getRelevantReports(it)
|
|
|
|
} ?: emptySet()
|
2023-06-01 23:56:31 +00:00
|
|
|
|
|
|
|
onChange(newIsAcceptable, newCanPreview, newRelevantReports)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-28 19:34:13 +00:00
|
|
|
@Composable
|
|
|
|
fun NormalNote(
|
|
|
|
baseNote: Note,
|
|
|
|
routeForLastRead: String? = null,
|
|
|
|
modifier: Modifier = Modifier,
|
|
|
|
isBoostedNote: Boolean = false,
|
|
|
|
isQuotedNote: Boolean = false,
|
|
|
|
unPackReply: Boolean = true,
|
|
|
|
makeItShort: Boolean = false,
|
|
|
|
addMarginTop: Boolean = true,
|
|
|
|
canPreview: Boolean = true,
|
|
|
|
parentBackgroundColor: Color? = null,
|
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
nav: (String) -> Unit
|
|
|
|
) {
|
|
|
|
val noteEvent = remember { baseNote.event }
|
|
|
|
val channelHex = remember { baseNote.channelHex() }
|
2023-02-27 18:41:23 +00:00
|
|
|
|
2023-05-28 19:34:13 +00:00
|
|
|
if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && channelHex != null) {
|
2023-06-01 19:16:03 +00:00
|
|
|
ChannelHeader(channelHex = channelHex, accountViewModel = accountViewModel, nav = nav)
|
2023-05-28 19:34:13 +00:00
|
|
|
} else if (noteEvent is BadgeDefinitionEvent) {
|
|
|
|
BadgeDisplay(baseNote = baseNote)
|
|
|
|
} else if (noteEvent is FileHeaderEvent) {
|
|
|
|
FileHeaderDisplay(baseNote)
|
|
|
|
} else if (noteEvent is FileStorageHeaderEvent) {
|
|
|
|
FileStorageHeaderDisplay(baseNote)
|
|
|
|
} else {
|
2023-06-09 14:02:56 +00:00
|
|
|
NoteWithReactions(
|
|
|
|
baseNote,
|
|
|
|
routeForLastRead,
|
|
|
|
modifier,
|
|
|
|
isBoostedNote,
|
|
|
|
isQuotedNote,
|
|
|
|
unPackReply,
|
|
|
|
makeItShort,
|
|
|
|
addMarginTop,
|
|
|
|
canPreview,
|
|
|
|
parentBackgroundColor,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2023-03-23 14:49:01 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
@Composable
|
|
|
|
@OptIn(ExperimentalFoundationApi::class)
|
|
|
|
private fun NoteWithReactions(
|
|
|
|
baseNote: Note,
|
|
|
|
routeForLastRead: String? = null,
|
|
|
|
modifier: Modifier = Modifier,
|
|
|
|
isBoostedNote: Boolean = false,
|
|
|
|
isQuotedNote: Boolean = false,
|
|
|
|
unPackReply: Boolean = true,
|
|
|
|
makeItShort: Boolean = false,
|
|
|
|
addMarginTop: Boolean = true,
|
|
|
|
canPreview: Boolean = true,
|
|
|
|
parentBackgroundColor: Color? = null,
|
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
nav: (String) -> Unit
|
|
|
|
) {
|
|
|
|
var isNew by remember { mutableStateOf<Boolean>(false) }
|
|
|
|
var popupExpanded by remember { mutableStateOf(false) }
|
2023-05-28 19:34:13 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
val scope = rememberCoroutineScope()
|
2023-05-28 19:34:13 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
LaunchedEffect(key1 = routeForLastRead) {
|
|
|
|
launch(Dispatchers.IO) {
|
|
|
|
routeForLastRead?.let {
|
|
|
|
val lastTime = NotificationCache.load(it)
|
2023-05-28 19:34:13 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
val createdAt = baseNote.createdAt()
|
|
|
|
if (createdAt != null) {
|
|
|
|
NotificationCache.markAsRead(it, createdAt)
|
|
|
|
|
|
|
|
val newIsNew = createdAt > lastTime
|
|
|
|
if (newIsNew != isNew) {
|
|
|
|
isNew = newIsNew
|
2023-05-10 23:08:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-05-28 19:34:13 +00:00
|
|
|
}
|
2023-06-09 14:02:56 +00:00
|
|
|
}
|
2023-02-28 18:20:49 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
val primaryColor = MaterialTheme.colors.newItemBackgroundColor
|
|
|
|
val defaultBackgroundColor = MaterialTheme.colors.background
|
2023-05-22 21:51:12 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
val backgroundColor = remember(isNew, parentBackgroundColor) {
|
|
|
|
if (isNew) {
|
|
|
|
if (parentBackgroundColor != null) {
|
|
|
|
primaryColor.compositeOver(parentBackgroundColor)
|
2023-05-28 19:34:13 +00:00
|
|
|
} else {
|
2023-06-09 14:02:56 +00:00
|
|
|
primaryColor.compositeOver(defaultBackgroundColor)
|
2023-05-10 23:08:56 +00:00
|
|
|
}
|
2023-06-09 14:02:56 +00:00
|
|
|
} else {
|
|
|
|
parentBackgroundColor ?: defaultBackgroundColor
|
2023-05-28 19:34:13 +00:00
|
|
|
}
|
2023-06-09 14:02:56 +00:00
|
|
|
}
|
2023-02-27 00:22:22 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
val columnModifier = remember(backgroundColor) {
|
|
|
|
modifier
|
|
|
|
.combinedClickable(
|
|
|
|
onClick = {
|
|
|
|
scope.launch {
|
|
|
|
routeFor(baseNote, accountViewModel.userProfile())?.let {
|
|
|
|
nav(it)
|
2023-05-28 19:34:13 +00:00
|
|
|
}
|
2023-06-09 14:02:56 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
onLongClick = { popupExpanded = true }
|
|
|
|
)
|
|
|
|
.background(backgroundColor)
|
|
|
|
}
|
|
|
|
|
|
|
|
val notBoostedNorQuote by remember {
|
|
|
|
derivedStateOf {
|
|
|
|
!isBoostedNote && !isQuotedNote
|
2023-05-28 19:34:13 +00:00
|
|
|
}
|
2023-06-09 14:02:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
val showSecondRow by remember {
|
|
|
|
derivedStateOf {
|
|
|
|
baseNote.event !is RepostEvent && !isBoostedNote && !isQuotedNote
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Column(modifier = columnModifier) {
|
|
|
|
Row(
|
|
|
|
modifier = remember {
|
|
|
|
Modifier
|
|
|
|
.padding(
|
|
|
|
start = if (!isBoostedNote) 12.dp else 0.dp,
|
|
|
|
end = if (!isBoostedNote) 12.dp else 0.dp,
|
|
|
|
top = if (addMarginTop && !isBoostedNote) 10.dp else 0.dp
|
2023-06-09 14:49:10 +00:00
|
|
|
// Don't add margin to the bottom because of the Divider down below
|
2023-06-09 14:02:56 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
) {
|
|
|
|
if (notBoostedNorQuote) {
|
|
|
|
DrawAuthorImages(baseNote, accountViewModel, nav)
|
2023-06-09 14:49:10 +00:00
|
|
|
Spacer(modifier = Modifier.width(10.dp))
|
2023-06-09 14:02:56 +00:00
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
NoteBody(
|
|
|
|
baseNote,
|
|
|
|
isQuotedNote,
|
|
|
|
unPackReply,
|
|
|
|
makeItShort,
|
|
|
|
canPreview,
|
|
|
|
showSecondRow,
|
|
|
|
backgroundColor,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
2023-05-28 19:34:13 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
NoteQuickActionMenu(
|
|
|
|
baseNote,
|
|
|
|
popupExpanded,
|
|
|
|
{ popupExpanded = false },
|
|
|
|
accountViewModel
|
|
|
|
)
|
|
|
|
}
|
2023-01-11 18:31:20 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
if (!makeItShort && baseNote.event !is RepostEvent) {
|
|
|
|
ReactionsRow(
|
|
|
|
baseNote,
|
|
|
|
notBoostedNorQuote,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
2023-06-09 14:25:06 +00:00
|
|
|
} else {
|
2023-06-09 15:27:02 +00:00
|
|
|
if (!isBoostedNote && baseNote.event !is RepostEvent) {
|
2023-06-09 14:25:06 +00:00
|
|
|
Spacer(modifier = Modifier.height(10.dp))
|
|
|
|
}
|
2023-06-09 14:02:56 +00:00
|
|
|
}
|
2023-01-11 18:31:20 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
if (!isQuotedNote && !isBoostedNote) {
|
|
|
|
Divider(
|
|
|
|
thickness = 0.25.dp
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-11 18:31:20 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
@Composable
|
|
|
|
private fun NoteBody(
|
|
|
|
baseNote: Note,
|
|
|
|
showAuthorPicture: Boolean = false,
|
|
|
|
unPackReply: Boolean = true,
|
|
|
|
makeItShort: Boolean = false,
|
|
|
|
canPreview: Boolean = true,
|
|
|
|
showSecondRow: Boolean,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
nav: (String) -> Unit
|
|
|
|
) {
|
2023-06-09 14:49:10 +00:00
|
|
|
Column() {
|
2023-06-09 14:02:56 +00:00
|
|
|
FirstUserInfoRow(
|
|
|
|
baseNote = baseNote,
|
|
|
|
showAuthorPicture = showAuthorPicture,
|
|
|
|
accountViewModel = accountViewModel,
|
|
|
|
nav = nav
|
|
|
|
)
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
if (showSecondRow) {
|
|
|
|
SecondUserInfoRow(
|
|
|
|
baseNote,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
|
|
|
}
|
2023-06-03 16:39:06 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
Spacer(modifier = Modifier.height(2.dp))
|
2023-02-19 00:14:52 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
if (!makeItShort) {
|
|
|
|
ReplyRow(
|
|
|
|
baseNote,
|
|
|
|
unPackReply,
|
|
|
|
backgroundColor,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
RenderNoteRow(
|
|
|
|
baseNote,
|
|
|
|
backgroundColor,
|
|
|
|
makeItShort,
|
|
|
|
canPreview,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
@Composable
|
|
|
|
private fun RenderNoteRow(
|
|
|
|
baseNote: Note,
|
|
|
|
backgroundColor: Color,
|
|
|
|
makeItShort: Boolean,
|
|
|
|
canPreview: Boolean,
|
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
nav: (String) -> Unit
|
|
|
|
) {
|
|
|
|
val noteEvent = remember { baseNote.event }
|
|
|
|
when (noteEvent) {
|
|
|
|
is AppDefinitionEvent -> {
|
|
|
|
RenderAppDefinition(baseNote, accountViewModel, nav)
|
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is ReactionEvent -> {
|
|
|
|
RenderReaction(baseNote, backgroundColor, accountViewModel, nav)
|
|
|
|
}
|
2023-05-11 18:39:05 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is RepostEvent -> {
|
|
|
|
RenderRepost(baseNote, backgroundColor, accountViewModel, nav)
|
|
|
|
}
|
2023-05-15 15:15:32 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is ReportEvent -> {
|
|
|
|
RenderReport(baseNote, backgroundColor, accountViewModel, nav)
|
|
|
|
}
|
2023-06-07 21:16:48 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is LongTextNoteEvent -> {
|
|
|
|
RenderLongFormContent(baseNote, accountViewModel, nav)
|
|
|
|
}
|
2023-05-16 02:18:12 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is BadgeAwardEvent -> {
|
|
|
|
RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav)
|
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is PeopleListEvent -> {
|
|
|
|
RenderPeopleList(baseNote, backgroundColor, accountViewModel, nav)
|
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is RelaySetEvent -> {
|
|
|
|
RelaySetList(baseNote, backgroundColor, accountViewModel, nav)
|
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is AudioTrackEvent -> {
|
|
|
|
RenderAudioTrack(baseNote, accountViewModel, nav)
|
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is PinListEvent -> {
|
|
|
|
RenderPinListEvent(baseNote, backgroundColor, accountViewModel, nav)
|
|
|
|
}
|
2023-05-28 19:34:13 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is PrivateDmEvent -> {
|
|
|
|
RenderPrivateMessage(
|
|
|
|
baseNote,
|
|
|
|
makeItShort,
|
|
|
|
canPreview,
|
|
|
|
backgroundColor,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
|
|
|
}
|
2023-06-07 22:50:29 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is HighlightEvent -> {
|
|
|
|
RenderHighlight(
|
|
|
|
baseNote,
|
|
|
|
makeItShort,
|
|
|
|
canPreview,
|
|
|
|
backgroundColor,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
|
|
|
}
|
2023-06-07 22:50:29 +00:00
|
|
|
|
2023-06-09 14:02:56 +00:00
|
|
|
is PollNoteEvent -> {
|
|
|
|
RenderPoll(
|
|
|
|
baseNote,
|
|
|
|
makeItShort,
|
|
|
|
canPreview,
|
|
|
|
backgroundColor,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
RenderTextEvent(
|
|
|
|
baseNote,
|
|
|
|
makeItShort,
|
|
|
|
canPreview,
|
|
|
|
backgroundColor,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
2023-06-07 22:50:29 +00:00
|
|
|
)
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-02 00:18:07 +00:00
|
|
|
|
2023-05-06 15:49:53 +00:00
|
|
|
fun routeFor(note: Note, loggedIn: User): String? {
|
|
|
|
val noteEvent = note.event
|
2023-03-02 00:18:07 +00:00
|
|
|
|
2023-05-06 15:49:53 +00:00
|
|
|
if (noteEvent is ChannelMessageEvent || noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) {
|
2023-06-01 23:56:31 +00:00
|
|
|
note.channelHex()?.let {
|
|
|
|
return "Channel/$it"
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
} else if (noteEvent is PrivateDmEvent) {
|
2023-05-29 21:11:56 +00:00
|
|
|
return "Room/${noteEvent.talkingWith(loggedIn.pubkeyHex)}"
|
2023-05-06 15:49:53 +00:00
|
|
|
} else {
|
|
|
|
return "Note/${note.idHex}"
|
|
|
|
}
|
2023-03-09 23:55:57 +00:00
|
|
|
|
2023-05-06 15:49:53 +00:00
|
|
|
return null
|
|
|
|
}
|
2023-03-09 23:55:57 +00:00
|
|
|
|
2023-05-06 15:49:53 +00:00
|
|
|
@Composable
|
|
|
|
private fun RenderTextEvent(
|
|
|
|
note: Note,
|
|
|
|
makeItShort: Boolean,
|
|
|
|
canPreview: Boolean,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-06 15:49:53 +00:00
|
|
|
) {
|
2023-05-26 16:20:17 +00:00
|
|
|
val eventContent = remember(note.event) { accountViewModel.decrypt(note) }
|
2023-04-28 22:40:12 +00:00
|
|
|
|
2023-05-06 15:49:53 +00:00
|
|
|
if (eventContent != null) {
|
2023-05-26 16:20:17 +00:00
|
|
|
val isAuthorTheLoggedUser = remember(note.event) { accountViewModel.isLoggedUser(note.author) }
|
2023-06-01 23:56:31 +00:00
|
|
|
|
2023-05-10 23:08:56 +00:00
|
|
|
if (makeItShort && isAuthorTheLoggedUser) {
|
2023-05-06 15:49:53 +00:00
|
|
|
Text(
|
|
|
|
text = eventContent,
|
|
|
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
|
|
maxLines = 2,
|
|
|
|
overflow = TextOverflow.Ellipsis
|
|
|
|
)
|
|
|
|
} else {
|
2023-05-26 16:20:17 +00:00
|
|
|
val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false }
|
2023-01-31 02:36:06 +00:00
|
|
|
|
2023-05-26 16:20:17 +00:00
|
|
|
SensitivityWarning(
|
|
|
|
hasSensitiveContent = hasSensitiveContent,
|
|
|
|
accountViewModel = accountViewModel
|
|
|
|
) {
|
2023-06-05 19:33:16 +00:00
|
|
|
val modifier = remember(note) { Modifier.fillMaxWidth() }
|
2023-06-06 19:51:43 +00:00
|
|
|
val tags = remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: ImmutableListOfLists() }
|
2023-05-26 16:20:17 +00:00
|
|
|
|
|
|
|
TranslatableRichTextViewer(
|
|
|
|
content = eventContent,
|
|
|
|
canPreview = canPreview && !makeItShort,
|
|
|
|
modifier = modifier,
|
|
|
|
tags = tags,
|
|
|
|
backgroundColor = backgroundColor,
|
|
|
|
accountViewModel = accountViewModel,
|
|
|
|
nav = nav
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-06-06 19:51:43 +00:00
|
|
|
val hashtags = remember(note.event) { note.event?.hashtags()?.toImmutableList() ?: persistentListOf() }
|
2023-05-24 16:34:09 +00:00
|
|
|
DisplayUncitedHashtags(hashtags, eventContent, nav)
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun RenderPoll(
|
|
|
|
note: Note,
|
|
|
|
makeItShort: Boolean,
|
|
|
|
canPreview: Boolean,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-06 15:49:53 +00:00
|
|
|
) {
|
|
|
|
val noteEvent = note.event as? PollNoteEvent ?: return
|
2023-06-01 00:08:07 +00:00
|
|
|
val eventContent = remember { noteEvent.content() }
|
2023-05-06 15:49:53 +00:00
|
|
|
|
|
|
|
if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
|
|
|
|
Text(
|
|
|
|
text = eventContent,
|
|
|
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
|
|
maxLines = 2,
|
|
|
|
overflow = TextOverflow.Ellipsis
|
|
|
|
)
|
|
|
|
} else {
|
2023-05-26 16:20:17 +00:00
|
|
|
val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false }
|
2023-06-05 19:33:16 +00:00
|
|
|
|
2023-06-06 19:51:43 +00:00
|
|
|
val tags = remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: ImmutableListOfLists() }
|
2023-06-05 19:33:16 +00:00
|
|
|
|
2023-05-26 16:20:17 +00:00
|
|
|
SensitivityWarning(
|
|
|
|
hasSensitiveContent = hasSensitiveContent,
|
|
|
|
accountViewModel = accountViewModel
|
|
|
|
) {
|
|
|
|
TranslatableRichTextViewer(
|
2023-06-05 19:33:16 +00:00
|
|
|
content = eventContent,
|
2023-05-26 16:20:17 +00:00
|
|
|
canPreview = canPreview && !makeItShort,
|
2023-06-05 19:33:16 +00:00
|
|
|
modifier = remember { Modifier.fillMaxWidth() },
|
|
|
|
tags = tags,
|
|
|
|
backgroundColor = backgroundColor,
|
|
|
|
accountViewModel = accountViewModel,
|
|
|
|
nav = nav
|
2023-05-26 16:20:17 +00:00
|
|
|
)
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-05-26 16:20:17 +00:00
|
|
|
PollNote(
|
|
|
|
note,
|
|
|
|
canPreview = canPreview && !makeItShort,
|
|
|
|
backgroundColor,
|
|
|
|
accountViewModel,
|
|
|
|
nav
|
|
|
|
)
|
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-06-06 19:51:43 +00:00
|
|
|
var hashtags = remember { noteEvent.hashtags().toImmutableList() }
|
2023-06-05 19:33:16 +00:00
|
|
|
DisplayUncitedHashtags(hashtags, eventContent, nav)
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-03 16:39:06 +00:00
|
|
|
@OptIn(ExperimentalFoundationApi::class)
|
|
|
|
@Composable
|
|
|
|
fun RenderAppDefinition(
|
|
|
|
note: Note,
|
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
nav: (String) -> Unit
|
|
|
|
) {
|
|
|
|
val noteEvent = note.event as? AppDefinitionEvent ?: return
|
|
|
|
|
|
|
|
var metadata by remember {
|
|
|
|
mutableStateOf<UserMetadata?>(null)
|
|
|
|
}
|
|
|
|
|
|
|
|
LaunchedEffect(key1 = noteEvent) {
|
|
|
|
launch(Dispatchers.Default) {
|
|
|
|
metadata = noteEvent.appMetaData()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
metadata?.let {
|
|
|
|
Box {
|
2023-06-09 17:19:14 +00:00
|
|
|
val clipboardManager = LocalClipboardManager.current
|
|
|
|
val uri = LocalUriHandler.current
|
|
|
|
|
2023-06-03 16:39:06 +00:00
|
|
|
if (!it.banner.isNullOrBlank()) {
|
|
|
|
var zoomImageDialogOpen by remember { mutableStateOf(false) }
|
|
|
|
|
|
|
|
AsyncImage(
|
|
|
|
model = it.banner,
|
|
|
|
contentDescription = stringResource(id = R.string.profile_image),
|
|
|
|
contentScale = ContentScale.FillWidth,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.height(125.dp)
|
|
|
|
.combinedClickable(
|
|
|
|
onClick = {},
|
|
|
|
onLongClick = {
|
|
|
|
clipboardManager.setText(AnnotatedString(it.banner!!))
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
if (zoomImageDialogOpen) {
|
|
|
|
ZoomableImageDialog(imageUrl = figureOutMimeType(it.banner!!), onDismiss = { zoomImageDialogOpen = false })
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Image(
|
|
|
|
painter = painterResource(R.drawable.profile_banner),
|
|
|
|
contentDescription = stringResource(id = R.string.profile_banner),
|
|
|
|
contentScale = ContentScale.FillWidth,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.height(125.dp)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
Column(
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.padding(horizontal = 10.dp)
|
|
|
|
.padding(top = 75.dp)
|
|
|
|
) {
|
|
|
|
Row(
|
|
|
|
horizontalArrangement = Arrangement.SpaceBetween,
|
|
|
|
verticalAlignment = Alignment.Bottom
|
|
|
|
) {
|
|
|
|
var zoomImageDialogOpen by remember { mutableStateOf(false) }
|
|
|
|
|
|
|
|
Box(Modifier.size(100.dp)) {
|
|
|
|
it.picture?.let {
|
|
|
|
AsyncImage(
|
|
|
|
model = it,
|
|
|
|
contentDescription = null,
|
|
|
|
contentScale = ContentScale.FillWidth,
|
|
|
|
modifier = Modifier
|
|
|
|
.border(
|
|
|
|
3.dp,
|
|
|
|
MaterialTheme.colors.background,
|
|
|
|
CircleShape
|
|
|
|
)
|
|
|
|
.clip(shape = CircleShape)
|
|
|
|
.fillMaxSize()
|
2023-06-03 20:01:39 +00:00
|
|
|
.background(MaterialTheme.colors.background)
|
2023-06-03 16:39:06 +00:00
|
|
|
.combinedClickable(
|
|
|
|
onClick = { zoomImageDialogOpen = true },
|
|
|
|
onLongClick = {
|
|
|
|
clipboardManager.setText(AnnotatedString(it))
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (zoomImageDialogOpen) {
|
|
|
|
ZoomableImageDialog(imageUrl = figureOutMimeType(it.banner!!), onDismiss = { zoomImageDialogOpen = false })
|
|
|
|
}
|
|
|
|
|
|
|
|
Spacer(Modifier.weight(1f))
|
|
|
|
|
|
|
|
Row(
|
|
|
|
modifier = Modifier
|
|
|
|
.height(35.dp)
|
|
|
|
.padding(bottom = 3.dp)
|
|
|
|
) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-05 22:19:23 +00:00
|
|
|
val name = remember(it) { it.anyName() }
|
|
|
|
name?.let {
|
2023-06-03 16:39:06 +00:00
|
|
|
Row(verticalAlignment = Alignment.Bottom, modifier = Modifier.padding(top = 7.dp)) {
|
|
|
|
CreateTextWithEmoji(
|
|
|
|
text = it,
|
2023-06-06 19:51:43 +00:00
|
|
|
tags = remember { (note.event?.tags() ?: emptyList()).toImmutableListOfLists() },
|
2023-06-03 16:39:06 +00:00
|
|
|
fontWeight = FontWeight.Bold,
|
|
|
|
fontSize = 25.sp
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-05 22:19:23 +00:00
|
|
|
val website = remember(it) { it.website }
|
2023-06-03 16:39:06 +00:00
|
|
|
if (!website.isNullOrEmpty()) {
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
|
|
Icon(
|
|
|
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
|
|
imageVector = Icons.Default.Link,
|
|
|
|
contentDescription = stringResource(R.string.website),
|
|
|
|
modifier = Modifier.size(16.dp)
|
|
|
|
)
|
|
|
|
|
|
|
|
ClickableText(
|
|
|
|
text = AnnotatedString(website.removePrefix("https://")),
|
|
|
|
onClick = { website.let { runCatching { uri.openUri(it) } } },
|
|
|
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
|
|
|
|
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
it.about?.let {
|
|
|
|
Row(
|
|
|
|
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)
|
|
|
|
) {
|
2023-06-06 19:51:43 +00:00
|
|
|
val tags = remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: ImmutableListOfLists() }
|
2023-06-03 16:39:06 +00:00
|
|
|
TranslatableRichTextViewer(
|
|
|
|
content = it,
|
|
|
|
canPreview = false,
|
2023-06-05 19:33:16 +00:00
|
|
|
tags = tags,
|
2023-06-03 16:39:06 +00:00
|
|
|
backgroundColor = MaterialTheme.colors.background,
|
|
|
|
accountViewModel = accountViewModel,
|
|
|
|
nav = nav
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-06 15:49:53 +00:00
|
|
|
@Composable
|
|
|
|
private fun RenderHighlight(
|
|
|
|
note: Note,
|
|
|
|
makeItShort: Boolean,
|
|
|
|
canPreview: Boolean,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-06 15:49:53 +00:00
|
|
|
) {
|
2023-06-09 15:27:27 +00:00
|
|
|
val quote = remember {
|
|
|
|
(note.event as? HighlightEvent)?.quote() ?: ""
|
|
|
|
}
|
|
|
|
val author = remember() {
|
|
|
|
(note.event as? HighlightEvent)?.author()
|
|
|
|
}
|
|
|
|
val url = remember() {
|
|
|
|
(note.event as? HighlightEvent)?.inUrl()
|
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
|
|
|
|
DisplayHighlight(
|
2023-06-09 15:27:27 +00:00
|
|
|
quote,
|
|
|
|
author,
|
|
|
|
url,
|
2023-05-06 15:49:53 +00:00
|
|
|
makeItShort,
|
|
|
|
canPreview,
|
|
|
|
backgroundColor,
|
|
|
|
accountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav
|
2023-05-06 15:49:53 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun RenderPrivateMessage(
|
|
|
|
note: Note,
|
|
|
|
makeItShort: Boolean,
|
|
|
|
canPreview: Boolean,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-06 15:49:53 +00:00
|
|
|
) {
|
|
|
|
val noteEvent = note.event as? PrivateDmEvent ?: return
|
2023-05-26 00:39:15 +00:00
|
|
|
|
2023-05-07 20:12:28 +00:00
|
|
|
val withMe = remember { noteEvent.with(accountViewModel.userProfile().pubkeyHex) }
|
2023-05-06 15:49:53 +00:00
|
|
|
|
|
|
|
if (withMe) {
|
2023-05-07 20:12:28 +00:00
|
|
|
val eventContent = remember { accountViewModel.decrypt(note) }
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-06-06 19:51:43 +00:00
|
|
|
val hashtags = remember(note.event?.id()) { note.event?.hashtags()?.toImmutableList() ?: persistentListOf() }
|
2023-05-28 19:16:50 +00:00
|
|
|
val modifier = remember(note.event?.id()) { Modifier.fillMaxWidth() }
|
|
|
|
val isAuthorTheLoggedUser = remember(note.event?.id()) { accountViewModel.isLoggedUser(note.author) }
|
2023-06-05 19:33:16 +00:00
|
|
|
|
2023-06-06 19:51:43 +00:00
|
|
|
val tags = remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: ImmutableListOfLists() }
|
2023-05-28 19:16:50 +00:00
|
|
|
|
2023-05-06 15:49:53 +00:00
|
|
|
if (eventContent != null) {
|
2023-05-26 00:39:15 +00:00
|
|
|
if (makeItShort && isAuthorTheLoggedUser) {
|
2023-05-06 15:49:53 +00:00
|
|
|
Text(
|
|
|
|
text = eventContent,
|
|
|
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
|
|
maxLines = 2,
|
|
|
|
overflow = TextOverflow.Ellipsis
|
|
|
|
)
|
|
|
|
} else {
|
2023-05-26 16:20:17 +00:00
|
|
|
val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false }
|
|
|
|
SensitivityWarning(
|
|
|
|
hasSensitiveContent = hasSensitiveContent,
|
|
|
|
accountViewModel = accountViewModel
|
|
|
|
) {
|
|
|
|
TranslatableRichTextViewer(
|
|
|
|
content = eventContent,
|
|
|
|
canPreview = canPreview && !makeItShort,
|
|
|
|
modifier = modifier,
|
|
|
|
tags = tags,
|
|
|
|
backgroundColor = backgroundColor,
|
|
|
|
accountViewModel = accountViewModel,
|
|
|
|
nav = nav
|
|
|
|
)
|
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-05-26 00:39:15 +00:00
|
|
|
DisplayUncitedHashtags(hashtags, eventContent, nav)
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2023-05-07 16:51:12 +00:00
|
|
|
val recipient = noteEvent.recipientPubKeyBytes()?.toNpub() ?: "Someone"
|
2023-05-06 15:49:53 +00:00
|
|
|
|
|
|
|
TranslatableRichTextViewer(
|
|
|
|
stringResource(
|
|
|
|
id = R.string.private_conversation_notification,
|
|
|
|
"@${note.author?.pubkeyNpub()}",
|
2023-05-07 16:51:12 +00:00
|
|
|
"@$recipient"
|
2023-05-06 15:49:53 +00:00
|
|
|
),
|
|
|
|
canPreview = !makeItShort,
|
|
|
|
Modifier.fillMaxWidth(),
|
2023-06-06 19:51:43 +00:00
|
|
|
ImmutableListOfLists(),
|
2023-05-06 15:49:53 +00:00
|
|
|
backgroundColor,
|
|
|
|
accountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav
|
2023-05-06 15:49:53 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-07 21:16:48 +00:00
|
|
|
@Composable
|
|
|
|
fun RelaySetList(
|
|
|
|
baseNote: Note,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
nav: (String) -> Unit
|
|
|
|
) {
|
|
|
|
DisplayRelaySet(baseNote, backgroundColor, accountViewModel, nav)
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:39:05 +00:00
|
|
|
@Composable
|
|
|
|
fun RenderPeopleList(
|
2023-05-28 19:34:13 +00:00
|
|
|
baseNote: Note,
|
2023-05-11 18:39:05 +00:00
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-11 18:39:05 +00:00
|
|
|
) {
|
2023-05-28 19:34:13 +00:00
|
|
|
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
|
2023-05-11 18:39:05 +00:00
|
|
|
}
|
|
|
|
|
2023-06-07 21:16:48 +00:00
|
|
|
@Composable
|
|
|
|
fun DisplayRelaySet(
|
|
|
|
baseNote: Note,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
nav: (String) -> Unit
|
|
|
|
) {
|
|
|
|
val noteEvent = baseNote.event as? RelaySetEvent ?: return
|
|
|
|
|
|
|
|
val relays by remember {
|
|
|
|
mutableStateOf<ImmutableList<String>>(
|
|
|
|
noteEvent.relays().toImmutableList()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
var expanded by remember {
|
|
|
|
mutableStateOf(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
val toMembersShow = if (expanded) {
|
|
|
|
relays
|
|
|
|
} else {
|
|
|
|
relays.take(3)
|
|
|
|
}
|
|
|
|
|
|
|
|
val relayListName by remember {
|
|
|
|
derivedStateOf {
|
|
|
|
"#${noteEvent.dTag()}"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val relayDescription by remember {
|
|
|
|
derivedStateOf {
|
|
|
|
noteEvent.description()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Text(
|
|
|
|
text = relayListName,
|
|
|
|
fontWeight = FontWeight.Bold,
|
|
|
|
maxLines = 1,
|
|
|
|
overflow = TextOverflow.Ellipsis,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.padding(5.dp),
|
|
|
|
textAlign = TextAlign.Center
|
|
|
|
)
|
|
|
|
|
|
|
|
relayDescription?.let {
|
|
|
|
Text(
|
|
|
|
text = it,
|
|
|
|
maxLines = 3,
|
|
|
|
overflow = TextOverflow.Ellipsis,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.padding(5.dp),
|
|
|
|
textAlign = TextAlign.Center,
|
|
|
|
color = Color.Gray
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
Box {
|
|
|
|
Column(modifier = Modifier.padding(top = 5.dp)) {
|
|
|
|
toMembersShow.forEach { relay ->
|
|
|
|
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = CenterVertically) {
|
|
|
|
Text(
|
|
|
|
relay.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/"),
|
|
|
|
fontWeight = FontWeight.Bold,
|
|
|
|
maxLines = 1,
|
|
|
|
overflow = TextOverflow.Ellipsis,
|
|
|
|
modifier = Modifier
|
|
|
|
.padding(start = 10.dp, bottom = 5.dp)
|
|
|
|
.weight(1f)
|
|
|
|
)
|
|
|
|
|
|
|
|
Column(modifier = Modifier.padding(start = 10.dp)) {
|
|
|
|
RelayOptionsAction(relay, accountViewModel)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (relays.size > 3 && !expanded) {
|
|
|
|
Row(
|
|
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
|
|
horizontalArrangement = Arrangement.Center,
|
|
|
|
modifier = Modifier
|
|
|
|
.align(Alignment.BottomCenter)
|
|
|
|
.fillMaxWidth()
|
|
|
|
.background(
|
|
|
|
brush = Brush.verticalGradient(
|
|
|
|
colors = listOf(
|
|
|
|
backgroundColor.copy(alpha = 0f),
|
|
|
|
backgroundColor
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
ShowMoreButton {
|
|
|
|
expanded = !expanded
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun RelayOptionsAction(
|
|
|
|
relay: String,
|
|
|
|
accountViewModel: AccountViewModel
|
|
|
|
) {
|
|
|
|
val userStateRelayInfo by accountViewModel.account.userProfile().live().relayInfo.observeAsState()
|
|
|
|
val isCurrentlyOnTheUsersList by remember(userStateRelayInfo) {
|
|
|
|
derivedStateOf {
|
|
|
|
userStateRelayInfo?.user?.latestContactList?.relays()?.none { it.key == relay } == true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var wantsToAddRelay by remember {
|
|
|
|
mutableStateOf("")
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wantsToAddRelay.isNotEmpty()) {
|
|
|
|
NewRelayListView({ wantsToAddRelay = "" }, accountViewModel, wantsToAddRelay)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isCurrentlyOnTheUsersList) {
|
|
|
|
AddRelayButton { wantsToAddRelay = relay }
|
|
|
|
} else {
|
|
|
|
RemoveRelayButton { wantsToAddRelay = relay }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
@OptIn(ExperimentalLayoutApi::class)
|
2023-05-11 18:39:05 +00:00
|
|
|
@Composable
|
|
|
|
fun DisplayPeopleList(
|
2023-05-28 19:34:13 +00:00
|
|
|
baseNote: Note,
|
2023-05-11 18:39:05 +00:00
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-11 18:39:05 +00:00
|
|
|
) {
|
2023-05-28 19:34:13 +00:00
|
|
|
val noteEvent = baseNote.event as? PeopleListEvent ?: return
|
2023-05-11 18:39:05 +00:00
|
|
|
|
|
|
|
var members by remember { mutableStateOf<List<User>>(listOf()) }
|
|
|
|
|
|
|
|
val account = accountViewModel.userProfile()
|
|
|
|
var expanded by remember {
|
|
|
|
mutableStateOf(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
val toMembersShow = if (expanded) {
|
|
|
|
members
|
|
|
|
} else {
|
|
|
|
members.take(3)
|
|
|
|
}
|
|
|
|
|
|
|
|
Text(
|
|
|
|
text = "#${noteEvent.dTag()}",
|
|
|
|
fontWeight = FontWeight.Bold,
|
|
|
|
maxLines = 1,
|
|
|
|
overflow = TextOverflow.Ellipsis,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.padding(5.dp),
|
|
|
|
textAlign = TextAlign.Center
|
|
|
|
)
|
|
|
|
|
2023-05-28 19:34:13 +00:00
|
|
|
LaunchedEffect(Unit) {
|
2023-06-01 19:16:03 +00:00
|
|
|
launch(Dispatchers.IO) {
|
2023-05-11 18:39:05 +00:00
|
|
|
members = noteEvent.bookmarkedPeople().mapNotNull { hex ->
|
|
|
|
LocalCache.checkGetOrCreateUser(hex)
|
|
|
|
}.sortedBy { account.isFollowing(it) }.reversed()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Box {
|
|
|
|
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
|
|
|
|
toMembersShow.forEach { user ->
|
|
|
|
Row(modifier = Modifier.fillMaxWidth()) {
|
|
|
|
UserCompose(
|
|
|
|
user,
|
|
|
|
overallModifier = Modifier,
|
|
|
|
accountViewModel = accountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav = nav
|
2023-05-11 18:39:05 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (members.size > 3 && !expanded) {
|
|
|
|
Row(
|
|
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
|
|
horizontalArrangement = Arrangement.Center,
|
|
|
|
modifier = Modifier
|
|
|
|
.align(Alignment.BottomCenter)
|
|
|
|
.fillMaxWidth()
|
|
|
|
.background(
|
|
|
|
brush = Brush.verticalGradient(
|
|
|
|
colors = listOf(
|
|
|
|
backgroundColor.copy(alpha = 0f),
|
|
|
|
backgroundColor
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
Button(
|
|
|
|
modifier = Modifier.padding(top = 10.dp),
|
|
|
|
onClick = { expanded = !expanded },
|
2023-06-09 17:45:46 +00:00
|
|
|
shape = ButtonBorder,
|
2023-05-11 18:39:05 +00:00
|
|
|
colors = ButtonDefaults.buttonColors(
|
|
|
|
backgroundColor = MaterialTheme.colors.primary.copy(alpha = 0.32f)
|
|
|
|
.compositeOver(MaterialTheme.colors.background)
|
|
|
|
),
|
|
|
|
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
|
|
|
|
) {
|
|
|
|
Text(text = stringResource(R.string.show_more), color = Color.White)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
@OptIn(ExperimentalLayoutApi::class)
|
2023-05-06 15:49:53 +00:00
|
|
|
@Composable
|
|
|
|
private fun RenderBadgeAward(
|
|
|
|
note: Note,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-06 15:49:53 +00:00
|
|
|
) {
|
|
|
|
if (note.replyTo.isNullOrEmpty()) return
|
|
|
|
|
|
|
|
val noteEvent = note.event as? BadgeAwardEvent ?: return
|
2023-05-06 23:19:57 +00:00
|
|
|
var awardees by remember { mutableStateOf<List<User>>(listOf()) }
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-05-07 12:53:52 +00:00
|
|
|
val account = accountViewModel.userProfile()
|
|
|
|
|
2023-05-06 15:49:53 +00:00
|
|
|
Text(text = stringResource(R.string.award_granted_to))
|
2023-05-07 00:02:54 +00:00
|
|
|
|
|
|
|
LaunchedEffect(key1 = note) {
|
2023-06-01 19:16:03 +00:00
|
|
|
launch(Dispatchers.IO) {
|
2023-05-07 00:02:54 +00:00
|
|
|
awardees = noteEvent.awardees().mapNotNull { hex ->
|
|
|
|
LocalCache.checkGetOrCreateUser(hex)
|
2023-05-07 12:53:52 +00:00
|
|
|
}.sortedBy { account.isFollowing(it) }.reversed()
|
2023-05-06 23:19:57 +00:00
|
|
|
}
|
2023-05-07 00:02:54 +00:00
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-05-07 00:02:54 +00:00
|
|
|
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
|
2023-05-07 12:53:52 +00:00
|
|
|
awardees.take(100).forEach { user ->
|
2023-05-07 00:03:04 +00:00
|
|
|
Row(
|
2023-05-10 23:08:56 +00:00
|
|
|
modifier = Modifier
|
|
|
|
.size(size = 35.dp)
|
|
|
|
.clickable {
|
2023-05-24 16:34:09 +00:00
|
|
|
nav("User/${user.pubkeyHex}")
|
2023-05-10 23:08:56 +00:00
|
|
|
},
|
2023-05-07 00:02:54 +00:00
|
|
|
verticalAlignment = Alignment.CenterVertically
|
|
|
|
) {
|
|
|
|
UserPicture(
|
|
|
|
baseUser = user,
|
2023-06-01 19:16:03 +00:00
|
|
|
accountViewModel = accountViewModel,
|
2023-05-07 00:02:54 +00:00
|
|
|
size = 35.dp
|
|
|
|
)
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
2023-05-07 00:02:54 +00:00
|
|
|
}
|
2023-05-07 12:53:52 +00:00
|
|
|
|
|
|
|
if (awardees.size > 100) {
|
|
|
|
Text(" and ${awardees.size - 100} others")
|
|
|
|
}
|
2023-05-07 00:02:54 +00:00
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-05-07 00:02:54 +00:00
|
|
|
note.replyTo?.firstOrNull()?.let {
|
|
|
|
NoteCompose(
|
|
|
|
it,
|
|
|
|
modifier = Modifier,
|
|
|
|
isBoostedNote = false,
|
|
|
|
isQuotedNote = true,
|
|
|
|
unPackReply = false,
|
|
|
|
parentBackgroundColor = backgroundColor,
|
|
|
|
accountViewModel = accountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav = nav
|
2023-05-07 00:02:54 +00:00
|
|
|
)
|
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun RenderReaction(
|
|
|
|
note: Note,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-06 15:49:53 +00:00
|
|
|
) {
|
|
|
|
note.replyTo?.lastOrNull()?.let {
|
|
|
|
NoteCompose(
|
|
|
|
it,
|
|
|
|
modifier = Modifier,
|
|
|
|
isBoostedNote = true,
|
|
|
|
unPackReply = false,
|
|
|
|
parentBackgroundColor = backgroundColor,
|
|
|
|
accountViewModel = accountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav = nav
|
2023-05-06 15:49:53 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reposts have trash in their contents.
|
|
|
|
val refactorReactionText =
|
|
|
|
if (note.event?.content() == "+") "❤" else note.event?.content() ?: ""
|
|
|
|
|
|
|
|
Text(
|
|
|
|
text = refactorReactionText
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun RenderRepost(
|
|
|
|
note: Note,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-06 15:49:53 +00:00
|
|
|
) {
|
2023-05-10 23:08:56 +00:00
|
|
|
val boostedNote = remember {
|
|
|
|
note.replyTo?.lastOrNull()
|
|
|
|
}
|
|
|
|
|
|
|
|
boostedNote?.let {
|
2023-05-06 15:49:53 +00:00
|
|
|
NoteCompose(
|
|
|
|
it,
|
2023-06-04 15:45:34 +00:00
|
|
|
modifier = remember { Modifier },
|
2023-05-06 15:49:53 +00:00
|
|
|
isBoostedNote = true,
|
|
|
|
unPackReply = false,
|
|
|
|
parentBackgroundColor = backgroundColor,
|
|
|
|
accountViewModel = accountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav = nav
|
2023-05-06 15:49:53 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-16 02:18:12 +00:00
|
|
|
@Composable
|
|
|
|
private fun RenderPinListEvent(
|
2023-05-28 19:34:13 +00:00
|
|
|
baseNote: Note,
|
2023-05-16 02:18:12 +00:00
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-16 02:18:12 +00:00
|
|
|
) {
|
2023-05-28 19:34:13 +00:00
|
|
|
PinListHeader(baseNote, backgroundColor, accountViewModel, nav)
|
2023-05-16 02:18:12 +00:00
|
|
|
}
|
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
@OptIn(ExperimentalLayoutApi::class)
|
2023-05-16 02:18:12 +00:00
|
|
|
@Composable
|
|
|
|
fun PinListHeader(
|
2023-05-28 19:34:13 +00:00
|
|
|
baseNote: Note,
|
2023-05-16 02:18:12 +00:00
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-16 02:18:12 +00:00
|
|
|
) {
|
2023-05-28 19:34:13 +00:00
|
|
|
val noteEvent = baseNote.event as? PinListEvent ?: return
|
2023-05-16 02:18:12 +00:00
|
|
|
|
2023-06-05 19:33:16 +00:00
|
|
|
val pins by remember { mutableStateOf(noteEvent.pins()) }
|
2023-05-16 02:18:12 +00:00
|
|
|
|
|
|
|
var expanded by remember {
|
|
|
|
mutableStateOf(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
val pinsToShow = if (expanded) {
|
|
|
|
pins
|
|
|
|
} else {
|
|
|
|
pins.take(3)
|
|
|
|
}
|
|
|
|
|
|
|
|
Text(
|
|
|
|
text = "#${noteEvent.dTag()}",
|
|
|
|
fontWeight = FontWeight.Bold,
|
|
|
|
maxLines = 1,
|
|
|
|
overflow = TextOverflow.Ellipsis,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.padding(5.dp),
|
|
|
|
textAlign = TextAlign.Center
|
|
|
|
)
|
|
|
|
|
|
|
|
Box {
|
|
|
|
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
|
|
|
|
pinsToShow.forEach { pin ->
|
|
|
|
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = CenterVertically) {
|
|
|
|
Icon(
|
|
|
|
imageVector = Icons.Default.PushPin,
|
|
|
|
contentDescription = null,
|
2023-05-16 12:32:03 +00:00
|
|
|
tint = MaterialTheme.colors.onBackground.copy(0.32f),
|
2023-05-16 02:18:12 +00:00
|
|
|
modifier = Modifier.size(15.dp)
|
|
|
|
)
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.width(5.dp))
|
|
|
|
|
|
|
|
TranslatableRichTextViewer(
|
|
|
|
content = pin,
|
|
|
|
canPreview = true,
|
2023-06-06 19:51:43 +00:00
|
|
|
tags = remember { ImmutableListOfLists() },
|
2023-05-16 02:18:12 +00:00
|
|
|
backgroundColor = backgroundColor,
|
|
|
|
accountViewModel = accountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav = nav
|
2023-05-16 02:18:12 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pins.size > 3 && !expanded) {
|
|
|
|
Row(
|
|
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
|
|
horizontalArrangement = Arrangement.Center,
|
|
|
|
modifier = Modifier
|
|
|
|
.align(Alignment.BottomCenter)
|
|
|
|
.fillMaxWidth()
|
|
|
|
.background(
|
|
|
|
brush = Brush.verticalGradient(
|
|
|
|
colors = listOf(
|
|
|
|
backgroundColor.copy(alpha = 0f),
|
|
|
|
backgroundColor
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
Button(
|
|
|
|
modifier = Modifier.padding(top = 10.dp),
|
|
|
|
onClick = { expanded = !expanded },
|
2023-06-09 17:45:46 +00:00
|
|
|
shape = ButtonBorder,
|
2023-05-16 02:18:12 +00:00
|
|
|
colors = ButtonDefaults.buttonColors(
|
|
|
|
backgroundColor = MaterialTheme.colors.primary.copy(alpha = 0.32f)
|
|
|
|
.compositeOver(MaterialTheme.colors.background)
|
|
|
|
),
|
|
|
|
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
|
|
|
|
) {
|
|
|
|
Text(text = stringResource(R.string.show_more), color = Color.White)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-15 15:15:32 +00:00
|
|
|
@Composable
|
|
|
|
private fun RenderAudioTrack(
|
|
|
|
note: Note,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-15 15:15:32 +00:00
|
|
|
) {
|
|
|
|
val noteEvent = note.event as? AudioTrackEvent ?: return
|
|
|
|
|
2023-06-01 19:16:03 +00:00
|
|
|
AudioTrackHeader(noteEvent, accountViewModel, nav)
|
2023-05-15 15:15:32 +00:00
|
|
|
}
|
|
|
|
|
2023-05-06 15:49:53 +00:00
|
|
|
@Composable
|
|
|
|
private fun RenderLongFormContent(
|
|
|
|
note: Note,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-06 15:49:53 +00:00
|
|
|
) {
|
|
|
|
val noteEvent = note.event as? LongTextNoteEvent ?: return
|
|
|
|
|
2023-06-01 19:16:03 +00:00
|
|
|
LongFormHeader(noteEvent, note, accountViewModel)
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
2023-05-30 16:03:33 +00:00
|
|
|
private fun RenderReport(
|
|
|
|
note: Note,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
nav: (String) -> Unit
|
|
|
|
) {
|
|
|
|
val noteEvent = note.event as? ReportEvent ?: return
|
|
|
|
|
2023-05-10 23:08:56 +00:00
|
|
|
val base = remember {
|
2023-05-30 16:03:33 +00:00
|
|
|
(noteEvent.reportedPost() + noteEvent.reportedAuthor())
|
2023-05-10 23:08:56 +00:00
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-05-10 23:08:56 +00:00
|
|
|
val reportType = base.map {
|
2023-05-06 15:49:53 +00:00
|
|
|
when (it.reportType) {
|
|
|
|
ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content)
|
|
|
|
ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity)
|
|
|
|
ReportEvent.ReportType.PROFANITY -> stringResource(R.string.profanity_hateful_speech)
|
|
|
|
ReportEvent.ReportType.SPAM -> stringResource(R.string.spam)
|
|
|
|
ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation)
|
|
|
|
ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior)
|
|
|
|
}
|
|
|
|
}.toSet().joinToString(", ")
|
|
|
|
|
2023-05-30 16:03:33 +00:00
|
|
|
val content = remember {
|
|
|
|
reportType + (note.event?.content()?.ifBlank { null }?.let { ": $it" } ?: "")
|
|
|
|
}
|
|
|
|
|
|
|
|
TranslatableRichTextViewer(
|
|
|
|
content = content,
|
|
|
|
canPreview = true,
|
|
|
|
modifier = remember { Modifier },
|
2023-06-06 19:51:43 +00:00
|
|
|
tags = remember { ImmutableListOfLists() },
|
2023-05-30 16:03:33 +00:00
|
|
|
backgroundColor = backgroundColor,
|
|
|
|
accountViewModel = accountViewModel,
|
|
|
|
nav = nav
|
2023-05-06 15:49:53 +00:00
|
|
|
)
|
|
|
|
|
2023-05-30 16:03:33 +00:00
|
|
|
note.replyTo?.lastOrNull()?.let {
|
|
|
|
NoteCompose(
|
|
|
|
baseNote = it,
|
|
|
|
isQuotedNote = true,
|
|
|
|
modifier = Modifier
|
|
|
|
.padding(top = 5.dp)
|
|
|
|
.fillMaxWidth()
|
2023-06-09 17:45:46 +00:00
|
|
|
.clip(shape = QuoteBorder)
|
2023-05-30 16:03:33 +00:00
|
|
|
.border(
|
|
|
|
1.dp,
|
|
|
|
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
2023-06-09 17:45:46 +00:00
|
|
|
QuoteBorder
|
2023-05-30 16:03:33 +00:00
|
|
|
),
|
|
|
|
unPackReply = false,
|
|
|
|
makeItShort = true,
|
|
|
|
parentBackgroundColor = backgroundColor,
|
|
|
|
accountViewModel = accountViewModel,
|
|
|
|
nav = nav
|
|
|
|
)
|
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun ReplyRow(
|
|
|
|
note: Note,
|
|
|
|
unPackReply: Boolean,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-06 15:49:53 +00:00
|
|
|
) {
|
|
|
|
val noteEvent = note.event
|
|
|
|
|
2023-05-07 00:02:54 +00:00
|
|
|
if (noteEvent is TextNoteEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
|
2023-05-10 23:08:56 +00:00
|
|
|
val replyingDirectlyTo = remember {
|
|
|
|
note.replyTo?.lastOrNull()
|
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
if (replyingDirectlyTo != null && unPackReply) {
|
|
|
|
NoteCompose(
|
|
|
|
baseNote = replyingDirectlyTo,
|
|
|
|
isQuotedNote = true,
|
|
|
|
modifier = Modifier
|
|
|
|
.padding(top = 5.dp)
|
|
|
|
.fillMaxWidth()
|
2023-06-09 17:45:46 +00:00
|
|
|
.clip(shape = QuoteBorder)
|
2023-05-06 15:49:53 +00:00
|
|
|
.border(
|
|
|
|
1.dp,
|
|
|
|
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
2023-06-09 17:45:46 +00:00
|
|
|
QuoteBorder
|
2023-05-06 15:49:53 +00:00
|
|
|
),
|
|
|
|
unPackReply = false,
|
|
|
|
makeItShort = true,
|
|
|
|
parentBackgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.05f)
|
|
|
|
.compositeOver(backgroundColor),
|
|
|
|
accountViewModel = accountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav = nav
|
2023-05-06 15:49:53 +00:00
|
|
|
)
|
|
|
|
} else {
|
2023-06-04 15:56:53 +00:00
|
|
|
// ReplyInformation(note.replyTo, noteEvent.mentions(), accountViewModel, nav)
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(5.dp))
|
2023-05-07 00:02:54 +00:00
|
|
|
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
|
2023-06-06 19:51:43 +00:00
|
|
|
val channelHex = note.channelHex()
|
|
|
|
channelHex?.let {
|
|
|
|
val replies = remember { note.replyTo?.toImmutableList() }
|
|
|
|
val mentions = remember { noteEvent.mentions().toImmutableList() }
|
|
|
|
|
|
|
|
ReplyInformationChannel(replies, mentions, it, accountViewModel, nav)
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(5.dp))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun SecondUserInfoRow(
|
|
|
|
note: Note,
|
2023-06-01 19:16:03 +00:00
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-06 15:49:53 +00:00
|
|
|
) {
|
2023-05-10 23:08:56 +00:00
|
|
|
val noteEvent = remember { note.event } ?: return
|
|
|
|
val noteAuthor = remember { note.author } ?: return
|
2023-05-06 15:49:53 +00:00
|
|
|
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
2023-06-04 16:17:02 +00:00
|
|
|
ObserveDisplayNip05Status(noteAuthor, remember { Modifier.weight(1f) })
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-06-06 19:51:43 +00:00
|
|
|
val baseReward = remember { noteEvent.getReward()?.let { Reward(it) } }
|
2023-05-06 15:49:53 +00:00
|
|
|
if (baseReward != null) {
|
2023-06-01 19:16:03 +00:00
|
|
|
DisplayReward(baseReward, note, accountViewModel, nav)
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
|
2023-05-10 23:08:56 +00:00
|
|
|
val pow = remember { noteEvent.getPoWRank() }
|
2023-05-06 15:49:53 +00:00
|
|
|
if (pow > 20) {
|
|
|
|
DisplayPoW(pow)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun FirstUserInfoRow(
|
|
|
|
baseNote: Note,
|
|
|
|
showAuthorPicture: Boolean,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-05-06 15:49:53 +00:00
|
|
|
) {
|
2023-05-10 23:08:56 +00:00
|
|
|
val eventNote = remember { baseNote.event } ?: return
|
|
|
|
val time = remember { baseNote.createdAt() } ?: return
|
|
|
|
val padding = remember {
|
|
|
|
Modifier.padding(horizontal = 5.dp)
|
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
|
|
if (showAuthorPicture) {
|
2023-06-07 16:26:32 +00:00
|
|
|
NoteAuthorPicture(baseNote, nav, accountViewModel, remember { 25.dp })
|
2023-05-10 23:08:56 +00:00
|
|
|
Spacer(padding)
|
2023-06-07 16:26:32 +00:00
|
|
|
NoteUsernameDisplay(baseNote, remember { Modifier.weight(1f) })
|
2023-05-06 15:49:53 +00:00
|
|
|
} else {
|
2023-06-07 16:26:32 +00:00
|
|
|
NoteUsernameDisplay(baseNote, remember { Modifier.weight(1f) })
|
2023-05-06 15:49:53 +00:00
|
|
|
}
|
|
|
|
|
2023-05-06 23:19:57 +00:00
|
|
|
if (eventNote is RepostEvent) {
|
2023-05-06 15:49:53 +00:00
|
|
|
Text(
|
|
|
|
" ${stringResource(id = R.string.boosted)}",
|
|
|
|
fontWeight = FontWeight.Bold,
|
|
|
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
|
|
|
)
|
|
|
|
} else {
|
2023-06-01 19:16:03 +00:00
|
|
|
DisplayFollowingHashtagsInPost(eventNote, accountViewModel, nav)
|
2023-01-11 18:31:20 +00:00
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-05-06 23:19:57 +00:00
|
|
|
TimeAgo(time)
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
MoreOptionsButton(baseNote, accountViewModel)
|
|
|
|
}
|
|
|
|
}
|
2023-05-06 15:49:53 +00:00
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
@Composable
|
|
|
|
private fun MoreOptionsButton(
|
|
|
|
baseNote: Note,
|
|
|
|
accountViewModel: AccountViewModel
|
|
|
|
) {
|
|
|
|
var moreActionsExpanded by remember { mutableStateOf(false) }
|
|
|
|
|
|
|
|
IconButton(
|
|
|
|
modifier = Modifier.size(24.dp),
|
|
|
|
onClick = { moreActionsExpanded = true }
|
|
|
|
) {
|
|
|
|
Icon(
|
|
|
|
imageVector = Icons.Default.MoreVert,
|
|
|
|
null,
|
|
|
|
modifier = Modifier.size(15.dp),
|
|
|
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
|
|
|
)
|
|
|
|
|
|
|
|
NoteDropDownMenu(
|
|
|
|
baseNote,
|
|
|
|
moreActionsExpanded,
|
|
|
|
{ moreActionsExpanded = false },
|
|
|
|
accountViewModel
|
|
|
|
)
|
2023-01-11 18:31:20 +00:00
|
|
|
}
|
2023-01-13 15:20:54 +00:00
|
|
|
}
|
|
|
|
|
2023-05-06 23:19:57 +00:00
|
|
|
@Composable
|
|
|
|
fun TimeAgo(time: Long) {
|
|
|
|
val context = LocalContext.current
|
|
|
|
var timeStr by remember { mutableStateOf("") }
|
|
|
|
|
|
|
|
LaunchedEffect(key1 = time) {
|
2023-06-04 01:46:26 +00:00
|
|
|
launch(Dispatchers.IO) {
|
2023-05-23 00:04:23 +00:00
|
|
|
val newTimeStr = timeAgo(time, context = context)
|
|
|
|
if (newTimeStr != timeStr) {
|
|
|
|
timeStr = newTimeStr
|
|
|
|
}
|
2023-05-06 23:19:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Text(
|
|
|
|
timeStr,
|
|
|
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
|
|
maxLines = 1
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-05-05 01:57:19 +00:00
|
|
|
@Composable
|
2023-06-01 19:16:03 +00:00
|
|
|
private fun DrawAuthorImages(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
2023-06-01 23:56:31 +00:00
|
|
|
val baseChannelHex = remember { baseNote.channelHex() }
|
2023-05-10 23:08:56 +00:00
|
|
|
val modifier = remember { Modifier.width(55.dp) }
|
2023-05-05 01:57:19 +00:00
|
|
|
|
2023-05-10 23:08:56 +00:00
|
|
|
Column(modifier) {
|
2023-05-05 01:57:19 +00:00
|
|
|
// Draws the boosted picture outside the boosted card.
|
2023-05-10 23:08:56 +00:00
|
|
|
Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) {
|
2023-06-03 00:30:49 +00:00
|
|
|
NoteAuthorPicture(baseNote, nav, accountViewModel, 55.dp)
|
2023-05-05 01:57:19 +00:00
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
if (baseNote.event is RepostEvent) {
|
|
|
|
RepostNoteAuthorPicture(baseNote, accountViewModel, nav)
|
2023-05-05 01:57:19 +00:00
|
|
|
}
|
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
if (baseNote.event is ChannelMessageEvent && baseChannelHex != null) {
|
|
|
|
LoadChannel(baseChannelHex) { channel ->
|
|
|
|
ChannelNotePicture(channel)
|
2023-06-01 23:56:31 +00:00
|
|
|
}
|
2023-05-05 01:57:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (baseNote.event is RepostEvent) {
|
2023-05-10 23:08:56 +00:00
|
|
|
val baseReply = remember {
|
|
|
|
baseNote.replyTo?.lastOrNull()
|
|
|
|
}
|
|
|
|
baseReply?.let {
|
2023-05-05 01:57:19 +00:00
|
|
|
RelayBadges(it)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
RelayBadges(baseNote)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-01 23:56:31 +00:00
|
|
|
@Composable
|
|
|
|
fun LoadChannel(baseChannelHex: String, content: @Composable (Channel) -> Unit) {
|
|
|
|
var channel by remember(baseChannelHex) {
|
|
|
|
mutableStateOf<Channel?>(null)
|
|
|
|
}
|
|
|
|
|
|
|
|
LaunchedEffect(key1 = baseChannelHex) {
|
|
|
|
if (channel == null) {
|
|
|
|
launch(Dispatchers.IO) {
|
|
|
|
channel = LocalCache.checkGetOrCreateChannel(baseChannelHex)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
channel?.let {
|
|
|
|
content(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-05 01:57:19 +00:00
|
|
|
@Composable
|
|
|
|
private fun ChannelNotePicture(baseChannel: Channel) {
|
|
|
|
val channelState by baseChannel.live.observeAsState()
|
2023-06-01 23:56:31 +00:00
|
|
|
val channel = remember(channelState) { channelState?.channel } ?: return
|
2023-05-05 01:57:19 +00:00
|
|
|
|
2023-05-10 23:08:56 +00:00
|
|
|
val modifier = remember {
|
|
|
|
Modifier
|
|
|
|
.width(30.dp)
|
|
|
|
.height(30.dp)
|
|
|
|
.clip(shape = CircleShape)
|
|
|
|
}
|
|
|
|
|
|
|
|
val boxModifier = remember {
|
|
|
|
Modifier
|
|
|
|
.width(30.dp)
|
|
|
|
.height(30.dp)
|
|
|
|
}
|
|
|
|
|
2023-06-01 23:56:31 +00:00
|
|
|
val model = remember(channelState) {
|
|
|
|
ResizeImage(channel.profilePicture(), 30.dp)
|
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-01 23:56:31 +00:00
|
|
|
Box(boxModifier) {
|
|
|
|
RobohashAsyncImageProxy(
|
|
|
|
robot = channel.idHex,
|
|
|
|
model = model,
|
|
|
|
contentDescription = stringResource(R.string.group_picture),
|
|
|
|
modifier = modifier
|
|
|
|
.background(MaterialTheme.colors.background)
|
|
|
|
.border(
|
|
|
|
2.dp,
|
|
|
|
MaterialTheme.colors.background,
|
|
|
|
CircleShape
|
|
|
|
)
|
|
|
|
)
|
2023-05-05 01:57:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun RepostNoteAuthorPicture(
|
|
|
|
baseNote: Note,
|
2023-06-01 19:16:03 +00:00
|
|
|
accountViewModel: AccountViewModel,
|
|
|
|
nav: (String) -> Unit
|
2023-05-05 01:57:19 +00:00
|
|
|
) {
|
2023-05-10 23:08:56 +00:00
|
|
|
val baseRepost = remember { baseNote.replyTo?.lastOrNull() }
|
|
|
|
|
|
|
|
val modifier = remember {
|
2023-06-03 00:30:49 +00:00
|
|
|
Modifier.size(30.dp)
|
2023-05-10 23:08:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
baseRepost?.let {
|
|
|
|
Box(modifier) {
|
2023-05-05 01:57:19 +00:00
|
|
|
NoteAuthorPicture(
|
2023-06-01 19:16:03 +00:00
|
|
|
baseNote = it,
|
|
|
|
nav = nav,
|
|
|
|
accountViewModel = accountViewModel,
|
2023-06-07 16:26:32 +00:00
|
|
|
size = remember { 30.dp },
|
2023-05-05 01:57:19 +00:00
|
|
|
pictureModifier = Modifier.border(
|
|
|
|
2.dp,
|
|
|
|
MaterialTheme.colors.background,
|
|
|
|
CircleShape
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
@OptIn(ExperimentalLayoutApi::class)
|
2023-04-28 22:40:12 +00:00
|
|
|
@Composable
|
|
|
|
fun DisplayHighlight(
|
|
|
|
highlight: String,
|
|
|
|
authorHex: String?,
|
|
|
|
url: String?,
|
|
|
|
makeItShort: Boolean,
|
|
|
|
canPreview: Boolean,
|
|
|
|
backgroundColor: Color,
|
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-04-28 22:40:12 +00:00
|
|
|
) {
|
2023-05-07 20:12:28 +00:00
|
|
|
val quote =
|
|
|
|
remember {
|
|
|
|
highlight
|
|
|
|
.split("\n")
|
|
|
|
.map { "> *${it.removeSuffix(" ")}*" }
|
|
|
|
.joinToString("\n")
|
|
|
|
}
|
2023-04-28 22:40:12 +00:00
|
|
|
|
2023-05-06 15:49:53 +00:00
|
|
|
TranslatableRichTextViewer(
|
|
|
|
quote,
|
|
|
|
canPreview = canPreview && !makeItShort,
|
2023-06-06 19:51:43 +00:00
|
|
|
remember { Modifier.fillMaxWidth() },
|
|
|
|
remember { ImmutableListOfLists<String>(emptyList()) },
|
2023-05-06 15:49:53 +00:00
|
|
|
backgroundColor,
|
|
|
|
accountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav
|
2023-05-06 15:49:53 +00:00
|
|
|
)
|
2023-04-28 22:40:12 +00:00
|
|
|
|
2023-05-07 16:51:12 +00:00
|
|
|
var userBase by remember { mutableStateOf<User?>(null) }
|
|
|
|
|
|
|
|
LaunchedEffect(Unit) {
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
if (authorHex != null) {
|
|
|
|
userBase = LocalCache.checkGetOrCreateUser(authorHex)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-28 22:40:12 +00:00
|
|
|
FlowRow() {
|
|
|
|
authorHex?.let { authorHex ->
|
2023-05-07 16:51:12 +00:00
|
|
|
userBase?.let { userBase ->
|
2023-04-28 22:40:12 +00:00
|
|
|
val userState by userBase.live().metadata.observeAsState()
|
2023-05-10 23:08:56 +00:00
|
|
|
val route = remember { "User/${userBase.pubkeyHex}" }
|
|
|
|
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
|
2023-06-06 19:51:43 +00:00
|
|
|
val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() }
|
2023-04-28 22:40:12 +00:00
|
|
|
|
2023-05-10 23:08:56 +00:00
|
|
|
if (userDisplayName != null) {
|
2023-05-16 01:26:59 +00:00
|
|
|
CreateClickableTextWithEmoji(
|
|
|
|
clickablePart = userDisplayName,
|
2023-05-21 00:35:13 +00:00
|
|
|
suffix = " ",
|
2023-05-16 01:26:59 +00:00
|
|
|
tags = userTags,
|
|
|
|
route = route,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav = nav
|
2023-04-28 22:40:12 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
url?.let { url ->
|
2023-05-10 23:08:56 +00:00
|
|
|
val validatedUrl = remember {
|
|
|
|
try {
|
|
|
|
URL(url)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
Log.w("Note Compose", "Invalid URI: $url")
|
|
|
|
null
|
|
|
|
}
|
2023-04-28 22:40:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
validatedUrl?.host?.let { host ->
|
|
|
|
Text("on ")
|
|
|
|
ClickableUrl(urlText = host, url = url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-23 14:49:01 +00:00
|
|
|
@Composable
|
|
|
|
fun DisplayFollowingHashtagsInPost(
|
|
|
|
noteEvent: EventInterface,
|
2023-06-01 19:16:03 +00:00
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-03-23 14:49:01 +00:00
|
|
|
) {
|
2023-06-01 19:16:03 +00:00
|
|
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
2023-04-07 21:38:02 +00:00
|
|
|
var firstTag by remember { mutableStateOf<String?>(null) }
|
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
LaunchedEffect(key1 = accountState) {
|
|
|
|
launch(Dispatchers.Default) {
|
|
|
|
val followingTags = accountState?.account?.followingTagSet() ?: emptySet()
|
|
|
|
val newFirstTag = noteEvent.firstIsTaggedHashes(followingTags)
|
|
|
|
|
|
|
|
if (firstTag != newFirstTag) {
|
|
|
|
firstTag = newFirstTag
|
|
|
|
}
|
2023-04-18 12:45:02 +00:00
|
|
|
}
|
2023-04-07 21:38:02 +00:00
|
|
|
}
|
|
|
|
|
2023-03-23 14:49:01 +00:00
|
|
|
Column() {
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
2023-04-07 21:38:02 +00:00
|
|
|
firstTag?.let {
|
2023-06-01 19:16:03 +00:00
|
|
|
val displayTag = remember(firstTag) { AnnotatedString(" #$firstTag") }
|
|
|
|
val route = remember(firstTag) { "Hashtag/$firstTag" }
|
|
|
|
|
2023-03-23 14:49:01 +00:00
|
|
|
ClickableText(
|
2023-06-01 19:16:03 +00:00
|
|
|
text = displayTag,
|
|
|
|
onClick = { nav(route) },
|
2023-03-23 14:49:01 +00:00
|
|
|
style = LocalTextStyle.current.copy(
|
|
|
|
color = MaterialTheme.colors.primary.copy(
|
|
|
|
alpha = 0.52f
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
@OptIn(ExperimentalLayoutApi::class)
|
2023-03-23 14:49:01 +00:00
|
|
|
@Composable
|
|
|
|
fun DisplayUncitedHashtags(
|
2023-06-06 19:51:43 +00:00
|
|
|
hashtags: ImmutableList<String>,
|
2023-03-23 14:49:01 +00:00
|
|
|
eventContent: String,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-03-23 14:49:01 +00:00
|
|
|
) {
|
|
|
|
if (hashtags.isNotEmpty()) {
|
2023-03-23 15:07:29 +00:00
|
|
|
FlowRow(
|
|
|
|
modifier = Modifier.padding(top = 5.dp)
|
|
|
|
) {
|
|
|
|
hashtags.forEach { hashtag ->
|
|
|
|
if (!eventContent.contains(hashtag, true)) {
|
2023-03-23 14:49:01 +00:00
|
|
|
ClickableText(
|
2023-03-23 15:07:02 +00:00
|
|
|
text = AnnotatedString("#$hashtag "),
|
2023-05-24 16:34:09 +00:00
|
|
|
onClick = { nav("Hashtag/$hashtag") },
|
2023-03-23 14:49:01 +00:00
|
|
|
style = LocalTextStyle.current.copy(
|
|
|
|
color = MaterialTheme.colors.primary.copy(
|
|
|
|
alpha = 0.52f
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-28 12:46:07 +00:00
|
|
|
@Composable
|
|
|
|
fun DisplayPoW(
|
|
|
|
pow: Int
|
|
|
|
) {
|
|
|
|
Text(
|
|
|
|
"PoW-$pow",
|
|
|
|
color = MaterialTheme.colors.primary.copy(
|
|
|
|
alpha = 0.52f
|
|
|
|
),
|
|
|
|
fontSize = 14.sp,
|
|
|
|
fontWeight = FontWeight.Bold
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-06-06 19:51:43 +00:00
|
|
|
@Stable
|
|
|
|
data class Reward(val amount: BigDecimal)
|
|
|
|
|
2023-03-23 14:49:01 +00:00
|
|
|
@Composable
|
|
|
|
fun DisplayReward(
|
2023-06-06 19:51:43 +00:00
|
|
|
baseReward: Reward,
|
2023-03-23 14:49:01 +00:00
|
|
|
baseNote: Note,
|
2023-06-01 19:16:03 +00:00
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit
|
2023-03-23 14:49:01 +00:00
|
|
|
) {
|
2023-03-23 16:24:46 +00:00
|
|
|
var popupExpanded by remember { mutableStateOf(false) }
|
|
|
|
|
2023-03-23 14:49:01 +00:00
|
|
|
Column() {
|
2023-03-23 16:24:46 +00:00
|
|
|
Row(
|
|
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
|
|
modifier = Modifier.clickable { popupExpanded = true }
|
|
|
|
) {
|
2023-03-23 14:49:01 +00:00
|
|
|
ClickableText(
|
|
|
|
text = AnnotatedString("#bounty"),
|
2023-05-24 16:34:09 +00:00
|
|
|
onClick = { nav("Hashtag/bounty") },
|
2023-03-23 14:49:01 +00:00
|
|
|
style = LocalTextStyle.current.copy(
|
|
|
|
color = MaterialTheme.colors.primary.copy(
|
|
|
|
alpha = 0.52f
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2023-06-01 19:16:03 +00:00
|
|
|
RenderPledgeAmount(baseNote, baseReward, accountViewModel)
|
|
|
|
}
|
2023-03-23 16:24:46 +00:00
|
|
|
|
2023-06-01 19:16:03 +00:00
|
|
|
if (popupExpanded) {
|
|
|
|
AddBountyAmountDialog(baseNote, accountViewModel) {
|
|
|
|
popupExpanded = false
|
2023-03-23 14:49:01 +00:00
|
|
|
}
|
2023-06-01 19:16:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-23 14:49:01 +00:00
|
|
|
|
2023-06-01 19:16:03 +00:00
|
|
|
@Composable
|
|
|
|
private fun RenderPledgeAmount(
|
|
|
|
baseNote: Note,
|
2023-06-06 19:51:43 +00:00
|
|
|
baseReward: Reward,
|
2023-06-01 19:16:03 +00:00
|
|
|
accountViewModel: AccountViewModel
|
|
|
|
) {
|
|
|
|
val repliesState by baseNote.live().replies.observeAsState()
|
2023-06-06 19:51:43 +00:00
|
|
|
var reward by remember {
|
|
|
|
mutableStateOf<String>(
|
|
|
|
showAmount(baseReward.amount)
|
2023-06-01 19:16:03 +00:00
|
|
|
)
|
|
|
|
}
|
2023-03-23 14:49:01 +00:00
|
|
|
|
2023-06-01 19:16:03 +00:00
|
|
|
var hasPledge by remember {
|
|
|
|
mutableStateOf<Boolean>(
|
|
|
|
false
|
|
|
|
)
|
|
|
|
}
|
2023-03-23 16:24:46 +00:00
|
|
|
|
2023-06-01 19:16:03 +00:00
|
|
|
LaunchedEffect(key1 = repliesState) {
|
2023-06-03 01:27:57 +00:00
|
|
|
launch(Dispatchers.IO) {
|
2023-06-01 19:16:03 +00:00
|
|
|
repliesState?.note?.pledgedAmountByOthers()?.let {
|
2023-06-06 19:51:43 +00:00
|
|
|
val newRewardAmount = showAmount(baseReward.amount.add(it))
|
|
|
|
if (newRewardAmount != reward) {
|
|
|
|
reward = newRewardAmount
|
2023-06-03 01:27:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
val newHasPledge = repliesState?.note?.hasPledgeBy(accountViewModel.userProfile()) == true
|
|
|
|
if (hasPledge != newHasPledge) {
|
|
|
|
hasPledge = newHasPledge
|
2023-03-23 16:24:46 +00:00
|
|
|
}
|
|
|
|
}
|
2023-03-23 14:49:01 +00:00
|
|
|
}
|
2023-06-01 19:16:03 +00:00
|
|
|
|
|
|
|
if (hasPledge) {
|
|
|
|
Icon(
|
|
|
|
imageVector = Icons.Default.Bolt,
|
|
|
|
contentDescription = stringResource(R.string.zaps),
|
|
|
|
modifier = Modifier.size(20.dp),
|
|
|
|
tint = BitcoinOrange
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
Icon(
|
|
|
|
imageVector = Icons.Default.Bolt,
|
|
|
|
contentDescription = stringResource(R.string.zaps),
|
|
|
|
modifier = Modifier.size(20.dp),
|
|
|
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
Text(
|
2023-06-06 19:51:43 +00:00
|
|
|
text = reward,
|
2023-06-01 19:16:03 +00:00
|
|
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
|
|
|
)
|
2023-03-23 14:49:01 +00:00
|
|
|
}
|
|
|
|
|
2023-03-05 23:34:11 +00:00
|
|
|
@Composable
|
|
|
|
fun BadgeDisplay(baseNote: Note) {
|
2023-03-16 20:32:42 +00:00
|
|
|
val background = MaterialTheme.colors.background
|
2023-03-05 23:34:11 +00:00
|
|
|
val badgeData = baseNote.event as? BadgeDefinitionEvent ?: return
|
2023-05-06 23:19:57 +00:00
|
|
|
|
2023-06-01 00:08:30 +00:00
|
|
|
val image = remember { badgeData.thumb()?.ifBlank { null } ?: badgeData.image() }
|
2023-05-07 20:12:28 +00:00
|
|
|
val name = remember { badgeData.name() }
|
|
|
|
val description = remember { badgeData.description() }
|
2023-05-06 23:19:57 +00:00
|
|
|
|
|
|
|
var backgroundFromImage by remember { mutableStateOf(Pair(background, background)) }
|
2023-06-01 00:08:30 +00:00
|
|
|
var imageResult by remember { mutableStateOf<SuccessResult?>(null) }
|
2023-05-06 23:19:57 +00:00
|
|
|
|
|
|
|
LaunchedEffect(key1 = imageResult) {
|
2023-06-01 00:08:30 +00:00
|
|
|
launch(Dispatchers.IO) {
|
2023-05-06 23:19:57 +00:00
|
|
|
imageResult?.let {
|
2023-06-01 00:08:30 +00:00
|
|
|
val backgroundColor = it.drawable.toBitmap(200, 200).copy(Bitmap.Config.ARGB_8888, false).get(0, 199)
|
2023-05-06 23:19:57 +00:00
|
|
|
val colorFromImage = Color(backgroundColor)
|
2023-06-01 00:08:30 +00:00
|
|
|
val textBackground = if (colorFromImage.luminance() > 0.5) {
|
|
|
|
lightColors().onBackground
|
|
|
|
} else {
|
|
|
|
darkColors().onBackground
|
|
|
|
}
|
|
|
|
|
2023-05-06 23:19:57 +00:00
|
|
|
backgroundFromImage = Pair(colorFromImage, textBackground)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-05 23:34:11 +00:00
|
|
|
|
|
|
|
Row(
|
|
|
|
modifier = Modifier
|
|
|
|
.padding(10.dp)
|
2023-03-16 20:32:42 +00:00
|
|
|
.clip(shape = CutCornerShape(20, 20, 20, 20))
|
2023-03-05 23:34:11 +00:00
|
|
|
.border(
|
|
|
|
5.dp,
|
|
|
|
MaterialTheme.colors.primary.copy(alpha = 0.32f),
|
|
|
|
CutCornerShape(20)
|
|
|
|
)
|
2023-05-06 23:19:57 +00:00
|
|
|
.background(backgroundFromImage.first)
|
2023-03-05 23:34:11 +00:00
|
|
|
) {
|
2023-06-01 00:08:30 +00:00
|
|
|
RenderBadge(
|
|
|
|
image,
|
|
|
|
name,
|
|
|
|
backgroundFromImage.second,
|
|
|
|
description
|
|
|
|
) {
|
|
|
|
if (imageResult == null) {
|
|
|
|
imageResult = it.result
|
2023-03-05 23:34:11 +00:00
|
|
|
}
|
2023-06-01 00:08:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-05 23:34:11 +00:00
|
|
|
|
2023-06-01 00:08:30 +00:00
|
|
|
@Composable
|
|
|
|
private fun RenderBadge(
|
|
|
|
image: String?,
|
|
|
|
name: String?,
|
|
|
|
backgroundFromImage: Color,
|
|
|
|
description: String?,
|
|
|
|
onSuccess: (AsyncImagePainter.State.Success) -> Unit
|
|
|
|
) {
|
|
|
|
Column {
|
|
|
|
image.let {
|
|
|
|
AsyncImage(
|
|
|
|
model = it,
|
|
|
|
contentDescription = stringResource(
|
|
|
|
R.string.badge_award_image_for,
|
|
|
|
name ?: ""
|
|
|
|
),
|
|
|
|
contentScale = ContentScale.FillWidth,
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
onSuccess = onSuccess
|
|
|
|
)
|
|
|
|
}
|
2023-03-05 23:34:11 +00:00
|
|
|
|
2023-06-01 00:08:30 +00:00
|
|
|
name?.let {
|
|
|
|
Text(
|
|
|
|
text = it,
|
|
|
|
style = MaterialTheme.typography.body1,
|
|
|
|
textAlign = TextAlign.Center,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.padding(start = 10.dp, end = 10.dp),
|
|
|
|
color = backgroundFromImage
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
description?.let {
|
|
|
|
Text(
|
|
|
|
text = it,
|
|
|
|
style = MaterialTheme.typography.caption,
|
|
|
|
textAlign = TextAlign.Center,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
|
|
|
|
color = Color.Gray,
|
|
|
|
maxLines = 3,
|
|
|
|
overflow = TextOverflow.Ellipsis
|
|
|
|
)
|
2023-03-05 23:34:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-21 21:01:42 +00:00
|
|
|
@Composable
|
|
|
|
fun FileHeaderDisplay(note: Note) {
|
|
|
|
val event = (note.event as? FileHeaderEvent) ?: return
|
|
|
|
val fullUrl = event.url() ?: return
|
2023-04-25 12:20:51 +00:00
|
|
|
|
|
|
|
var content by remember { mutableStateOf<ZoomableContent?>(null) }
|
|
|
|
|
|
|
|
LaunchedEffect(key1 = event.id) {
|
2023-06-01 01:32:46 +00:00
|
|
|
if (content == null) {
|
|
|
|
launch(Dispatchers.IO) {
|
|
|
|
val blurHash = event.blurhash()
|
|
|
|
val hash = event.hash()
|
|
|
|
val dimensions = event.dimensions()
|
|
|
|
val description = event.content
|
|
|
|
val removedParamsFromUrl = fullUrl.split("?")[0].lowercase()
|
|
|
|
val isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) }
|
|
|
|
val uri = "nostr:" + note.toNEvent()
|
|
|
|
content = if (isImage) {
|
|
|
|
ZoomableUrlImage(fullUrl, description, hash, blurHash, dimensions, uri)
|
|
|
|
} else {
|
|
|
|
ZoomableUrlVideo(fullUrl, description, hash, uri)
|
|
|
|
}
|
2023-04-25 12:20:51 +00:00
|
|
|
}
|
2023-04-21 21:01:42 +00:00
|
|
|
}
|
|
|
|
}
|
2023-04-25 12:20:51 +00:00
|
|
|
|
|
|
|
content?.let {
|
2023-06-06 19:51:43 +00:00
|
|
|
ZoomableContentView(content = it)
|
2023-05-02 19:44:59 +00:00
|
|
|
}
|
2023-04-21 21:01:42 +00:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:22:49 +00:00
|
|
|
@Composable
|
|
|
|
fun FileStorageHeaderDisplay(baseNote: Note) {
|
2023-05-01 20:42:58 +00:00
|
|
|
val appContext = LocalContext.current.applicationContext
|
2023-04-26 22:04:38 +00:00
|
|
|
val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return
|
|
|
|
|
2023-05-07 16:51:12 +00:00
|
|
|
var fileNote by remember { mutableStateOf<Note?>(null) }
|
2023-04-26 18:22:49 +00:00
|
|
|
|
2023-05-07 16:51:12 +00:00
|
|
|
LaunchedEffect(key1 = eventHeader.id) {
|
2023-06-01 01:32:46 +00:00
|
|
|
launch(Dispatchers.IO) {
|
2023-05-07 16:51:12 +00:00
|
|
|
fileNote = eventHeader.dataEventId()?.let { LocalCache.checkGetOrCreateNote(it) }
|
|
|
|
}
|
|
|
|
}
|
2023-04-26 18:22:49 +00:00
|
|
|
|
2023-05-16 13:47:10 +00:00
|
|
|
fileNote?.let { fileNote2 ->
|
|
|
|
val noteState by fileNote2.live().metadata.observeAsState()
|
|
|
|
val note = remember(noteState) { noteState?.note }
|
2023-04-26 18:22:49 +00:00
|
|
|
|
2023-05-16 13:47:10 +00:00
|
|
|
var content by remember { mutableStateOf<ZoomableContent?>(null) }
|
2023-04-26 18:22:49 +00:00
|
|
|
|
2023-05-16 13:47:10 +00:00
|
|
|
LaunchedEffect(key1 = eventHeader.id, key2 = noteState, key3 = note?.event) {
|
2023-06-01 01:32:46 +00:00
|
|
|
if (content == null) {
|
|
|
|
launch(Dispatchers.IO) {
|
|
|
|
val uri = "nostr:" + baseNote.toNEvent()
|
|
|
|
val localDir = note?.idHex?.let { File(File(appContext.externalCacheDir, "NIP95"), it) }
|
|
|
|
val blurHash = eventHeader.blurhash()
|
|
|
|
val dimensions = eventHeader.dimensions()
|
|
|
|
val description = eventHeader.content
|
|
|
|
val mimeType = eventHeader.mimeType()
|
|
|
|
|
|
|
|
content = if (mimeType?.startsWith("image") == true) {
|
|
|
|
ZoomableLocalImage(
|
|
|
|
localFile = localDir,
|
|
|
|
mimeType = mimeType,
|
|
|
|
description = description,
|
|
|
|
blurhash = blurHash,
|
|
|
|
dim = dimensions,
|
|
|
|
isVerified = true,
|
|
|
|
uri = uri
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
ZoomableLocalVideo(
|
|
|
|
localFile = localDir,
|
|
|
|
mimeType = mimeType,
|
|
|
|
description = description,
|
|
|
|
dim = dimensions,
|
|
|
|
isVerified = true,
|
|
|
|
uri = uri
|
|
|
|
)
|
|
|
|
}
|
2023-05-16 13:47:10 +00:00
|
|
|
}
|
2023-04-26 18:22:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-16 13:47:10 +00:00
|
|
|
content?.let {
|
2023-06-06 19:51:43 +00:00
|
|
|
ZoomableContentView(content = it)
|
2023-05-16 13:47:10 +00:00
|
|
|
}
|
2023-05-02 19:44:59 +00:00
|
|
|
}
|
2023-04-26 18:22:49 +00:00
|
|
|
}
|
|
|
|
|
2023-05-15 15:15:32 +00:00
|
|
|
@Composable
|
2023-06-01 19:16:03 +00:00
|
|
|
fun AudioTrackHeader(noteEvent: AudioTrackEvent, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
2023-05-15 15:15:32 +00:00
|
|
|
val media = remember { noteEvent.media() }
|
|
|
|
val cover = remember { noteEvent.cover() }
|
|
|
|
val subject = remember { noteEvent.subject() }
|
|
|
|
val content = remember { noteEvent.content() }
|
|
|
|
val participants = remember { noteEvent.participants() }
|
|
|
|
|
|
|
|
var participantUsers by remember { mutableStateOf<List<Pair<Participant, User>>>(emptyList()) }
|
|
|
|
|
|
|
|
LaunchedEffect(key1 = participants) {
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
participantUsers = participants.mapNotNull { part -> LocalCache.checkGetOrCreateUser(part.key)?.let { Pair(part, it) } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Row(modifier = Modifier.padding(top = 5.dp)) {
|
|
|
|
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
|
|
|
Row() {
|
|
|
|
subject?.let {
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)) {
|
|
|
|
Text(
|
|
|
|
text = it,
|
|
|
|
fontWeight = FontWeight.Bold,
|
|
|
|
maxLines = 3,
|
|
|
|
overflow = TextOverflow.Ellipsis,
|
|
|
|
modifier = Modifier.fillMaxWidth()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
participantUsers.forEach {
|
|
|
|
Row(
|
|
|
|
verticalAlignment = Alignment.CenterVertically,
|
2023-05-16 02:18:12 +00:00
|
|
|
modifier = Modifier
|
|
|
|
.padding(top = 5.dp, start = 10.dp, end = 10.dp)
|
|
|
|
.clickable {
|
2023-05-24 16:34:09 +00:00
|
|
|
nav("User/${it.second.pubkeyHex}")
|
2023-05-16 02:18:12 +00:00
|
|
|
}
|
2023-05-15 15:15:32 +00:00
|
|
|
) {
|
2023-06-01 19:16:03 +00:00
|
|
|
UserPicture(it.second, 25.dp, accountViewModel)
|
2023-05-15 15:15:32 +00:00
|
|
|
Spacer(Modifier.width(5.dp))
|
|
|
|
UsernameDisplay(it.second, Modifier.weight(1f))
|
|
|
|
Spacer(Modifier.width(5.dp))
|
|
|
|
it.first.role?.let {
|
|
|
|
Text(
|
|
|
|
text = it.capitalize(Locale.ROOT),
|
|
|
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
|
|
maxLines = 1
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
media?.let { media ->
|
|
|
|
Row(
|
|
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
|
|
modifier = Modifier.padding(10.dp)
|
|
|
|
) {
|
|
|
|
cover?.let { cover ->
|
2023-05-28 21:35:57 +00:00
|
|
|
LoadThumbAndThenVideoView(
|
2023-05-15 15:15:32 +00:00
|
|
|
videoUri = media,
|
|
|
|
description = noteEvent.subject(),
|
|
|
|
thumbUri = cover
|
|
|
|
)
|
|
|
|
}
|
|
|
|
?: VideoView(
|
|
|
|
videoUri = media,
|
|
|
|
noteEvent.subject()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-03 16:35:29 +00:00
|
|
|
@Composable
|
2023-06-01 19:16:03 +00:00
|
|
|
private fun LongFormHeader(noteEvent: LongTextNoteEvent, note: Note, accountViewModel: AccountViewModel) {
|
|
|
|
val image = remember(noteEvent) { noteEvent.image() }
|
|
|
|
val title = remember(noteEvent) { noteEvent.title() }
|
|
|
|
val summary = remember(noteEvent) { noteEvent.summary() ?: noteEvent.content.take(200).ifBlank { null } }
|
|
|
|
|
2023-03-03 16:35:29 +00:00
|
|
|
Row(
|
|
|
|
modifier = Modifier
|
2023-06-09 17:45:46 +00:00
|
|
|
.clip(shape = QuoteBorder)
|
2023-03-03 16:35:29 +00:00
|
|
|
.border(
|
|
|
|
1.dp,
|
|
|
|
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
2023-06-09 17:45:46 +00:00
|
|
|
QuoteBorder
|
2023-03-03 16:35:29 +00:00
|
|
|
)
|
|
|
|
) {
|
|
|
|
Column {
|
2023-06-01 19:16:03 +00:00
|
|
|
image?.let {
|
2023-03-03 16:35:29 +00:00
|
|
|
AsyncImage(
|
|
|
|
model = it,
|
|
|
|
contentDescription = stringResource(
|
|
|
|
R.string.preview_card_image_for,
|
|
|
|
it
|
|
|
|
),
|
|
|
|
contentScale = ContentScale.FillWidth,
|
|
|
|
modifier = Modifier.fillMaxWidth()
|
|
|
|
)
|
2023-06-01 19:16:03 +00:00
|
|
|
} ?: CreateImageHeader(note, accountViewModel)
|
2023-03-03 16:35:29 +00:00
|
|
|
|
2023-06-01 19:16:03 +00:00
|
|
|
title?.let {
|
2023-03-03 16:35:29 +00:00
|
|
|
Text(
|
|
|
|
text = it,
|
|
|
|
style = MaterialTheme.typography.body1,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.padding(start = 10.dp, end = 10.dp, top = 10.dp)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-06-01 19:16:03 +00:00
|
|
|
summary?.let {
|
2023-03-03 16:35:29 +00:00
|
|
|
Text(
|
|
|
|
text = it,
|
|
|
|
style = MaterialTheme.typography.caption,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
|
|
|
|
color = Color.Gray,
|
|
|
|
maxLines = 3,
|
|
|
|
overflow = TextOverflow.Ellipsis
|
|
|
|
)
|
|
|
|
}
|
2023-06-01 19:16:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun CreateImageHeader(
|
|
|
|
note: Note,
|
|
|
|
accountViewModel: AccountViewModel
|
|
|
|
) {
|
|
|
|
val banner = remember(note.author?.info) { note.author?.info?.banner }
|
|
|
|
|
|
|
|
Box() {
|
|
|
|
banner?.let {
|
|
|
|
AsyncImage(
|
|
|
|
model = it,
|
|
|
|
contentDescription = stringResource(
|
|
|
|
R.string.preview_card_image_for,
|
|
|
|
it
|
|
|
|
),
|
|
|
|
contentScale = ContentScale.FillWidth,
|
|
|
|
modifier = Modifier.fillMaxWidth()
|
|
|
|
)
|
|
|
|
} ?: Image(
|
|
|
|
painter = painterResource(R.drawable.profile_banner),
|
|
|
|
contentDescription = stringResource(R.string.profile_banner),
|
|
|
|
contentScale = ContentScale.FillWidth,
|
|
|
|
modifier = Modifier
|
|
|
|
.fillMaxWidth()
|
|
|
|
.height(150.dp)
|
|
|
|
)
|
|
|
|
|
|
|
|
Box(
|
|
|
|
Modifier
|
|
|
|
.width(75.dp)
|
|
|
|
.height(75.dp)
|
|
|
|
.padding(10.dp)
|
|
|
|
.align(Alignment.BottomStart)
|
|
|
|
) {
|
|
|
|
NoteAuthorPicture(baseNote = note, accountViewModel = accountViewModel, size = 55.dp)
|
2023-03-03 16:35:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-06 22:16:27 +00:00
|
|
|
@Composable
|
|
|
|
private fun RelayBadges(baseNote: Note) {
|
|
|
|
var expanded by remember { mutableStateOf(false) }
|
2023-04-20 21:19:34 +00:00
|
|
|
var showShowMore by remember { mutableStateOf(false) }
|
2023-06-07 22:50:29 +00:00
|
|
|
|
2023-06-06 19:51:43 +00:00
|
|
|
var lazyRelayList by remember { mutableStateOf<ImmutableList<String>>(persistentListOf()) }
|
2023-06-07 22:50:29 +00:00
|
|
|
var shortRelayList by remember { mutableStateOf<ImmutableList<String>>(persistentListOf()) }
|
2023-02-06 22:16:27 +00:00
|
|
|
|
2023-06-04 01:46:26 +00:00
|
|
|
WatchRelayLists(baseNote) { relayList ->
|
2023-06-07 22:50:29 +00:00
|
|
|
if (!equalImmutableLists(relayList, lazyRelayList)) {
|
|
|
|
lazyRelayList = relayList.toImmutableList()
|
|
|
|
shortRelayList = relayList.take(3).toImmutableList()
|
2023-06-04 01:46:26 +00:00
|
|
|
}
|
2023-04-20 21:19:34 +00:00
|
|
|
|
2023-06-07 22:50:29 +00:00
|
|
|
val nextShowMore = relayList.size > 3
|
2023-06-04 01:46:26 +00:00
|
|
|
if (nextShowMore != showShowMore) {
|
|
|
|
// only triggers recomposition when actually different
|
|
|
|
showShowMore = nextShowMore
|
2023-04-20 21:19:34 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-06 22:16:27 +00:00
|
|
|
|
2023-03-28 14:55:30 +00:00
|
|
|
Spacer(Modifier.height(10.dp))
|
|
|
|
|
2023-06-07 22:50:29 +00:00
|
|
|
if (expanded) {
|
|
|
|
VerticalRelayPanelWithFlow(lazyRelayList)
|
|
|
|
} else {
|
|
|
|
VerticalRelayPanelWithFlow(shortRelayList)
|
|
|
|
}
|
2023-03-28 14:55:30 +00:00
|
|
|
|
2023-06-07 22:50:29 +00:00
|
|
|
if (showShowMore && !expanded) {
|
2023-04-20 21:19:34 +00:00
|
|
|
ShowMoreRelaysButton {
|
|
|
|
expanded = true
|
2023-02-06 22:16:27 +00:00
|
|
|
}
|
|
|
|
}
|
2023-04-20 21:19:34 +00:00
|
|
|
}
|
2023-02-06 22:16:27 +00:00
|
|
|
|
2023-06-04 01:46:26 +00:00
|
|
|
@Composable
|
|
|
|
private fun WatchRelayLists(baseNote: Note, onListChanges: (ImmutableList<String>) -> Unit) {
|
|
|
|
val noteRelaysState by baseNote.live().relays.observeAsState()
|
|
|
|
|
|
|
|
LaunchedEffect(key1 = noteRelaysState) {
|
|
|
|
launch(Dispatchers.IO) {
|
|
|
|
val relayList = noteRelaysState?.note?.relays?.map {
|
|
|
|
it.removePrefix("wss://").removePrefix("ws://")
|
|
|
|
} ?: emptyList()
|
|
|
|
|
|
|
|
onListChanges(relayList.toImmutableList())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
@OptIn(ExperimentalLayoutApi::class)
|
2023-04-20 21:19:34 +00:00
|
|
|
@Composable
|
|
|
|
@Stable
|
|
|
|
private fun VerticalRelayPanelWithFlow(
|
2023-06-06 19:51:43 +00:00
|
|
|
relays: ImmutableList<String>
|
2023-04-20 21:19:34 +00:00
|
|
|
) {
|
|
|
|
// FlowRow Seems to be a lot faster than LazyVerticalGrid
|
|
|
|
FlowRow() {
|
|
|
|
relays.forEach { url ->
|
2023-05-27 20:04:20 +00:00
|
|
|
RenderRelay(url)
|
2023-04-20 21:19:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun ShowMoreRelaysButton(onClick: () -> Unit) {
|
2023-05-10 23:08:56 +00:00
|
|
|
val boxModifier = remember {
|
2023-04-20 21:19:34 +00:00
|
|
|
Modifier
|
|
|
|
.fillMaxWidth()
|
2023-05-10 23:08:56 +00:00
|
|
|
.height(25.dp)
|
|
|
|
}
|
|
|
|
val iconButtonModifier = remember { Modifier.size(24.dp) }
|
|
|
|
val iconModifier = remember { Modifier.size(15.dp) }
|
|
|
|
|
|
|
|
Row(
|
|
|
|
boxModifier,
|
2023-04-20 21:19:34 +00:00
|
|
|
horizontalArrangement = Arrangement.Center,
|
|
|
|
verticalAlignment = Alignment.Top
|
|
|
|
) {
|
|
|
|
IconButton(
|
2023-05-10 23:08:56 +00:00
|
|
|
modifier = iconButtonModifier,
|
2023-04-20 21:19:34 +00:00
|
|
|
onClick = onClick
|
2023-03-08 22:07:56 +00:00
|
|
|
) {
|
2023-04-20 21:19:34 +00:00
|
|
|
Icon(
|
|
|
|
imageVector = Icons.Default.ExpandMore,
|
|
|
|
null,
|
2023-05-10 23:08:56 +00:00
|
|
|
modifier = iconModifier,
|
2023-04-20 21:19:34 +00:00
|
|
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
|
|
|
)
|
2023-02-06 22:16:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-26 16:16:57 +00:00
|
|
|
@Composable
|
2023-02-01 01:12:24 +00:00
|
|
|
fun NoteAuthorPicture(
|
2023-05-06 15:49:53 +00:00
|
|
|
baseNote: Note,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit,
|
2023-06-01 19:16:03 +00:00
|
|
|
accountViewModel: AccountViewModel,
|
2023-01-26 16:16:57 +00:00
|
|
|
size: Dp,
|
|
|
|
pictureModifier: Modifier = Modifier
|
|
|
|
) {
|
2023-06-01 19:16:03 +00:00
|
|
|
NoteAuthorPicture(baseNote, size, accountViewModel, pictureModifier) {
|
2023-05-24 16:34:09 +00:00
|
|
|
nav("User/${it.pubkeyHex}")
|
2023-01-26 16:16:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
2023-02-01 01:12:24 +00:00
|
|
|
fun NoteAuthorPicture(
|
|
|
|
baseNote: Note,
|
2023-01-26 16:16:57 +00:00
|
|
|
size: Dp,
|
2023-06-01 19:16:03 +00:00
|
|
|
accountViewModel: AccountViewModel,
|
2023-03-13 17:47:44 +00:00
|
|
|
modifier: Modifier = Modifier,
|
2023-02-01 01:12:24 +00:00
|
|
|
onClick: ((User) -> Unit)? = null
|
2023-01-26 16:16:57 +00:00
|
|
|
) {
|
2023-02-19 16:22:01 +00:00
|
|
|
val noteState by baseNote.live().metadata.observeAsState()
|
2023-06-01 23:56:31 +00:00
|
|
|
val author by remember(noteState) {
|
|
|
|
derivedStateOf {
|
|
|
|
noteState?.note?.author
|
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
}
|
2023-01-28 01:05:22 +00:00
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
if (author == null) {
|
|
|
|
val nullModifier = remember {
|
2023-06-03 16:39:06 +00:00
|
|
|
modifier
|
|
|
|
.size(size)
|
|
|
|
.clip(shape = CircleShape)
|
2023-02-01 01:12:24 +00:00
|
|
|
}
|
2023-06-03 00:30:49 +00:00
|
|
|
|
|
|
|
RobohashAsyncImage(
|
|
|
|
robot = "authornotfound",
|
|
|
|
robotSize = size,
|
|
|
|
contentDescription = stringResource(R.string.unknown_author),
|
|
|
|
modifier = nullModifier.background(MaterialTheme.colors.background)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
UserPicture(author!!, size, accountViewModel, modifier, onClick)
|
2023-02-01 01:12:24 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-28 01:05:22 +00:00
|
|
|
|
2023-02-01 01:12:24 +00:00
|
|
|
@Composable
|
|
|
|
fun UserPicture(
|
|
|
|
user: User,
|
2023-05-24 16:34:09 +00:00
|
|
|
nav: (String) -> Unit,
|
2023-06-01 19:16:03 +00:00
|
|
|
accountViewModel: AccountViewModel,
|
2023-02-01 01:12:24 +00:00
|
|
|
size: Dp,
|
|
|
|
pictureModifier: Modifier = Modifier
|
|
|
|
) {
|
2023-06-01 19:16:03 +00:00
|
|
|
UserPicture(user, size, accountViewModel, pictureModifier) {
|
2023-05-24 16:34:09 +00:00
|
|
|
nav("User/${it.pubkeyHex}")
|
2023-02-01 01:12:24 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-26 16:16:57 +00:00
|
|
|
|
2023-05-06 23:19:57 +00:00
|
|
|
@OptIn(ExperimentalFoundationApi::class)
|
2023-02-01 01:12:24 +00:00
|
|
|
@Composable
|
|
|
|
fun UserPicture(
|
|
|
|
baseUser: User,
|
|
|
|
size: Dp,
|
2023-06-01 19:16:03 +00:00
|
|
|
accountViewModel: AccountViewModel,
|
2023-05-25 22:28:34 +00:00
|
|
|
modifier: Modifier = remember { Modifier },
|
2023-03-01 18:29:58 +00:00
|
|
|
onClick: ((User) -> Unit)? = null,
|
|
|
|
onLongClick: ((User) -> Unit)? = null
|
2023-02-01 01:12:24 +00:00
|
|
|
) {
|
2023-02-19 16:22:01 +00:00
|
|
|
val userState by baseUser.live().metadata.observeAsState()
|
2023-03-15 15:39:30 +00:00
|
|
|
|
2023-05-10 23:08:56 +00:00
|
|
|
val userPubkey = remember {
|
|
|
|
baseUser.pubkeyHex
|
|
|
|
}
|
2023-03-15 15:39:30 +00:00
|
|
|
|
2023-05-30 17:58:00 +00:00
|
|
|
val userProfile by remember(userState) {
|
|
|
|
derivedStateOf {
|
|
|
|
userState?.user?.profilePicture()
|
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// BaseUser is the same reference as accountState.user
|
|
|
|
val myModifier = remember {
|
|
|
|
if (onClick != null && onLongClick != null) {
|
|
|
|
Modifier.combinedClickable(
|
|
|
|
onClick = { onClick(baseUser) },
|
|
|
|
onLongClick = { onLongClick(baseUser) }
|
|
|
|
)
|
|
|
|
} else if (onClick != null) {
|
|
|
|
Modifier.clickable(onClick = { onClick(baseUser) })
|
|
|
|
} else {
|
|
|
|
Modifier
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Row(modifier = myModifier) {
|
2023-05-06 23:19:57 +00:00
|
|
|
UserPicture(
|
2023-05-10 23:08:56 +00:00
|
|
|
userHex = userPubkey,
|
|
|
|
userPicture = userProfile,
|
2023-05-06 23:19:57 +00:00
|
|
|
size = size,
|
2023-06-01 19:16:03 +00:00
|
|
|
modifier = modifier,
|
|
|
|
accountViewModel = accountViewModel
|
2023-05-06 23:19:57 +00:00
|
|
|
)
|
|
|
|
}
|
2023-03-15 15:39:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
fun UserPicture(
|
|
|
|
userHex: String,
|
|
|
|
userPicture: String?,
|
|
|
|
size: Dp,
|
2023-06-01 19:16:03 +00:00
|
|
|
modifier: Modifier = Modifier,
|
|
|
|
accountViewModel: AccountViewModel
|
2023-03-15 15:39:30 +00:00
|
|
|
) {
|
2023-05-10 23:08:56 +00:00
|
|
|
val myBoxModifier = remember {
|
2023-06-01 23:56:31 +00:00
|
|
|
Modifier.size(size)
|
2023-05-10 23:08:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
val myImageModifier = remember {
|
|
|
|
modifier
|
2023-06-01 23:56:31 +00:00
|
|
|
.size(size)
|
2023-05-10 23:08:56 +00:00
|
|
|
.clip(shape = CircleShape)
|
|
|
|
}
|
|
|
|
|
2023-06-07 16:26:32 +00:00
|
|
|
val myIconSize = remember(size) { size.div(3.5f) }
|
|
|
|
|
2023-06-01 19:16:03 +00:00
|
|
|
Box(myBoxModifier, contentAlignment = TopEnd) {
|
2023-03-11 05:52:17 +00:00
|
|
|
RobohashAsyncImageProxy(
|
2023-03-15 15:39:30 +00:00
|
|
|
robot = userHex,
|
2023-06-01 19:16:03 +00:00
|
|
|
model = remember(userPicture) {
|
|
|
|
ResizeImage(userPicture, size)
|
|
|
|
},
|
2023-02-28 18:20:49 +00:00
|
|
|
contentDescription = stringResource(id = R.string.profile_image),
|
2023-05-10 23:08:56 +00:00
|
|
|
modifier = myImageModifier.background(MaterialTheme.colors.background)
|
2023-02-01 01:12:24 +00:00
|
|
|
)
|
|
|
|
|
2023-06-07 16:26:32 +00:00
|
|
|
ObserveAndDisplayFollowingMark(userHex, myIconSize, accountViewModel)
|
2023-06-01 19:16:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun ObserveAndDisplayFollowingMark(userHex: String, iconSize: Dp, accountViewModel: AccountViewModel) {
|
|
|
|
val accountFollowsState by accountViewModel.account.userProfile().live().follows.observeAsState()
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-01 23:56:31 +00:00
|
|
|
var showFollowingMark by remember { mutableStateOf(false) }
|
|
|
|
|
|
|
|
LaunchedEffect(key1 = accountFollowsState) {
|
|
|
|
launch(Dispatchers.Default) {
|
|
|
|
val newShowFollowingMark =
|
|
|
|
accountFollowsState?.user?.isFollowingCached(userHex) == true ||
|
|
|
|
(userHex == accountViewModel.account.userProfile().pubkeyHex)
|
|
|
|
|
|
|
|
if (newShowFollowingMark != showFollowingMark) {
|
|
|
|
showFollowingMark = newShowFollowingMark
|
|
|
|
}
|
2023-06-01 00:08:07 +00:00
|
|
|
}
|
|
|
|
}
|
2023-06-01 19:16:03 +00:00
|
|
|
|
|
|
|
if (showFollowingMark) {
|
|
|
|
FollowingIcon(iconSize)
|
|
|
|
}
|
2023-06-01 00:08:07 +00:00
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-01 00:08:07 +00:00
|
|
|
@Composable
|
2023-06-01 19:16:03 +00:00
|
|
|
private fun FollowingIcon(iconSize: Dp) {
|
|
|
|
val myIconBoxModifier = remember {
|
|
|
|
Modifier.size(iconSize)
|
|
|
|
}
|
|
|
|
|
2023-06-01 00:08:07 +00:00
|
|
|
Box(myIconBoxModifier, contentAlignment = Alignment.Center) {
|
|
|
|
val myIconBackgroundModifier = remember {
|
|
|
|
Modifier
|
|
|
|
.clip(CircleShape)
|
|
|
|
.fillMaxSize(0.6f)
|
|
|
|
}
|
2023-05-10 23:08:56 +00:00
|
|
|
|
2023-06-01 00:08:07 +00:00
|
|
|
Box(
|
|
|
|
myIconBackgroundModifier.background(MaterialTheme.colors.background)
|
|
|
|
)
|
2023-02-01 01:12:24 +00:00
|
|
|
|
2023-06-01 00:08:07 +00:00
|
|
|
val myIconModifier = remember {
|
2023-06-01 19:16:03 +00:00
|
|
|
Modifier.size(iconSize)
|
2023-01-26 16:16:57 +00:00
|
|
|
}
|
2023-06-01 00:08:07 +00:00
|
|
|
|
|
|
|
Icon(
|
|
|
|
painter = painterResource(R.drawable.ic_verified),
|
|
|
|
stringResource(id = R.string.following),
|
|
|
|
modifier = myIconModifier,
|
|
|
|
tint = Following
|
|
|
|
)
|
2023-01-26 16:16:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
@Immutable
|
2023-04-07 22:02:54 +00:00
|
|
|
data class DropDownParams(
|
|
|
|
val isFollowingAuthor: Boolean,
|
|
|
|
val isPrivateBookmarkNote: Boolean,
|
|
|
|
val isPublicBookmarkNote: Boolean,
|
2023-05-26 16:20:17 +00:00
|
|
|
val isLoggedUser: Boolean,
|
|
|
|
val isSensitive: Boolean,
|
|
|
|
val showSensitiveContent: Boolean?
|
2023-04-07 22:02:54 +00:00
|
|
|
)
|
|
|
|
|
2023-01-13 15:20:54 +00:00
|
|
|
@Composable
|
|
|
|
fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) {
|
2023-03-14 17:41:39 +00:00
|
|
|
var reportDialogShowing by remember { mutableStateOf(false) }
|
2023-01-13 15:20:54 +00:00
|
|
|
|
2023-04-07 22:02:54 +00:00
|
|
|
var state by remember {
|
|
|
|
mutableStateOf<DropDownParams>(
|
2023-06-03 00:30:49 +00:00
|
|
|
DropDownParams(
|
|
|
|
isFollowingAuthor = false,
|
|
|
|
isPrivateBookmarkNote = false,
|
|
|
|
isPublicBookmarkNote = false,
|
|
|
|
isLoggedUser = false,
|
|
|
|
isSensitive = false,
|
|
|
|
showSensitiveContent = null
|
2023-04-18 12:45:34 +00:00
|
|
|
)
|
2023-06-03 00:30:49 +00:00
|
|
|
)
|
2023-04-07 22:02:54 +00:00
|
|
|
}
|
|
|
|
|
2023-01-13 15:20:54 +00:00
|
|
|
DropdownMenu(
|
|
|
|
expanded = popupExpanded,
|
|
|
|
onDismissRequest = onDismiss
|
|
|
|
) {
|
2023-06-09 17:19:14 +00:00
|
|
|
val clipboardManager = LocalClipboardManager.current
|
|
|
|
val appContext = LocalContext.current.applicationContext
|
|
|
|
val actContext = LocalContext.current
|
|
|
|
|
2023-06-03 00:30:49 +00:00
|
|
|
WatchBookmarksFollowsAndAccount(note, accountViewModel) { newState ->
|
|
|
|
if (state != newState) {
|
|
|
|
state = newState
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-05 19:33:16 +00:00
|
|
|
val scope = rememberCoroutineScope()
|
|
|
|
|
2023-04-07 22:02:54 +00:00
|
|
|
if (!state.isFollowingAuthor) {
|
2023-03-03 06:53:56 +00:00
|
|
|
DropdownMenuItem(onClick = {
|
|
|
|
accountViewModel.follow(
|
|
|
|
note.author ?: return@DropdownMenuItem
|
|
|
|
); onDismiss()
|
|
|
|
}) {
|
|
|
|
Text(stringResource(R.string.follow))
|
|
|
|
}
|
|
|
|
Divider()
|
|
|
|
}
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(
|
|
|
|
onClick = {
|
|
|
|
scope.launch(Dispatchers.IO) {
|
|
|
|
clipboardManager.setText(AnnotatedString(accountViewModel.decrypt(note) ?: ""))
|
|
|
|
onDismiss()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
) {
|
2023-03-09 14:34:25 +00:00
|
|
|
Text(stringResource(R.string.copy_text))
|
|
|
|
}
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(
|
|
|
|
onClick = {
|
|
|
|
scope.launch(Dispatchers.IO) {
|
|
|
|
clipboardManager.setText(AnnotatedString("nostr:${note.author?.pubkeyNpub()}"))
|
|
|
|
onDismiss()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
) {
|
2023-03-09 14:34:25 +00:00
|
|
|
Text(stringResource(R.string.copy_user_pubkey))
|
|
|
|
}
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(onClick = {
|
|
|
|
scope.launch(Dispatchers.IO) {
|
|
|
|
clipboardManager.setText(AnnotatedString("nostr:" + note.toNEvent()))
|
|
|
|
onDismiss()
|
|
|
|
}
|
|
|
|
}) {
|
2023-03-09 14:34:25 +00:00
|
|
|
Text(stringResource(R.string.copy_note_id))
|
|
|
|
}
|
|
|
|
DropdownMenuItem(onClick = {
|
|
|
|
val sendIntent = Intent().apply {
|
|
|
|
action = Intent.ACTION_SEND
|
|
|
|
type = "text/plain"
|
|
|
|
putExtra(
|
|
|
|
Intent.EXTRA_TEXT,
|
|
|
|
externalLinkForNote(note)
|
|
|
|
)
|
|
|
|
putExtra(Intent.EXTRA_TITLE, actContext.getString(R.string.quick_action_share_browser_link))
|
|
|
|
}
|
|
|
|
|
|
|
|
val shareIntent = Intent.createChooser(sendIntent, appContext.getString(R.string.quick_action_share))
|
|
|
|
ContextCompat.startActivity(actContext, shareIntent, null)
|
|
|
|
onDismiss()
|
|
|
|
}) {
|
|
|
|
Text(stringResource(R.string.quick_action_share))
|
|
|
|
}
|
|
|
|
Divider()
|
2023-04-07 22:02:54 +00:00
|
|
|
if (state.isPrivateBookmarkNote) {
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.removePrivateBookmark(note); onDismiss() } }) {
|
2023-03-20 22:16:07 +00:00
|
|
|
Text(stringResource(R.string.remove_from_private_bookmarks))
|
|
|
|
}
|
|
|
|
} else {
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.addPrivateBookmark(note); onDismiss() } }) {
|
2023-03-20 22:16:07 +00:00
|
|
|
Text(stringResource(R.string.add_to_private_bookmarks))
|
|
|
|
}
|
|
|
|
}
|
2023-04-07 22:02:54 +00:00
|
|
|
if (state.isPublicBookmarkNote) {
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.removePublicBookmark(note); onDismiss() } }) {
|
2023-03-20 22:16:07 +00:00
|
|
|
Text(stringResource(R.string.remove_from_public_bookmarks))
|
|
|
|
}
|
|
|
|
} else {
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.addPublicBookmark(note); onDismiss() } }) {
|
2023-03-20 22:16:07 +00:00
|
|
|
Text(stringResource(R.string.add_to_public_bookmarks))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Divider()
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.broadcast(note); onDismiss() } }) {
|
2023-02-28 18:20:49 +00:00
|
|
|
Text(stringResource(R.string.broadcast))
|
2023-01-13 15:20:54 +00:00
|
|
|
}
|
2023-03-15 17:12:21 +00:00
|
|
|
Divider()
|
2023-04-07 22:02:54 +00:00
|
|
|
if (state.isLoggedUser) {
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.delete(note); onDismiss() } }) {
|
2023-03-11 04:00:35 +00:00
|
|
|
Text(stringResource(R.string.request_deletion))
|
2023-02-26 17:02:07 +00:00
|
|
|
}
|
2023-03-15 17:12:21 +00:00
|
|
|
} else {
|
2023-03-14 17:41:39 +00:00
|
|
|
DropdownMenuItem(onClick = { reportDialogShowing = true }) {
|
|
|
|
Text("Block / Report")
|
2023-02-22 21:17:56 +00:00
|
|
|
}
|
2023-01-30 01:06:48 +00:00
|
|
|
}
|
2023-05-26 16:20:17 +00:00
|
|
|
Divider()
|
|
|
|
if (state.showSensitiveContent == null || state.showSensitiveContent == true) {
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.hideSensitiveContent(); onDismiss() } }) {
|
2023-05-26 16:20:17 +00:00
|
|
|
Text(stringResource(R.string.content_warning_hide_all_sensitive_content))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (state.showSensitiveContent == null || state.showSensitiveContent == false) {
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.disableContentWarnings(); onDismiss() } }) {
|
2023-05-26 16:20:17 +00:00
|
|
|
Text(stringResource(R.string.content_warning_show_all_sensitive_content))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (state.showSensitiveContent != null) {
|
2023-06-05 19:33:16 +00:00
|
|
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.seeContentWarnings(); onDismiss() } }) {
|
2023-05-26 16:20:17 +00:00
|
|
|
Text(stringResource(R.string.content_warning_see_warnings))
|
|
|
|
}
|
|
|
|
}
|
2023-01-13 15:20:54 +00:00
|
|
|
}
|
2023-03-14 17:41:39 +00:00
|
|
|
|
|
|
|
if (reportDialogShowing) {
|
|
|
|
ReportNoteDialog(note = note, accountViewModel = accountViewModel) {
|
|
|
|
reportDialogShowing = false
|
|
|
|
onDismiss()
|
|
|
|
}
|
|
|
|
}
|
2023-03-05 21:42:19 +00:00
|
|
|
}
|
2023-06-03 00:30:49 +00:00
|
|
|
|
|
|
|
@Composable
|
|
|
|
fun WatchBookmarksFollowsAndAccount(note: Note, accountViewModel: AccountViewModel, onNew: (DropDownParams) -> Unit) {
|
|
|
|
val followState by accountViewModel.userProfile().live().follows.observeAsState()
|
|
|
|
val bookmarkState by accountViewModel.userProfile().live().bookmarks.observeAsState()
|
|
|
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
|
|
|
|
|
|
LaunchedEffect(key1 = followState, key2 = bookmarkState, key3 = accountState) {
|
|
|
|
launch(Dispatchers.IO) {
|
|
|
|
onNew(
|
|
|
|
DropDownParams(
|
|
|
|
isFollowingAuthor = accountViewModel.isFollowing(note.author),
|
|
|
|
isPrivateBookmarkNote = accountViewModel.isInPrivateBookmarks(note),
|
|
|
|
isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note),
|
|
|
|
isLoggedUser = accountViewModel.isLoggedUser(note.author),
|
|
|
|
isSensitive = note.event?.isSensitive() ?: false,
|
|
|
|
showSensitiveContent = accountState?.account?.showSensitiveContent
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|