Private
Public Access
1
0

Links in message bubbles

This commit is contained in:
2024-03-23 20:22:05 -07:00
parent f266e04895
commit 3d165b6acd
3 changed files with 55 additions and 3 deletions

View File

@@ -6,7 +6,10 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.cache
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.buzzert.kordophone.backend.model.Conversation import net.buzzert.kordophone.backend.model.Conversation
@@ -29,6 +32,7 @@ class ConversationListViewModel @Inject constructor(
) : ViewModel() { ) : ViewModel() {
val conversations: Flow<List<Conversation>> val conversations: Flow<List<Conversation>>
get() = chatRepository.conversationChanges get() = chatRepository.conversationChanges
.shareIn(viewModelScope, started = SharingStarted.WhileSubscribed())
.map { .map {
it.sortedBy { it.date } it.sortedBy { it.date }
.reversed() .reversed()

View File

@@ -19,6 +19,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold import androidx.compose.material.Scaffold
@@ -37,6 +38,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@@ -58,6 +60,8 @@ import net.buzzert.kordophonedroid.ui.Destination
import net.buzzert.kordophonedroid.ui.LocalNavController import net.buzzert.kordophonedroid.ui.LocalNavController
import net.buzzert.kordophonedroid.ui.attachments.AttachmentFetchData import net.buzzert.kordophonedroid.ui.attachments.AttachmentFetchData
import net.buzzert.kordophonedroid.ui.attachments.AttachmentViewModel import net.buzzert.kordophonedroid.ui.attachments.AttachmentViewModel
import net.buzzert.kordophonedroid.ui.shared.LINK_ANNOTATION_TAG
import net.buzzert.kordophonedroid.ui.shared.linkify
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
@@ -317,16 +321,27 @@ fun MessageBubble(
) { ) {
val backgroundBubbleColor = if (mine) MaterialTheme.colors.primary else MaterialTheme.colors.secondary val backgroundBubbleColor = if (mine) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
// Linkify text
val annotatedString = text.linkify()
val urlHandler = LocalUriHandler.current
BubbleScaffold(mine = mine, modifier = modifier) { BubbleScaffold(mine = mine, modifier = modifier) {
Surface( Surface(
color = backgroundBubbleColor, color = backgroundBubbleColor,
shape = if (mine) OutgoingChatBubbleShape else IncomingChatBubbleShape, shape = if (mine) OutgoingChatBubbleShape else IncomingChatBubbleShape,
) { ) {
Text( ClickableText(
text = text, text = annotatedString,
style = MaterialTheme.typography.body2, style = MaterialTheme.typography.body2,
modifier = Modifier modifier = Modifier
.padding(12.dp) .padding(12.dp),
onClick = { index ->
annotatedString
.getStringAnnotations(LINK_ANNOTATION_TAG, index, index)
.firstOrNull()?.let {
urlHandler.openUri(it.item)
}
}
) )
} }
} }

View File

@@ -0,0 +1,33 @@
package net.buzzert.kordophonedroid.ui.shared
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
const val LINK_ANNOTATION_TAG = "link"
private val LINK_REGEX = "(https?)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]".toRegex()
fun String.linkify(): AnnotatedString {
val text = this
val matches = LINK_REGEX.findAll(this)
return buildAnnotatedString {
append(text)
for (match in matches) {
val range = match.range.also {
// Make inclusive.
IntRange(it.first, it.last + 1)
}
// Annotate link
addStringAnnotation(LINK_ANNOTATION_TAG, match.value, range.first, range.last)
// Add style
addStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
start = range.first, end = range.last
)
}
}
}