@@ -269,13 +269,13 @@ private fun formatCount(count: Int): String {
|
|||||||
* Parse HTML content and create AnnotatedString with clickable links
|
* Parse HTML content and create AnnotatedString with clickable links
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun parseHtmlContent(html: String, linkColor: Color) = buildAnnotatedString {
|
fun parseHtmlContent(html: String, linkColor: Color) = buildAnnotatedString {
|
||||||
// Convert HTML to plain text but extract links
|
// Convert HTML to plain text but extract links
|
||||||
val plainText = Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT).toString()
|
val plainText = Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT).toString()
|
||||||
|
|
||||||
// Regex patterns for URLs and hashtags
|
// Regex patterns for URLs and hashtags
|
||||||
val urlPattern = Regex("""https?://[^\s<>"{}|\\^`\[\]]+""")
|
val urlPattern = Regex("""https?://[^\s<>"{}|\\^`\[\]]+""")
|
||||||
val hashtagPattern = Regex("""(^|\s)#(\w+)""")
|
val hashtagPattern = Regex("""#(\w+)""") // Match # and word, simpler pattern
|
||||||
|
|
||||||
val allMatches = mutableListOf<Pair<IntRange, String>>()
|
val allMatches = mutableListOf<Pair<IntRange, String>>()
|
||||||
|
|
||||||
@@ -286,11 +286,10 @@ private fun parseHtmlContent(html: String, linkColor: Color) = buildAnnotatedStr
|
|||||||
|
|
||||||
// Find all hashtags
|
// Find all hashtags
|
||||||
hashtagPattern.findAll(plainText).forEach { match ->
|
hashtagPattern.findAll(plainText).forEach { match ->
|
||||||
val hashtagRange = match.groups[2]?.range
|
val fullHashtagRange = match.range // Include the # in the range
|
||||||
val hashtagValue = match.groups[2]?.value
|
val hashtagValue = match.groups[1]?.value // Just the word without #
|
||||||
if (hashtagRange != null && hashtagValue != null) {
|
if (hashtagValue != null) {
|
||||||
// Don't add # prefix as it's already in the text
|
allMatches.add(fullHashtagRange to hashtagValue)
|
||||||
allMatches.add(hashtagRange to hashtagValue)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +313,12 @@ private fun parseHtmlContent(html: String, linkColor: Color) = buildAnnotatedStr
|
|||||||
) {
|
) {
|
||||||
val tag = if (value.startsWith("http")) "URL" else "HASHTAG"
|
val tag = if (value.startsWith("http")) "URL" else "HASHTAG"
|
||||||
pushStringAnnotation(tag = tag, annotation = value)
|
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()
|
pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package com.manalejandro.myactivitypub.ui.screens
|
package com.manalejandro.myactivitypub.ui.screens
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.text.ClickableText
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
@@ -15,12 +18,14 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.manalejandro.myactivitypub.data.models.Status
|
import com.manalejandro.myactivitypub.data.models.Status
|
||||||
import com.manalejandro.myactivitypub.data.repository.MastodonRepository
|
import com.manalejandro.myactivitypub.data.repository.MastodonRepository
|
||||||
|
import com.manalejandro.myactivitypub.ui.components.parseHtmlContent
|
||||||
import com.manalejandro.myactivitypub.ui.viewmodel.StatusDetailViewModel
|
import com.manalejandro.myactivitypub.ui.viewmodel.StatusDetailViewModel
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -113,6 +118,7 @@ fun StatusDetailScreen(
|
|||||||
onReplyClick = onReplyClick,
|
onReplyClick = onReplyClick,
|
||||||
onBoostClick = { viewModel.toggleBoost() },
|
onBoostClick = { viewModel.toggleBoost() },
|
||||||
onFavoriteClick = { viewModel.toggleFavorite() },
|
onFavoriteClick = { viewModel.toggleFavorite() },
|
||||||
|
onHashtagClick = onHashtagClick,
|
||||||
isAuthenticated = isAuthenticated
|
isAuthenticated = isAuthenticated
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -169,6 +175,7 @@ private fun DetailedStatusCard(
|
|||||||
onReplyClick: (Status) -> Unit,
|
onReplyClick: (Status) -> Unit,
|
||||||
onBoostClick: (Status) -> Unit,
|
onBoostClick: (Status) -> Unit,
|
||||||
onFavoriteClick: (Status) -> Unit,
|
onFavoriteClick: (Status) -> Unit,
|
||||||
|
onHashtagClick: (String) -> Unit,
|
||||||
isAuthenticated: Boolean
|
isAuthenticated: Boolean
|
||||||
) {
|
) {
|
||||||
var selectedMedia by remember { mutableStateOf<com.manalejandro.myactivitypub.data.models.MediaAttachment?>(null) }
|
var selectedMedia by remember { mutableStateOf<com.manalejandro.myactivitypub.data.models.MediaAttachment?>(null) }
|
||||||
@@ -224,13 +231,28 @@ private fun DetailedStatusCard(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
// Content
|
// Content with clickable links and hashtags
|
||||||
Text(
|
val context = LocalContext.current
|
||||||
text = android.text.Html.fromHtml(
|
val annotatedContent = parseHtmlContent(status.content, MaterialTheme.colorScheme.primary)
|
||||||
status.content,
|
|
||||||
android.text.Html.FROM_HTML_MODE_COMPACT
|
@Suppress("DEPRECATION")
|
||||||
).toString(),
|
ClickableText(
|
||||||
style = MaterialTheme.typography.bodyLarge
|
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
|
// Media attachments
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user