feat: channel links, link info on long-tap
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
cdc944a571
commit
a3c5e43c5d
|
|
@ -4,6 +4,8 @@ import kotlinx.coroutines.channels.Channel
|
||||||
|
|
||||||
sealed class Action {
|
sealed class Action {
|
||||||
data class OpenUserSheet(val userId: String, val serverId: String?) : Action()
|
data class OpenUserSheet(val userId: String, val serverId: String?) : Action()
|
||||||
|
data class SwitchChannel(val channelId: String) : Action()
|
||||||
|
data class LinkInfo(val url: String) : Action()
|
||||||
}
|
}
|
||||||
|
|
||||||
val ActionChannel = Channel<Action>(
|
val ActionChannel = Channel<Action>(
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import android.net.Uri
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.text.method.LinkMovementMethod
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
|
@ -59,6 +58,7 @@ import chat.revolt.api.schemas.AutumnResource
|
||||||
import chat.revolt.api.schemas.User
|
import chat.revolt.api.schemas.User
|
||||||
import chat.revolt.components.generic.UserAvatar
|
import chat.revolt.components.generic.UserAvatar
|
||||||
import chat.revolt.components.generic.UserAvatarWidthPlaceholder
|
import chat.revolt.components.generic.UserAvatarWidthPlaceholder
|
||||||
|
import chat.revolt.internals.markdown.LongClickLinkMovementMethod
|
||||||
import chat.revolt.api.schemas.Message as MessageSchema
|
import chat.revolt.api.schemas.Message as MessageSchema
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -294,7 +294,7 @@ fun Message(
|
||||||
textSize = 16f
|
textSize = 16f
|
||||||
typeface = ResourcesCompat.getFont(ctx, R.font.inter)
|
typeface = ResourcesCompat.getFont(ctx, R.font.inter)
|
||||||
|
|
||||||
movementMethod = LinkMovementMethod.getInstance()
|
movementMethod = LongClickLinkMovementMethod.instance
|
||||||
|
|
||||||
setTextColor(contentColor.toArgb())
|
setTextColor(contentColor.toArgb())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package chat.revolt.components.generic
|
||||||
|
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.text.method.LinkMovementMethod
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
|
@ -25,6 +24,7 @@ import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.internals.markdown.LongClickLinkMovementMethod
|
||||||
import chat.revolt.internals.markdown.MarkdownContext
|
import chat.revolt.internals.markdown.MarkdownContext
|
||||||
import chat.revolt.internals.markdown.MarkdownParser
|
import chat.revolt.internals.markdown.MarkdownParser
|
||||||
import chat.revolt.internals.markdown.MarkdownState
|
import chat.revolt.internals.markdown.MarkdownState
|
||||||
|
|
@ -97,7 +97,7 @@ fun UIMarkdown(
|
||||||
setMaxLines(maxLines)
|
setMaxLines(maxLines)
|
||||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize.value)
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize.value)
|
||||||
|
|
||||||
movementMethod = LinkMovementMethod.getInstance()
|
movementMethod = LongClickLinkMovementMethod.instance
|
||||||
|
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package chat.revolt.internals
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
|
object Platform {
|
||||||
|
fun needsShowClipboardNotification(): Boolean {
|
||||||
|
return Build.VERSION.SDK_INT < Build.VERSION_CODES.S
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ package chat.revolt.internals.markdown
|
||||||
import android.content.Intent
|
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.view.View
|
import android.view.View
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import chat.revolt.activities.InviteActivity
|
import chat.revolt.activities.InviteActivity
|
||||||
|
|
@ -15,7 +14,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
class LinkSpan(private val url: String, private val drawBackground: Boolean = false) :
|
class LinkSpan(private val url: String, private val drawBackground: Boolean = false) :
|
||||||
ClickableSpan() {
|
LongClickableSpan() {
|
||||||
override fun onClick(widget: View) {
|
override fun onClick(widget: View) {
|
||||||
val uri = Uri.parse(url)
|
val uri = Uri.parse(url)
|
||||||
|
|
||||||
|
|
@ -49,6 +48,14 @@ class LinkSpan(private val url: String, private val drawBackground: Boolean = fa
|
||||||
ActionChannel.send(Action.OpenUserSheet(userId!!, serverId))
|
ActionChannel.send(Action.OpenUserSheet(userId!!, serverId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"channel" -> {
|
||||||
|
val channelId = uri.getQueryParameter("channel")
|
||||||
|
|
||||||
|
runBlocking(Dispatchers.IO) {
|
||||||
|
ActionChannel.send(Action.SwitchChannel(channelId!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
@ -61,6 +68,12 @@ class LinkSpan(private val url: String, private val drawBackground: Boolean = fa
|
||||||
customTab.launchUrl(widget.context, Uri.parse(url))
|
customTab.launchUrl(widget.context, Uri.parse(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLongClick(view: View?) {
|
||||||
|
runBlocking(Dispatchers.IO) {
|
||||||
|
ActionChannel.send(Action.LinkInfo(url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateDrawState(ds: TextPaint) {
|
override fun updateDrawState(ds: TextPaint) {
|
||||||
ds.color = ds.linkColor
|
ds.color = ds.linkColor
|
||||||
ds.isUnderlineText = false
|
ds.isUnderlineText = false
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package chat.revolt.internals.markdown
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.text.Selection
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.text.method.MovementMethod
|
||||||
|
import android.text.style.ClickableSpan
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
|
||||||
|
abstract class LongClickableSpan : ClickableSpan() {
|
||||||
|
abstract fun onLongClick(view: View?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/63398843
|
||||||
|
class LongClickLinkMovementMethod : LinkMovementMethod() {
|
||||||
|
private var longClickHandler: Handler? = null
|
||||||
|
private var isLongPressed = false
|
||||||
|
override fun onTouchEvent(
|
||||||
|
widget: TextView, buffer: Spannable,
|
||||||
|
event: MotionEvent
|
||||||
|
): Boolean {
|
||||||
|
val action = event.action
|
||||||
|
if (action == MotionEvent.ACTION_CANCEL) {
|
||||||
|
longClickHandler?.removeCallbacksAndMessages(null)
|
||||||
|
}
|
||||||
|
if (action == MotionEvent.ACTION_UP ||
|
||||||
|
action == MotionEvent.ACTION_DOWN
|
||||||
|
) {
|
||||||
|
var x = event.x.toInt()
|
||||||
|
var y = event.y.toInt()
|
||||||
|
x -= widget.totalPaddingLeft
|
||||||
|
y -= widget.totalPaddingTop
|
||||||
|
x += widget.scrollX
|
||||||
|
y += widget.scrollY
|
||||||
|
val layout = widget.layout
|
||||||
|
val line = layout.getLineForVertical(y)
|
||||||
|
val off = layout.getOffsetForHorizontal(line, x.toFloat())
|
||||||
|
val link = buffer.getSpans(
|
||||||
|
off, off,
|
||||||
|
LongClickableSpan::class.java
|
||||||
|
)
|
||||||
|
if (link.isNotEmpty()) {
|
||||||
|
if (action == MotionEvent.ACTION_UP) {
|
||||||
|
longClickHandler?.removeCallbacksAndMessages(null)
|
||||||
|
if (!isLongPressed) {
|
||||||
|
link[0].onClick(widget)
|
||||||
|
}
|
||||||
|
isLongPressed = false
|
||||||
|
} else {
|
||||||
|
Selection.setSelection(
|
||||||
|
buffer,
|
||||||
|
buffer.getSpanStart(link[0]),
|
||||||
|
buffer.getSpanEnd(link[0])
|
||||||
|
)
|
||||||
|
longClickHandler?.postDelayed({
|
||||||
|
link[0].onLongClick(widget)
|
||||||
|
isLongPressed = true
|
||||||
|
}, LONG_CLICK_TIME)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onTouchEvent(widget, buffer, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val LONG_CLICK_TIME = 500L
|
||||||
|
val instance: MovementMethod?
|
||||||
|
get() {
|
||||||
|
if (sInstance == null) {
|
||||||
|
sInstance = LongClickLinkMovementMethod()
|
||||||
|
// Handler deprecated https://stackoverflow.com/a/62477706/4116924
|
||||||
|
sInstance!!.longClickHandler = Handler(Looper.getMainLooper())
|
||||||
|
}
|
||||||
|
return sInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
private var sInstance: LongClickLinkMovementMethod? = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -37,9 +37,18 @@ class UserMentionNode(private val userId: String) : Node<MarkdownContext>() {
|
||||||
|
|
||||||
class ChannelMentionNode(private val channelId: String) : Node<MarkdownContext>() {
|
class ChannelMentionNode(private val channelId: String) : Node<MarkdownContext>() {
|
||||||
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
|
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
|
||||||
builder.append(
|
val content = renderContext.channelMap[channelId]?.let { "#$it" }
|
||||||
renderContext.channelMap[channelId]?.let { "#$it" }
|
?: "<#$channelId>"
|
||||||
?: "<#${channelId}>"
|
|
||||||
|
builder.append(content)
|
||||||
|
builder.setSpan(
|
||||||
|
LinkSpan(
|
||||||
|
"revolt-android://link-action/channel?channel=$channelId",
|
||||||
|
drawBackground = true
|
||||||
|
),
|
||||||
|
builder.length - content.length,
|
||||||
|
builder.length,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import androidx.compose.material3.DrawerState
|
||||||
import androidx.compose.material3.DrawerValue
|
import androidx.compose.material3.DrawerValue
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
|
@ -49,7 +50,9 @@ import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
@ -91,6 +94,7 @@ import chat.revolt.screens.chat.views.NoCurrentChannelScreen
|
||||||
import chat.revolt.screens.chat.views.channel.ChannelScreen
|
import chat.revolt.screens.chat.views.channel.ChannelScreen
|
||||||
import chat.revolt.sheets.AddServerSheet
|
import chat.revolt.sheets.AddServerSheet
|
||||||
import chat.revolt.sheets.ChangelogSheet
|
import chat.revolt.sheets.ChangelogSheet
|
||||||
|
import chat.revolt.sheets.LinkInfoSheet
|
||||||
import chat.revolt.sheets.ServerContextSheet
|
import chat.revolt.sheets.ServerContextSheet
|
||||||
import chat.revolt.sheets.StatusSheet
|
import chat.revolt.sheets.StatusSheet
|
||||||
import chat.revolt.sheets.UserContextSheet
|
import chat.revolt.sheets.UserContextSheet
|
||||||
|
|
@ -275,6 +279,11 @@ fun ChatRouterScreen(
|
||||||
var userContextSheetTarget by remember { mutableStateOf("") }
|
var userContextSheetTarget by remember { mutableStateOf("") }
|
||||||
var userContextSheetServer by remember { mutableStateOf<String?>(null) }
|
var userContextSheetServer by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
var showChannelUnavailableAlert by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
var showLinkInfoSheet by remember { mutableStateOf(false) }
|
||||||
|
var linkInfoSheetUrl by remember { mutableStateOf("") }
|
||||||
|
|
||||||
var useTabletAwareUI by remember { mutableStateOf(false) }
|
var useTabletAwareUI by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val drawerBackHandler = remember {
|
val drawerBackHandler = remember {
|
||||||
|
|
@ -359,6 +368,27 @@ fun ChatRouterScreen(
|
||||||
userContextSheetServer = action.serverId
|
userContextSheetServer = action.serverId
|
||||||
showUserContextSheet = true
|
showUserContextSheet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is Action.SwitchChannel -> {
|
||||||
|
val resolvedChannel = RevoltAPI.channelCache[action.channelId]
|
||||||
|
|
||||||
|
if (resolvedChannel == null) {
|
||||||
|
showChannelUnavailableAlert = true
|
||||||
|
return@let
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.navigateToChannel(action.channelId, navController)
|
||||||
|
if (resolvedChannel.server != null) {
|
||||||
|
viewModel.navigateToServer(resolvedChannel.server, navController)
|
||||||
|
} else {
|
||||||
|
viewModel.navigateToServer("home", navController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is Action.LinkInfo -> {
|
||||||
|
linkInfoSheetUrl = action.url
|
||||||
|
showLinkInfoSheet = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -508,6 +538,60 @@ fun ChatRouterScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showChannelUnavailableAlert) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
showChannelUnavailableAlert = false
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_lock_alert_24dp),
|
||||||
|
contentDescription = null, // decorative
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.channel_link_invalid),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.channel_link_invalid_description),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
showChannelUnavailableAlert = false
|
||||||
|
}) {
|
||||||
|
Text(text = stringResource(id = R.string.ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showLinkInfoSheet) {
|
||||||
|
val linkInfoSheetState = rememberModalBottomSheetState()
|
||||||
|
|
||||||
|
ModalBottomSheet(
|
||||||
|
sheetState = linkInfoSheetState,
|
||||||
|
onDismissRequest = {
|
||||||
|
showLinkInfoSheet = false
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
LinkInfoSheet(
|
||||||
|
url = linkInfoSheetUrl,
|
||||||
|
onDismiss = {
|
||||||
|
showLinkInfoSheet = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
package chat.revolt.sheets
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
|
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.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import chat.revolt.R
|
||||||
|
import chat.revolt.components.generic.SheetClickable
|
||||||
|
import chat.revolt.internals.Platform
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LinkInfoSheet(url: String, onDismiss: () -> Unit) {
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(0.dp))
|
||||||
|
.clickable(onClick = {
|
||||||
|
if (url.startsWith("revolt-android://")) return@clickable
|
||||||
|
|
||||||
|
val customTab = CustomTabsIntent
|
||||||
|
.Builder()
|
||||||
|
.setShowTitle(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
customTab.launchUrl(context, Uri.parse(url))
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = url,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
SheetClickable(
|
||||||
|
icon = { modifier ->
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_content_copy_24dp),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = { style ->
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.copy),
|
||||||
|
style = style
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
clipboardManager.setText(AnnotatedString(url))
|
||||||
|
if (Platform.needsShowClipboardNotification()) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.copied),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="M10 17C11.1 17 12 16.1 12 15C12 13.9 11.1 13 10 13C8.9 13 8 13.9 8 15S8.9 17 10 17M16 8C17.1 8 18 8.9 18 10V20C18 21.1 17.1 22 16 22H4C2.9 22 2 21.1 2 20V10C2 8.9 2.9 8 4 8H5V6C5 3.2 7.2 1 10 1S15 3.2 15 6V8H16M10 3C8.3 3 7 4.3 7 6V8H13V6C13 4.3 11.7 3 10 3M22 13H20V7H22V13M22 17H20V15H22V17Z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -184,6 +184,9 @@
|
||||||
<string name="copy">Copy</string>
|
<string name="copy">Copy</string>
|
||||||
<string name="copied">Copied to clipboard</string>
|
<string name="copied">Copied to clipboard</string>
|
||||||
|
|
||||||
|
<string name="channel_link_invalid">You can\'t view this channel</string>
|
||||||
|
<string name="channel_link_invalid_description">This channel may have been deleted or you may not have permission to view it.</string>
|
||||||
|
|
||||||
<string name="channel_info_sheet_description">Channel description</string>
|
<string name="channel_info_sheet_description">Channel description</string>
|
||||||
<string name="channel_info_sheet_description_empty">There hasn\'t been a description set for this channel yet.</string>
|
<string name="channel_info_sheet_description_empty">There hasn\'t been a description set for this channel yet.</string>
|
||||||
<string name="channel_info_sheet_options">Options</string>
|
<string name="channel_info_sheet_options">Options</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue