diff --git a/app/src/main/java/com/manalejandro/myactivitypub/ui/components/StatusCard.kt b/app/src/main/java/com/manalejandro/myactivitypub/ui/components/StatusCard.kt index 59d05f7..2931edc 100644 --- a/app/src/main/java/com/manalejandro/myactivitypub/ui/components/StatusCard.kt +++ b/app/src/main/java/com/manalejandro/myactivitypub/ui/components/StatusCard.kt @@ -269,13 +269,13 @@ private fun formatCount(count: Int): String { * Parse HTML content and create AnnotatedString with clickable links */ @Composable -private fun parseHtmlContent(html: String, linkColor: Color) = buildAnnotatedString { +fun parseHtmlContent(html: String, linkColor: Color) = buildAnnotatedString { // Convert HTML to plain text but extract links val plainText = Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT).toString() // Regex patterns for URLs and hashtags val urlPattern = Regex("""https?://[^\s<>"{}|\\^`\[\]]+""") - val hashtagPattern = Regex("""(^|\s)#(\w+)""") + val hashtagPattern = Regex("""#(\w+)""") // Match # and word, simpler pattern val allMatches = mutableListOf>() @@ -286,11 +286,10 @@ private fun parseHtmlContent(html: String, linkColor: Color) = buildAnnotatedStr // Find all hashtags hashtagPattern.findAll(plainText).forEach { match -> - val hashtagRange = match.groups[2]?.range - val hashtagValue = match.groups[2]?.value - if (hashtagRange != null && hashtagValue != null) { - // Don't add # prefix as it's already in the text - allMatches.add(hashtagRange to hashtagValue) + val fullHashtagRange = match.range // Include the # in the range + val hashtagValue = match.groups[1]?.value // Just the word without # + if (hashtagValue != null) { + allMatches.add(fullHashtagRange to hashtagValue) } } @@ -314,7 +313,12 @@ private fun parseHtmlContent(html: String, linkColor: Color) = buildAnnotatedStr ) { val tag = if (value.startsWith("http")) "URL" else "HASHTAG" pushStringAnnotation(tag = tag, annotation = value) - append(if (value.startsWith("http")) plainText.substring(range) else "#${value}") + // For URLs, use the full text from range; for hashtags, add # prefix to the value + if (value.startsWith("http")) { + append(plainText.substring(range)) + } else { + append("#$value") + } pop() } diff --git a/app/src/main/java/com/manalejandro/myactivitypub/ui/screens/StatusDetailScreen.kt b/app/src/main/java/com/manalejandro/myactivitypub/ui/screens/StatusDetailScreen.kt index 50467b8..5699e96 100644 --- a/app/src/main/java/com/manalejandro/myactivitypub/ui/screens/StatusDetailScreen.kt +++ b/app/src/main/java/com/manalejandro/myactivitypub/ui/screens/StatusDetailScreen.kt @@ -1,10 +1,13 @@ package com.manalejandro.myactivitypub.ui.screens +import android.content.Intent +import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.text.ClickableText import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.* @@ -15,12 +18,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import com.manalejandro.myactivitypub.data.models.Status import com.manalejandro.myactivitypub.data.repository.MastodonRepository +import com.manalejandro.myactivitypub.ui.components.parseHtmlContent import com.manalejandro.myactivitypub.ui.viewmodel.StatusDetailViewModel import java.text.SimpleDateFormat import java.util.* @@ -113,6 +118,7 @@ fun StatusDetailScreen( onReplyClick = onReplyClick, onBoostClick = { viewModel.toggleBoost() }, onFavoriteClick = { viewModel.toggleFavorite() }, + onHashtagClick = onHashtagClick, isAuthenticated = isAuthenticated ) @@ -169,6 +175,7 @@ private fun DetailedStatusCard( onReplyClick: (Status) -> Unit, onBoostClick: (Status) -> Unit, onFavoriteClick: (Status) -> Unit, + onHashtagClick: (String) -> Unit, isAuthenticated: Boolean ) { var selectedMedia by remember { mutableStateOf(null) } @@ -224,13 +231,28 @@ private fun DetailedStatusCard( Spacer(modifier = Modifier.height(16.dp)) - // Content - Text( - text = android.text.Html.fromHtml( - status.content, - android.text.Html.FROM_HTML_MODE_COMPACT - ).toString(), - style = MaterialTheme.typography.bodyLarge + // Content with clickable links and hashtags + val context = LocalContext.current + val annotatedContent = parseHtmlContent(status.content, MaterialTheme.colorScheme.primary) + + @Suppress("DEPRECATION") + ClickableText( + text = annotatedContent, + style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface), + onClick = { offset -> + // Check for URL + annotatedContent.getStringAnnotations(tag = "URL", start = offset, end = offset) + .firstOrNull()?.let { annotation -> + // Open URL in browser + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(annotation.item)) + context.startActivity(intent) + } + // Check for hashtag + annotatedContent.getStringAnnotations(tag = "HASHTAG", start = offset, end = offset) + .firstOrNull()?.let { annotation -> + onHashtagClick(annotation.item) + } + } ) // Media attachments