feat: intercept invite links in md, mentions clickable

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-09-17 05:15:24 +02:00
parent a2381fa005
commit ba6edec5a5
5 changed files with 95 additions and 5 deletions

View File

@ -46,6 +46,7 @@ const val REVOLT_MARKETING = "https://revolt.chat"
const val REVOLT_FILES = "https://autumn.revolt.chat"
const val REVOLT_JANUARY = "https://jan.revolt.chat"
const val REVOLT_APP = "https://app.revolt.chat"
const val REVOLT_INVITES = "https://rvlt.gg"
const val REVOLT_WEBSOCKET = "wss://ws.revolt.chat"
fun buildUserAgent(accessMethod: String = "Ktor"): String {

View File

@ -0,0 +1,11 @@
package chat.revolt.callbacks
import kotlinx.coroutines.channels.Channel
sealed class Action {
data class OpenUserSheet(val userId: String, val serverId: String?) : Action()
}
val ActionChannel = Channel<Action>(
Channel.BUFFERED
)

View File

@ -1,13 +1,59 @@
package chat.revolt.internals.markdown
import android.content.Intent
import android.net.Uri
import android.text.TextPaint
import android.text.style.ClickableSpan
import android.view.View
import androidx.browser.customtabs.CustomTabsIntent
import chat.revolt.activities.InviteActivity
import chat.revolt.api.REVOLT_APP
import chat.revolt.api.REVOLT_INVITES
import chat.revolt.callbacks.Action
import chat.revolt.callbacks.ActionChannel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
class LinkSpan(private val url: String) : ClickableSpan() {
class LinkSpan(private val url: String, private val drawBackground: Boolean = false) :
ClickableSpan() {
override fun onClick(widget: View) {
val uri = Uri.parse(url)
// Intercept invite links
if (uri.host == Uri.parse(REVOLT_INVITES).host!! ||
(uri.host?.endsWith(Uri.parse(REVOLT_APP).host!!) == true && uri.path?.startsWith(
"/invite"
) == true)
) {
val intent = Intent(
widget.context,
InviteActivity::class.java
).setAction(Intent.ACTION_VIEW)
intent.data = uri
widget.context.startActivity(intent)
return
}
if (url.startsWith("revolt-android://link-action")) {
// parse action
val action = uri.pathSegments[0]
when (action) {
"user" -> {
val userId = uri.getQueryParameter("user")
val serverId = uri.getQueryParameter("server")
runBlocking(Dispatchers.IO) {
ActionChannel.send(Action.OpenUserSheet(userId!!, serverId))
}
}
}
return
}
val customTab = CustomTabsIntent.Builder()
.setShowTitle(true)
.build()
@ -18,5 +64,8 @@ class LinkSpan(private val url: String) : ClickableSpan() {
override fun updateDrawState(ds: TextPaint) {
ds.color = ds.linkColor
ds.isUnderlineText = false
if (drawBackground) {
ds.bgColor = ds.linkColor and 0x33ffffff // 20% alpha
}
}
}

View File

@ -14,10 +14,23 @@ import java.util.Locale
class UserMentionNode(private val userId: String) : Node<MarkdownContext>() {
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
builder.append(
renderContext.memberMap[userId]?.let { "@$it" }
?: renderContext.userMap[userId]?.let { "@${it.username}" }
?: "<@${userId}>"
val content = renderContext.memberMap[userId]?.let { "@$it" }
?: renderContext.userMap[userId]?.let { "@${it.username}" }
?: "<@${userId}>"
builder.append(content)
builder.setSpan(
LinkSpan(
"revolt-android://link-action/user?user=$userId${
renderContext.serverId?.let {
"&server=$it"
}.orEmpty()
}",
drawBackground = true
),
builder.length - content.length,
builder.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}

View File

@ -72,6 +72,8 @@ import chat.revolt.api.schemas.ChannelType
import chat.revolt.api.schemas.User
import chat.revolt.api.settings.FeatureFlag
import chat.revolt.api.settings.SyncedSettings
import chat.revolt.callbacks.Action
import chat.revolt.callbacks.ActionChannel
import chat.revolt.components.chat.DisconnectedNotice
import chat.revolt.components.generic.GroupIcon
import chat.revolt.components.generic.UserAvatar
@ -348,6 +350,20 @@ fun ChatRouterScreen(
}
}
LaunchedEffect(Unit) {
while (true) {
ActionChannel.receive().let { action ->
when (action) {
is Action.OpenUserSheet -> {
userContextSheetTarget = action.userId
userContextSheetServer = action.serverId
showUserContextSheet = true
}
}
}
}
}
if (!viewModel.latestChangelogRead) {
val changelogSheetState = rememberModalBottomSheetState()