feat: intercept invite links in md, mentions clickable
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
a2381fa005
commit
ba6edec5a5
|
|
@ -46,6 +46,7 @@ const val REVOLT_MARKETING = "https://revolt.chat"
|
||||||
const val REVOLT_FILES = "https://autumn.revolt.chat"
|
const val REVOLT_FILES = "https://autumn.revolt.chat"
|
||||||
const val REVOLT_JANUARY = "https://jan.revolt.chat"
|
const val REVOLT_JANUARY = "https://jan.revolt.chat"
|
||||||
const val REVOLT_APP = "https://app.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"
|
const val REVOLT_WEBSOCKET = "wss://ws.revolt.chat"
|
||||||
|
|
||||||
fun buildUserAgent(accessMethod: String = "Ktor"): String {
|
fun buildUserAgent(accessMethod: String = "Ktor"): String {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
@ -1,13 +1,59 @@
|
||||||
package chat.revolt.internals.markdown
|
package chat.revolt.internals.markdown
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
import android.text.style.ClickableSpan
|
import android.text.style.ClickableSpan
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
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) {
|
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()
|
val customTab = CustomTabsIntent.Builder()
|
||||||
.setShowTitle(true)
|
.setShowTitle(true)
|
||||||
.build()
|
.build()
|
||||||
|
|
@ -18,5 +64,8 @@ class LinkSpan(private val url: String) : ClickableSpan() {
|
||||||
override fun updateDrawState(ds: TextPaint) {
|
override fun updateDrawState(ds: TextPaint) {
|
||||||
ds.color = ds.linkColor
|
ds.color = ds.linkColor
|
||||||
ds.isUnderlineText = false
|
ds.isUnderlineText = false
|
||||||
|
if (drawBackground) {
|
||||||
|
ds.bgColor = ds.linkColor and 0x33ffffff // 20% alpha
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,10 +14,23 @@ import java.util.Locale
|
||||||
|
|
||||||
class UserMentionNode(private val userId: String) : Node<MarkdownContext>() {
|
class UserMentionNode(private val userId: String) : Node<MarkdownContext>() {
|
||||||
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
|
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
|
||||||
builder.append(
|
val content = renderContext.memberMap[userId]?.let { "@$it" }
|
||||||
renderContext.memberMap[userId]?.let { "@$it" }
|
?: renderContext.userMap[userId]?.let { "@${it.username}" }
|
||||||
?: renderContext.userMap[userId]?.let { "@${it.username}" }
|
?: "<@${userId}>"
|
||||||
?: "<@${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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,8 @@ import chat.revolt.api.schemas.ChannelType
|
||||||
import chat.revolt.api.schemas.User
|
import chat.revolt.api.schemas.User
|
||||||
import chat.revolt.api.settings.FeatureFlag
|
import chat.revolt.api.settings.FeatureFlag
|
||||||
import chat.revolt.api.settings.SyncedSettings
|
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.chat.DisconnectedNotice
|
||||||
import chat.revolt.components.generic.GroupIcon
|
import chat.revolt.components.generic.GroupIcon
|
||||||
import chat.revolt.components.generic.UserAvatar
|
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) {
|
if (!viewModel.latestChangelogRead) {
|
||||||
val changelogSheetState = rememberModalBottomSheetState()
|
val changelogSheetState = rememberModalBottomSheetState()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue