Merge pull request #1959 from vitorpamplona/claude/auto-load-opengraph-bookmarks-XGj82

Add Open Graph preview fetching to web bookmarks dialog
This commit is contained in:
Vitor Pamplona
2026-03-27 09:57:24 -04:00
committed by GitHub
@@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
@@ -39,6 +40,7 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.OpenInBrowser import androidx.compose.material.icons.filled.OpenInBrowser
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@@ -52,9 +54,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -65,6 +69,7 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
import com.vitorpamplona.amethyst.ui.feeds.RenderFeedContentState import com.vitorpamplona.amethyst.ui.feeds.RenderFeedContentState
import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys
@@ -80,6 +85,7 @@ import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
import com.vitorpamplona.quartz.nipB0WebBookmarks.WebBookmarkEvent import com.vitorpamplona.quartz.nipB0WebBookmarks.WebBookmarkEvent
import kotlinx.coroutines.launch
@Composable @Composable
fun WebBookmarksScreen( fun WebBookmarksScreen(
@@ -102,6 +108,7 @@ private fun RenderWebBookmarksScreen(
if (showAddDialog) { if (showAddDialog) {
WebBookmarkEditDialog( WebBookmarkEditDialog(
accountViewModel = accountViewModel,
onDismiss = { showAddDialog = false }, onDismiss = { showAddDialog = false },
onSave = { url, title, description, tags -> onSave = { url, title, description, tags ->
accountViewModel.launchSigner { accountViewModel.launchSigner {
@@ -193,6 +200,7 @@ private fun WebBookmarkCard(
if (showEditDialog) { if (showEditDialog) {
WebBookmarkEditDialog( WebBookmarkEditDialog(
accountViewModel = accountViewModel,
initialUrl = event.url(), initialUrl = event.url(),
initialTitle = event.title() ?: "", initialTitle = event.title() ?: "",
initialDescription = event.description(), initialDescription = event.description(),
@@ -312,6 +320,7 @@ private fun WebBookmarkCard(
@Composable @Composable
fun WebBookmarkEditDialog( fun WebBookmarkEditDialog(
accountViewModel: AccountViewModel,
initialUrl: String = "", initialUrl: String = "",
initialTitle: String = "", initialTitle: String = "",
initialDescription: String = "", initialDescription: String = "",
@@ -323,8 +332,38 @@ fun WebBookmarkEditDialog(
var title by remember { mutableStateOf(initialTitle) } var title by remember { mutableStateOf(initialTitle) }
var description by remember { mutableStateOf(initialDescription) } var description by remember { mutableStateOf(initialDescription) }
var tags by remember { mutableStateOf(initialTags) } var tags by remember { mutableStateOf(initialTags) }
var isLoadingPreview by remember { mutableStateOf(false) }
var lastFetchedUrl by remember { mutableStateOf(initialUrl) }
val isEditing = initialUrl.isNotEmpty() val isEditing = initialUrl.isNotEmpty()
val scope = rememberCoroutineScope()
fun fetchOpenGraphData(urlToFetch: String) {
if (urlToFetch.isBlank() || urlToFetch == lastFetchedUrl) return
val normalizedUrl = if (!urlToFetch.startsWith("http")) "https://$urlToFetch" else urlToFetch
lastFetchedUrl = urlToFetch
isLoadingPreview = true
accountViewModel.urlPreview(normalizedUrl) { state ->
scope.launch {
when (state) {
is UrlPreviewState.Loaded -> {
val info = state.previewInfo
if (title.isBlank() && info.title.isNotBlank()) {
title = info.title
}
if (description.isBlank() && info.description.isNotBlank()) {
description = info.description
}
}
else -> {}
}
isLoadingPreview = false
}
}
}
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
@@ -343,7 +382,22 @@ fun WebBookmarkEditDialog(
label = { Text(stringResource(R.string.web_bookmark_url_label)) }, label = { Text(stringResource(R.string.web_bookmark_url_label)) },
placeholder = { Text(stringResource(R.string.web_bookmark_url_placeholder)) }, placeholder = { Text(stringResource(R.string.web_bookmark_url_placeholder)) },
singleLine = true, singleLine = true,
modifier = Modifier.fillMaxWidth(), modifier =
Modifier
.fillMaxWidth()
.onFocusChanged { focusState ->
if (!focusState.isFocused && url.isNotBlank()) {
fetchOpenGraphData(url)
}
},
trailingIcon = {
if (isLoadingPreview) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
strokeWidth = 2.dp,
)
}
},
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))