From 219d2529124298d9018c733fc70c659412ad289f Mon Sep 17 00:00:00 2001 From: Infi Date: Wed, 22 Mar 2023 02:02:34 +0100 Subject: [PATCH] feat: link and bot embeds --- app/build.gradle | 6 +- .../chat/revolt/api/internals/WebCompat.kt | 12 +- .../java/chat/revolt/api/routes/user/User.kt | 6 +- .../java/chat/revolt/components/chat/Embed.kt | 145 ++++++++++++++++++ .../chat/revolt/components/chat/Message.kt | 35 ++++- .../revolt/components/generic/Markdown.kt | 5 +- .../revolt/components/generic/RemoteImage.kt | 2 +- settings.gradle | 4 + 8 files changed, 197 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/chat/revolt/components/chat/Embed.kt diff --git a/app/build.gradle b/app/build.gradle index 2dff1ff7..3ac6bc98 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -112,8 +112,8 @@ dependencies { implementation 'androidx.compose.material:material' implementation 'androidx.compose.material3:material3' implementation "androidx.compose.ui:ui-tooling-preview" - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' - implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0' implementation 'androidx.activity:activity-compose:1.6.1' // Accompanist - Jetpack Compose Extensions @@ -144,7 +144,7 @@ dependencies { // Glide - Image Loading implementation "com.github.bumptech.glide:glide:$glide_version" - implementation "com.github.bumptech.glide:compose:1.0.0-alpha.1" + implementation "com.github.bumptech.glide:compose:1.0.0-alpha.4-SNAPSHOT" // AboutLibraries - automated OSS library attribution implementation "com.mikepenz:aboutlibraries-compose:$aboutlibraries_version" diff --git a/app/src/main/java/chat/revolt/api/internals/WebCompat.kt b/app/src/main/java/chat/revolt/api/internals/WebCompat.kt index ecdcc8ce..4fa28588 100644 --- a/app/src/main/java/chat/revolt/api/internals/WebCompat.kt +++ b/app/src/main/java/chat/revolt/api/internals/WebCompat.kt @@ -10,11 +10,15 @@ object WebCompat { @Composable fun parseColour(colour: String): Color { if (colour.startsWith("var(")) { + Log.d( + "WebCompat", + "Parsing colour $colour. ${colour.substringAfter("var(").substringBefore(")")}" + ) return when (colour.substringAfter("var(").substringBefore(")")) { - "accent" -> MaterialTheme.colorScheme.primary - "foreground" -> MaterialTheme.colorScheme.onBackground - "background" -> MaterialTheme.colorScheme.background - "error" -> MaterialTheme.colorScheme.error + "--accent" -> MaterialTheme.colorScheme.primary + "--foreground" -> MaterialTheme.colorScheme.onBackground + "--background" -> MaterialTheme.colorScheme.background + "--error" -> MaterialTheme.colorScheme.error else -> LocalContentColor.current } } else { diff --git a/app/src/main/java/chat/revolt/api/routes/user/User.kt b/app/src/main/java/chat/revolt/api/routes/user/User.kt index 96fc64ef..6b3f5de0 100644 --- a/app/src/main/java/chat/revolt/api/routes/user/User.kt +++ b/app/src/main/java/chat/revolt/api/routes/user/User.kt @@ -24,7 +24,11 @@ suspend fun fetchSelf(): User { val user = RevoltJson.decodeFromString(User.serializer(), response) - RevoltAPI.userCache[user.id!!] = user + if (user.id == null) { + throw Error("Self user ID is null") + } + + RevoltAPI.userCache[user.id] = user RevoltAPI.selfId = user.id return user diff --git a/app/src/main/java/chat/revolt/components/chat/Embed.kt b/app/src/main/java/chat/revolt/components/chat/Embed.kt new file mode 100644 index 00000000..33ad2a8a --- /dev/null +++ b/app/src/main/java/chat/revolt/components/chat/Embed.kt @@ -0,0 +1,145 @@ +package chat.revolt.components.chat + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import chat.revolt.api.asJanuaryProxyUrl +import chat.revolt.api.internals.WebCompat +import chat.revolt.api.schemas.Embed +import chat.revolt.components.generic.RemoteImage +import chat.revolt.components.generic.UIMarkdown +import chat.revolt.api.schemas.Embed as EmbedSchema + +@Composable +fun RegularEmbed( + embed: EmbedSchema, + modifier: Modifier = Modifier, + onLinkClick: (String) -> Unit = {} +) { + Row( + modifier = modifier + .clip(MaterialTheme.shapes.medium) + .background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)) + .fillMaxWidth() + .height(IntrinsicSize.Min) + ) { + // Stripe at the left side of the embed + Box( + modifier = Modifier + .width(4.dp) + .fillMaxHeight() + .background( + embed.colour?.let { WebCompat.parseColour(it) } + ?: MaterialTheme.colorScheme.primary + ) + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + ) { + Column { + // Title row (icon + title) + Row( + modifier = Modifier + .then( + if (embed.originalURL != null) + Modifier + .clickable { + onLinkClick(embed.originalURL) + } + else Modifier + ) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + embed.iconURL?.let { + if (it.endsWith(".svg")) return@let + + RemoteImage( + url = asJanuaryProxyUrl(it), + width = 48, + height = 48, + contentScale = ContentScale.Crop, + description = null // decorative + ) + Spacer(modifier = Modifier.width(8.dp)) + } + embed.title?.let { + Text( + text = it, + fontWeight = FontWeight.Bold + ) + } + } + + // Description + embed.description?.let { + UIMarkdown( + text = it, + modifier = Modifier.padding(top = 8.dp) + ) + } + + // Image + embed.image?.let { + if (it.url == null || it.url.endsWith(".svg")) return@let + + Spacer(modifier = Modifier.height(8.dp)) + RemoteImage( + url = asJanuaryProxyUrl(it.url), + width = (it.width ?: 48).toInt(), + height = (it.height ?: 48).toInt(), + modifier = Modifier + .clip(MaterialTheme.shapes.medium) + .then( + if (embed.originalURL != null) Modifier.clickable { + onLinkClick(embed.originalURL) + } else Modifier + ) + .aspectRatio( + (it.width?.toFloat() ?: 0f) / (it.height?.toFloat() ?: 0f) + ), + contentScale = ContentScale.Crop, + description = null // decorative + ) + } + } + } + } + +} + +@Composable +fun Embed( + embed: Embed, + modifier: Modifier = Modifier, + onLinkClick: (String) -> Unit +) { + Column { + when (embed.type) { + else -> RegularEmbed(embed = embed, modifier = modifier, onLinkClick = onLinkClick) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/components/chat/Message.kt b/app/src/main/java/chat/revolt/components/chat/Message.kt index 82157d35..787b4c0a 100644 --- a/app/src/main/java/chat/revolt/components/chat/Message.kt +++ b/app/src/main/java/chat/revolt/components/chat/Message.kt @@ -31,14 +31,16 @@ import chat.revolt.components.generic.UserAvatar import chat.revolt.components.generic.UserAvatarWidthPlaceholder import chat.revolt.api.schemas.Message as MessageSchema -fun viewAttachmentInBrowser(ctx: android.content.Context, attachment: AutumnResource) { +fun viewUrlInBrowser(ctx: android.content.Context, url: String) { val customTab = CustomTabsIntent .Builder() .build() - customTab.launchUrl( - ctx, - Uri.parse("$REVOLT_FILES/attachments/${attachment.id}/${attachment.filename}") - ) + customTab.launchUrl(ctx, Uri.parse(url)) +} + +fun viewAttachmentInBrowser(ctx: android.content.Context, attachment: AutumnResource) { + val url = REVOLT_FILES + attachment.id + "/" + attachment.filename + viewUrlInBrowser(ctx, url) } @@ -159,10 +161,31 @@ fun Message( message.attachments?.let { message.attachments.forEach { attachment -> - Spacer(modifier = Modifier.height(5.dp)) + Spacer(modifier = Modifier.height(2.dp)) MessageAttachment(attachment) { viewAttachmentInBrowser(context, attachment) } + Spacer(modifier = Modifier.height(2.dp)) + } + } + + message.embeds?.let { + message.embeds.forEach { embed -> + val embedIsEmpty = + embed.title == null && embed.description == null && embed.iconURL == null && embed.image == null + + if (embedIsEmpty) { + // if we do not emit anything, compose will cause an internal error. + // FIXME if you are doing fixme's anyways then check if this is still an issue + Box {} + return@forEach + } + + Spacer(modifier = Modifier.height(8.dp)) + Embed(embed = embed, onLinkClick = { + viewUrlInBrowser(context, it) + }) + Spacer(modifier = Modifier.height(8.dp)) } } } diff --git a/app/src/main/java/chat/revolt/components/generic/Markdown.kt b/app/src/main/java/chat/revolt/components/generic/Markdown.kt index 8d2936a9..1dd9d15d 100644 --- a/app/src/main/java/chat/revolt/components/generic/Markdown.kt +++ b/app/src/main/java/chat/revolt/components/generic/Markdown.kt @@ -4,6 +4,7 @@ import android.text.SpannableStringBuilder import android.text.TextUtils import android.util.Log import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable @@ -42,8 +43,8 @@ import com.discord.simpleast.core.simple.SimpleRenderer @Composable fun UIMarkdown( text: String, - fontSize: TextUnit, modifier: Modifier = Modifier, + fontSize: TextUnit = LocalTextStyle.current.fontSize, maxLines: Int = Int.MAX_VALUE, ) { val context = LocalContext.current @@ -96,8 +97,6 @@ fun UIMarkdown( setTextColor(foregroundColor.toArgb()) setMaxLines(maxLines) setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, fontSize.value) - - setText(spannableStringBuilder.value) } }, modifier = modifier, diff --git a/app/src/main/java/chat/revolt/components/generic/RemoteImage.kt b/app/src/main/java/chat/revolt/components/generic/RemoteImage.kt index f624c275..03b5101c 100644 --- a/app/src/main/java/chat/revolt/components/generic/RemoteImage.kt +++ b/app/src/main/java/chat/revolt/components/generic/RemoteImage.kt @@ -16,7 +16,7 @@ import com.bumptech.glide.integration.compose.GlideImage @Composable fun RemoteImage( url: String, - description: String, + description: String?, modifier: Modifier = Modifier, contentScale: ContentScale = ContentScale.Crop, width: Int = 0, diff --git a/settings.gradle b/settings.gradle index e7d9f024..96ba9041 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,10 @@ dependencyResolutionManagement { google() mavenCentral() maven { url "https://jitpack.io" } + maven { + name 'snapshot' + url 'https://oss.sonatype.org/content/repositories/snapshots' + } } } rootProject.name = "Revolt"