feat: link and bot embeds
This commit is contained in:
parent
40a9cdf344
commit
219d252912
|
|
@ -112,8 +112,8 @@ dependencies {
|
||||||
implementation 'androidx.compose.material:material'
|
implementation 'androidx.compose.material:material'
|
||||||
implementation 'androidx.compose.material3:material3'
|
implementation 'androidx.compose.material3:material3'
|
||||||
implementation "androidx.compose.ui:ui-tooling-preview"
|
implementation "androidx.compose.ui:ui-tooling-preview"
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0'
|
||||||
implementation 'androidx.activity:activity-compose:1.6.1'
|
implementation 'androidx.activity:activity-compose:1.6.1'
|
||||||
|
|
||||||
// Accompanist - Jetpack Compose Extensions
|
// Accompanist - Jetpack Compose Extensions
|
||||||
|
|
@ -144,7 +144,7 @@ dependencies {
|
||||||
|
|
||||||
// Glide - Image Loading
|
// Glide - Image Loading
|
||||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
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
|
// AboutLibraries - automated OSS library attribution
|
||||||
implementation "com.mikepenz:aboutlibraries-compose:$aboutlibraries_version"
|
implementation "com.mikepenz:aboutlibraries-compose:$aboutlibraries_version"
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,15 @@ object WebCompat {
|
||||||
@Composable
|
@Composable
|
||||||
fun parseColour(colour: String): Color {
|
fun parseColour(colour: String): Color {
|
||||||
if (colour.startsWith("var(")) {
|
if (colour.startsWith("var(")) {
|
||||||
|
Log.d(
|
||||||
|
"WebCompat",
|
||||||
|
"Parsing colour $colour. ${colour.substringAfter("var(").substringBefore(")")}"
|
||||||
|
)
|
||||||
return when (colour.substringAfter("var(").substringBefore(")")) {
|
return when (colour.substringAfter("var(").substringBefore(")")) {
|
||||||
"accent" -> MaterialTheme.colorScheme.primary
|
"--accent" -> MaterialTheme.colorScheme.primary
|
||||||
"foreground" -> MaterialTheme.colorScheme.onBackground
|
"--foreground" -> MaterialTheme.colorScheme.onBackground
|
||||||
"background" -> MaterialTheme.colorScheme.background
|
"--background" -> MaterialTheme.colorScheme.background
|
||||||
"error" -> MaterialTheme.colorScheme.error
|
"--error" -> MaterialTheme.colorScheme.error
|
||||||
else -> LocalContentColor.current
|
else -> LocalContentColor.current
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,11 @@ suspend fun fetchSelf(): User {
|
||||||
|
|
||||||
val user = RevoltJson.decodeFromString(User.serializer(), response)
|
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
|
RevoltAPI.selfId = user.id
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,14 +31,16 @@ import chat.revolt.components.generic.UserAvatar
|
||||||
import chat.revolt.components.generic.UserAvatarWidthPlaceholder
|
import chat.revolt.components.generic.UserAvatarWidthPlaceholder
|
||||||
import chat.revolt.api.schemas.Message as MessageSchema
|
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
|
val customTab = CustomTabsIntent
|
||||||
.Builder()
|
.Builder()
|
||||||
.build()
|
.build()
|
||||||
customTab.launchUrl(
|
customTab.launchUrl(ctx, Uri.parse(url))
|
||||||
ctx,
|
}
|
||||||
Uri.parse("$REVOLT_FILES/attachments/${attachment.id}/${attachment.filename}")
|
|
||||||
)
|
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?.let {
|
||||||
message.attachments.forEach { attachment ->
|
message.attachments.forEach { attachment ->
|
||||||
Spacer(modifier = Modifier.height(5.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
MessageAttachment(attachment) {
|
MessageAttachment(attachment) {
|
||||||
viewAttachmentInBrowser(context, 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.text.SpannableStringBuilder
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -42,8 +43,8 @@ import com.discord.simpleast.core.simple.SimpleRenderer
|
||||||
@Composable
|
@Composable
|
||||||
fun UIMarkdown(
|
fun UIMarkdown(
|
||||||
text: String,
|
text: String,
|
||||||
fontSize: TextUnit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
fontSize: TextUnit = LocalTextStyle.current.fontSize,
|
||||||
maxLines: Int = Int.MAX_VALUE,
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -96,8 +97,6 @@ fun UIMarkdown(
|
||||||
setTextColor(foregroundColor.toArgb())
|
setTextColor(foregroundColor.toArgb())
|
||||||
setMaxLines(maxLines)
|
setMaxLines(maxLines)
|
||||||
setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, fontSize.value)
|
setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, fontSize.value)
|
||||||
|
|
||||||
setText(spannableStringBuilder.value)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import com.bumptech.glide.integration.compose.GlideImage
|
||||||
@Composable
|
@Composable
|
||||||
fun RemoteImage(
|
fun RemoteImage(
|
||||||
url: String,
|
url: String,
|
||||||
description: String,
|
description: String?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
contentScale: ContentScale = ContentScale.Crop,
|
contentScale: ContentScale = ContentScale.Crop,
|
||||||
width: Int = 0,
|
width: Int = 0,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ dependencyResolutionManagement {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
|
maven {
|
||||||
|
name 'snapshot'
|
||||||
|
url 'https://oss.sonatype.org/content/repositories/snapshots'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rootProject.name = "Revolt"
|
rootProject.name = "Revolt"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue