Merge branch 'main' of https://github.com/vitorpamplona/amethyst
This commit is contained in:
+93
-1
@@ -20,12 +20,37 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.nip22Comments
|
package com.vitorpamplona.amethyst.ui.note.nip22Comments
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.text.LinkAnnotation
|
||||||
|
import androidx.compose.ui.text.LinkInteractionListener
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.text.withLink
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
|
||||||
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||||
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
|
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
|
||||||
import com.vitorpamplona.quartz.nip73ExternalIds.topics.HashtagId
|
import com.vitorpamplona.quartz.nip73ExternalIds.topics.HashtagId
|
||||||
|
import com.vitorpamplona.quartz.nip73ExternalIds.urls.UrlId
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisplayExternalId(
|
fun DisplayExternalId(
|
||||||
@@ -42,6 +67,73 @@ fun DisplayExternalId(
|
|||||||
DisplayHashtagExternalId(externalId, accountViewModel, nav)
|
DisplayHashtagExternalId(externalId, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {}
|
is UrlId -> {
|
||||||
|
DisplayUrlExternalId(externalId)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
DisplayGenericExternalId(externalId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplayUrlExternalId(externalId: UrlId) {
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
DisplayExternalIdChip(
|
||||||
|
symbol = MaterialSymbols.Link,
|
||||||
|
contentDescription = stringRes(id = R.string.external_url_scope),
|
||||||
|
label = externalId.url,
|
||||||
|
linkInteractionListener = { runCatching { uriHandler.openUri(externalId.url) } },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplayGenericExternalId(externalId: ExternalId) {
|
||||||
|
DisplayExternalIdChip(
|
||||||
|
symbol = MaterialSymbols.Public,
|
||||||
|
contentDescription = stringRes(id = R.string.external_id_scope),
|
||||||
|
label = externalId.toScope(),
|
||||||
|
linkInteractionListener = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DisplayExternalIdChip(
|
||||||
|
symbol: MaterialSymbol,
|
||||||
|
contentDescription: String,
|
||||||
|
label: String,
|
||||||
|
linkInteractionListener: LinkInteractionListener?,
|
||||||
|
) {
|
||||||
|
Row(modifier = MaterialTheme.colorScheme.replyModifier.padding(10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Icon(
|
||||||
|
symbol = symbol,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(StdHorzSpacer)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text =
|
||||||
|
if (linkInteractionListener != null) {
|
||||||
|
buildAnnotatedString {
|
||||||
|
withLink(
|
||||||
|
LinkAnnotation.Clickable("externalId", null, linkInteractionListener),
|
||||||
|
) {
|
||||||
|
append(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buildAnnotatedString { append(label) }
|
||||||
|
},
|
||||||
|
style =
|
||||||
|
LocalTextStyle.current.copy(
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContent
|
|||||||
import com.vitorpamplona.amethyst.ui.note.ReplyNoteComposition
|
import com.vitorpamplona.amethyst.ui.note.ReplyNoteComposition
|
||||||
import com.vitorpamplona.amethyst.ui.note.ReplyToLabel
|
import com.vitorpamplona.amethyst.ui.note.ReplyToLabel
|
||||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
|
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.nip22Comments.DisplayExternalId
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
@@ -54,7 +55,9 @@ import com.vitorpamplona.quartz.nip01Core.tags.people.hasAnyTaggedUser
|
|||||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||||
import com.vitorpamplona.quartz.nip14Subject.subject
|
import com.vitorpamplona.quartz.nip14Subject.subject
|
||||||
|
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||||
|
import com.vitorpamplona.quartz.nip73ExternalIds.scope
|
||||||
|
|
||||||
enum class ReplyRenderType {
|
enum class ReplyRenderType {
|
||||||
FULL,
|
FULL,
|
||||||
@@ -119,6 +122,14 @@ fun RenderTextEvent(
|
|||||||
Spacer(modifier = HalfVertSpacer)
|
Spacer(modifier = HalfVertSpacer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (!makeItShort && noteEvent is CommentEvent) {
|
||||||
|
// A comment scoped to an external identifier (`I` tag) has no in-cache parent
|
||||||
|
// note. Show the scope itself as the reply context.
|
||||||
|
val scope = remember(note) { noteEvent.scope() }
|
||||||
|
if (scope != null) {
|
||||||
|
DisplayExternalId(scope, accountViewModel, nav)
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1475,6 +1475,9 @@
|
|||||||
<string name="hashtag_exclusive">Hashtag-exclusive Post</string>
|
<string name="hashtag_exclusive">Hashtag-exclusive Post</string>
|
||||||
<string name="hashtag_exclusive_explainer">Only followers of the hashtag will see it. Your general followers won\'t see it.</string>
|
<string name="hashtag_exclusive_explainer">Only followers of the hashtag will see it. Your general followers won\'t see it.</string>
|
||||||
|
|
||||||
|
<string name="external_url_scope">Comment on a website</string>
|
||||||
|
<string name="external_id_scope">Comment on an external resource</string>
|
||||||
|
|
||||||
<string name="long_form_reading_minutes">%1$d min read</string>
|
<string name="long_form_reading_minutes">%1$d min read</string>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -773,15 +773,20 @@ open class Note(
|
|||||||
return author?.reportsOrNull()?.hasReportNewerThan(dayAgo) ?: false
|
return author?.reportsOrNull()?.hasReportNewerThan(dayAgo) ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isNewThread(): Boolean =
|
fun isNewThread(): Boolean {
|
||||||
(
|
val event = event
|
||||||
|
return (
|
||||||
event is RepostEvent ||
|
event is RepostEvent ||
|
||||||
event is GenericRepostEvent ||
|
event is GenericRepostEvent ||
|
||||||
replyTo == null ||
|
replyTo == null ||
|
||||||
replyTo?.size == 0
|
replyTo?.size == 0
|
||||||
) &&
|
) &&
|
||||||
|
// A comment scoped to an external identifier (`I` tag) is a reply to that
|
||||||
|
// scope, even though there is no in-cache parent note to populate replyTo.
|
||||||
|
!(event is CommentEvent && event.hasRootScopeIdentifier()) &&
|
||||||
event !is ChannelMessageEvent &&
|
event !is ChannelMessageEvent &&
|
||||||
event !is LiveActivitiesChatMessageEvent
|
event !is LiveActivitiesChatMessageEvent
|
||||||
|
}
|
||||||
|
|
||||||
fun hasZapped(loggedIn: User): Boolean = zaps.any { it.key.author == loggedIn }
|
fun hasZapped(loggedIn: User): Boolean = zaps.any { it.key.author == loggedIn }
|
||||||
|
|
||||||
|
|||||||
@@ -181,6 +181,9 @@ class CommentEvent(
|
|||||||
|
|
||||||
fun isScoped(scopeTest: (String) -> Boolean) = tags.any { RootIdentifierTag.isTagged(it, scopeTest) || ReplyIdentifierTag.isTagged(it, scopeTest) }
|
fun isScoped(scopeTest: (String) -> Boolean) = tags.any { RootIdentifierTag.isTagged(it, scopeTest) || ReplyIdentifierTag.isTagged(it, scopeTest) }
|
||||||
|
|
||||||
|
/** True when the comment points at an external identifier (`I` tag), e.g. a hashtag, geohash or url. */
|
||||||
|
fun hasRootScopeIdentifier() = tags.any { RootIdentifierTag.match(it) }
|
||||||
|
|
||||||
fun hasRootScopeKind(kind: String) = tags.any(RootKindTag::isKind, kind)
|
fun hasRootScopeKind(kind: String) = tags.any(RootKindTag::isKind, kind)
|
||||||
|
|
||||||
fun hasReplyScopeKind(kind: String) = tags.any(ReplyKindTag::isKind, kind)
|
fun hasReplyScopeKind(kind: String) = tags.any(ReplyKindTag::isKind, kind)
|
||||||
|
|||||||
Reference in New Issue
Block a user