diff --git a/app/src/main/java/chat/revolt/api/RevoltAPI.kt b/app/src/main/java/chat/revolt/api/RevoltAPI.kt index 57288e8d..77da79db 100644 --- a/app/src/main/java/chat/revolt/api/RevoltAPI.kt +++ b/app/src/main/java/chat/revolt/api/RevoltAPI.kt @@ -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 { diff --git a/app/src/main/java/chat/revolt/callbacks/ActionChannel.kt b/app/src/main/java/chat/revolt/callbacks/ActionChannel.kt new file mode 100644 index 00000000..07b73627 --- /dev/null +++ b/app/src/main/java/chat/revolt/callbacks/ActionChannel.kt @@ -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( + Channel.BUFFERED +) \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/internals/markdown/LinkSpan.kt b/app/src/main/java/chat/revolt/internals/markdown/LinkSpan.kt index 08a8c5ae..242981b3 100644 --- a/app/src/main/java/chat/revolt/internals/markdown/LinkSpan.kt +++ b/app/src/main/java/chat/revolt/internals/markdown/LinkSpan.kt @@ -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 + } } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt b/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt index 85a65f7c..0cdb40e8 100644 --- a/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt +++ b/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt @@ -14,10 +14,23 @@ import java.util.Locale class UserMentionNode(private val userId: String) : Node() { 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 ) } } diff --git a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt index c4a4b8f0..7beb2247 100644 --- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt @@ -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()