mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 01:07:46 +00:00
UrlPreviewer using Main thread :(
This commit is contained in:
@@ -3,10 +3,6 @@ package com.vitorpamplona.amethyst.model
|
||||
import com.vitorpamplona.amethyst.service.previews.BahaUrlPreview
|
||||
import com.vitorpamplona.amethyst.service.previews.IUrlPreviewCallback
|
||||
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object UrlCachedPreviewer {
|
||||
var cache = mapOf<String, UrlInfoItem>()
|
||||
@@ -25,22 +21,19 @@ object UrlCachedPreviewer {
|
||||
return
|
||||
}
|
||||
|
||||
val scope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
scope.launch {
|
||||
BahaUrlPreview(
|
||||
url,
|
||||
object : IUrlPreviewCallback {
|
||||
override fun onComplete(urlInfo: UrlInfoItem) {
|
||||
cache = cache + Pair(url, urlInfo)
|
||||
callback?.onComplete(urlInfo)
|
||||
}
|
||||
|
||||
override fun onFailed(throwable: Throwable) {
|
||||
failures = failures + Pair(url, throwable)
|
||||
callback?.onFailed(throwable)
|
||||
}
|
||||
BahaUrlPreview(
|
||||
url,
|
||||
object : IUrlPreviewCallback {
|
||||
override fun onComplete(urlInfo: UrlInfoItem) {
|
||||
cache = cache + Pair(url, urlInfo)
|
||||
callback?.onComplete(urlInfo)
|
||||
}
|
||||
).fetchUrlPreview()
|
||||
}
|
||||
|
||||
override fun onFailed(throwable: Throwable) {
|
||||
failures = failures + Pair(url, throwable)
|
||||
callback?.onFailed(throwable)
|
||||
}
|
||||
}
|
||||
).fetchUrlPreview()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
|
||||
class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) {
|
||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||
val scope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
private val imageExtensionArray = arrayOf(".gif", ".png", ".jpg", ".jpeg", ".bmp", ".webp")
|
||||
|
||||
fun fetchUrlPreview(timeOut: Int = 30000) {
|
||||
@@ -23,8 +23,7 @@ class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) {
|
||||
urlInfoItem = UrlInfoItem(url = url, image = url)
|
||||
} else {
|
||||
val document = getDocument(url, timeOut)
|
||||
urlInfoItem = parseHtml(document)
|
||||
urlInfoItem.url = url
|
||||
urlInfoItem = parseHtml(url, document)
|
||||
}
|
||||
callback?.onComplete(urlInfoItem)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
package com.vitorpamplona.amethyst.service.previews
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import java.net.URL
|
||||
|
||||
@Immutable
|
||||
data class UrlInfoItem(
|
||||
var url: String = "",
|
||||
var title: String = "",
|
||||
var description: String = "",
|
||||
var image: String = ""
|
||||
val url: String = "",
|
||||
val title: String = "",
|
||||
val description: String = "",
|
||||
val image: String = ""
|
||||
) {
|
||||
val verifiedUrl = kotlin.runCatching { URL(url) }.getOrNull()
|
||||
val imageUrlFullPath =
|
||||
if (image.startsWith("/")) {
|
||||
URL(verifiedUrl, image).toString()
|
||||
} else {
|
||||
image
|
||||
}
|
||||
|
||||
fun allFetchComplete(): Boolean {
|
||||
return title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty()
|
||||
}
|
||||
|
||||
@@ -67,50 +67,47 @@ suspend fun getDocument(url: String, timeOut: Int = 30000): Document =
|
||||
.get()
|
||||
}
|
||||
|
||||
suspend fun parseHtml(document: Document): UrlInfoItem =
|
||||
suspend fun parseHtml(url: String, document: Document): UrlInfoItem =
|
||||
withContext(Dispatchers.IO) {
|
||||
val metaTags = document.getElementsByTag(ELEMENT_TAG_META)
|
||||
val urlInfo = UrlInfoItem()
|
||||
|
||||
var title: String = ""
|
||||
var description: String = ""
|
||||
var image: String = ""
|
||||
|
||||
metaTags.forEach {
|
||||
val propertyTag = it.attr(ATTRIBUTE_VALUE_PROPERTY)
|
||||
when (propertyTag) {
|
||||
in META_OG_TITLE -> if (urlInfo.title.isEmpty()) urlInfo.title = it.attr(CONTENT)
|
||||
in META_OG_DESCRIPTION -> if (urlInfo.description.isEmpty()) {
|
||||
urlInfo.description =
|
||||
it.attr(CONTENT)
|
||||
in META_OG_TITLE -> if (title.isEmpty()) title = it.attr(CONTENT)
|
||||
in META_OG_DESCRIPTION -> if (description.isEmpty()) {
|
||||
description = it.attr(CONTENT)
|
||||
}
|
||||
in META_OG_IMAGE -> if (urlInfo.image.isEmpty()) urlInfo.image = it.attr(CONTENT)
|
||||
in META_OG_IMAGE -> if (image.isEmpty()) image = it.attr(CONTENT)
|
||||
}
|
||||
|
||||
when (it.attr(ATTRIBUTE_VALUE_NAME)) {
|
||||
in META_NAME_TITLE -> if (urlInfo.title.isEmpty()) urlInfo.title = it.attr(CONTENT)
|
||||
in META_NAME_DESCRIPTION -> if (urlInfo.description.isEmpty()) {
|
||||
urlInfo.description =
|
||||
it.attr(CONTENT)
|
||||
in META_NAME_TITLE -> if (title.isEmpty()) title = it.attr(CONTENT)
|
||||
in META_NAME_DESCRIPTION -> if (description.isEmpty()) {
|
||||
description = it.attr(CONTENT)
|
||||
}
|
||||
in META_OG_IMAGE -> if (urlInfo.image.isEmpty()) urlInfo.image = it.attr(CONTENT)
|
||||
in META_OG_IMAGE -> if (image.isEmpty()) image = it.attr(CONTENT)
|
||||
}
|
||||
|
||||
when (it.attr(ATTRIBUTE_VALUE_ITEMPROP)) {
|
||||
in META_ITEMPROP_TITLE -> if (urlInfo.title.isEmpty()) {
|
||||
urlInfo.title =
|
||||
it.attr(CONTENT)
|
||||
in META_ITEMPROP_TITLE -> if (title.isEmpty()) {
|
||||
title = it.attr(CONTENT)
|
||||
}
|
||||
in META_ITEMPROP_DESCRIPTION -> if (urlInfo.description.isEmpty()) {
|
||||
urlInfo.description =
|
||||
it.attr(
|
||||
CONTENT
|
||||
)
|
||||
in META_ITEMPROP_DESCRIPTION -> if (description.isEmpty()) {
|
||||
description = it.attr(CONTENT)
|
||||
}
|
||||
in META_ITEMPROP_IMAGE -> if (urlInfo.image.isEmpty()) {
|
||||
urlInfo.image = it.attr(
|
||||
CONTENT
|
||||
)
|
||||
in META_ITEMPROP_IMAGE -> if (image.isEmpty()) {
|
||||
image = it.attr(CONTENT)
|
||||
}
|
||||
}
|
||||
if (urlInfo.allFetchComplete()) {
|
||||
return@withContext urlInfo
|
||||
|
||||
if (title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty()) {
|
||||
return@withContext UrlInfoItem(url, title, description, image)
|
||||
}
|
||||
}
|
||||
return@withContext urlInfo
|
||||
return@withContext UrlInfoItem(url, title, description, image)
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ private fun RenderSeach(
|
||||
state = listState
|
||||
) {
|
||||
itemsIndexed(
|
||||
searchBarViewModel.searchResults.value,
|
||||
searchBarViewModel.searchResultsUsers.value,
|
||||
key = { _, item -> "u" + item.pubkeyHex }
|
||||
) { _, item ->
|
||||
UserComposeForChat(
|
||||
|
||||
@@ -489,15 +489,15 @@ fun InLineIconRenderer(
|
||||
"inlineContent$idx",
|
||||
InlineTextContent(
|
||||
Placeholder(
|
||||
width = 17.sp,
|
||||
height = 17.sp,
|
||||
width = 20.sp,
|
||||
height = 20.sp,
|
||||
placeholderVerticalAlign = PlaceholderVerticalAlign.Center
|
||||
)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = value.url,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxSize().padding(1.dp)
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 1.dp)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.vitorpamplona.amethyst.R
|
||||
@@ -32,12 +31,11 @@ fun UrlPreview(url: String, urlText: String) {
|
||||
|
||||
mutableStateOf(default)
|
||||
}
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// Doesn't use a viewModel because of viewModel reusing issues (too many UrlPreview are created).
|
||||
LaunchedEffect(url) {
|
||||
if (urlPreviewState == UrlPreviewState.Loading) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
launch(Dispatchers.IO) {
|
||||
UrlCachedPreviewer.previewInfo(
|
||||
url,
|
||||
object : IUrlPreviewCallback {
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -22,7 +21,6 @@ import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
||||
import java.net.URL
|
||||
|
||||
@Composable
|
||||
fun UrlPreviewCard(
|
||||
@@ -42,26 +40,15 @@ fun UrlPreviewCard(
|
||||
)
|
||||
) {
|
||||
Column {
|
||||
val validatedUrl = remember(url) { URL(previewInfo.url) }
|
||||
|
||||
// correctly treating relative images
|
||||
val imageUrl = remember(url) {
|
||||
if (previewInfo.image.startsWith("/")) {
|
||||
URL(validatedUrl, previewInfo.image).toString()
|
||||
} else {
|
||||
previewInfo.image
|
||||
}
|
||||
}
|
||||
|
||||
AsyncImage(
|
||||
model = imageUrl,
|
||||
contentDescription = stringResource(R.string.preview_card_image_for, validatedUrl),
|
||||
model = previewInfo.imageUrlFullPath,
|
||||
contentDescription = stringResource(R.string.preview_card_image_for, previewInfo.url),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Text(
|
||||
text = validatedUrl.host,
|
||||
text = previewInfo.verifiedUrl?.host ?: previewInfo.url,
|
||||
style = MaterialTheme.typography.caption,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
||||
|
||||
@Immutable
|
||||
sealed class UrlPreviewState {
|
||||
object Loading : UrlPreviewState()
|
||||
class Loaded(val previewInfo: UrlInfoItem) : UrlPreviewState()
|
||||
|
||||
@@ -36,6 +36,7 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Report
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -82,11 +83,13 @@ import net.engawapg.lib.zoomable.zoomable
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
|
||||
@Immutable
|
||||
abstract class ZoomableContent(
|
||||
val description: String? = null,
|
||||
val dim: String? = null
|
||||
)
|
||||
|
||||
@Immutable
|
||||
abstract class ZoomableUrlContent(
|
||||
val url: String,
|
||||
description: String? = null,
|
||||
@@ -95,6 +98,7 @@ abstract class ZoomableUrlContent(
|
||||
val uri: String? = null
|
||||
) : ZoomableContent(description, dim)
|
||||
|
||||
@Immutable
|
||||
class ZoomableUrlImage(
|
||||
url: String,
|
||||
description: String? = null,
|
||||
@@ -104,6 +108,7 @@ class ZoomableUrlImage(
|
||||
uri: String? = null
|
||||
) : ZoomableUrlContent(url, description, hash, dim, uri)
|
||||
|
||||
@Immutable
|
||||
class ZoomableUrlVideo(
|
||||
url: String,
|
||||
description: String? = null,
|
||||
@@ -112,6 +117,7 @@ class ZoomableUrlVideo(
|
||||
uri: String? = null
|
||||
) : ZoomableUrlContent(url, description, hash, dim, uri)
|
||||
|
||||
@Immutable
|
||||
abstract class ZoomablePreloadedContent(
|
||||
val localFile: File?,
|
||||
description: String? = null,
|
||||
@@ -121,6 +127,7 @@ abstract class ZoomablePreloadedContent(
|
||||
val uri: String
|
||||
) : ZoomableContent(description, dim)
|
||||
|
||||
@Immutable
|
||||
class ZoomableLocalImage(
|
||||
localFile: File?,
|
||||
mimeType: String? = null,
|
||||
@@ -131,6 +138,7 @@ class ZoomableLocalImage(
|
||||
uri: String
|
||||
) : ZoomablePreloadedContent(localFile, description, mimeType, isVerified, dim, uri)
|
||||
|
||||
@Immutable
|
||||
class ZoomableLocalVideo(
|
||||
localFile: File?,
|
||||
mimeType: String? = null,
|
||||
|
||||
@@ -78,6 +78,7 @@ import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.graphics.get
|
||||
import coil.compose.AsyncImage
|
||||
import coil.compose.AsyncImagePainter
|
||||
import coil.request.SuccessResult
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
import com.vitorpamplona.amethyst.NotificationCache
|
||||
import com.vitorpamplona.amethyst.R
|
||||
@@ -1679,19 +1680,24 @@ fun BadgeDisplay(baseNote: Note) {
|
||||
val background = MaterialTheme.colors.background
|
||||
val badgeData = baseNote.event as? BadgeDefinitionEvent ?: return
|
||||
|
||||
val image = remember { badgeData.image() }
|
||||
val image = remember { badgeData.thumb()?.ifBlank { null } ?: badgeData.image() }
|
||||
val name = remember { badgeData.name() }
|
||||
val description = remember { badgeData.description() }
|
||||
|
||||
var backgroundFromImage by remember { mutableStateOf(Pair(background, background)) }
|
||||
var imageResult by remember { mutableStateOf<AsyncImagePainter.State.Success?>(null) }
|
||||
var imageResult by remember { mutableStateOf<SuccessResult?>(null) }
|
||||
|
||||
LaunchedEffect(key1 = imageResult) {
|
||||
withContext(Dispatchers.IO) {
|
||||
launch(Dispatchers.IO) {
|
||||
imageResult?.let {
|
||||
val backgroundColor = it.result.drawable.toBitmap(200, 200).copy(Bitmap.Config.ARGB_8888, false).get(0, 199)
|
||||
val backgroundColor = it.drawable.toBitmap(200, 200).copy(Bitmap.Config.ARGB_8888, false).get(0, 199)
|
||||
val colorFromImage = Color(backgroundColor)
|
||||
val textBackground = if (colorFromImage.luminance() > 0.5) lightColors().onBackground else darkColors().onBackground
|
||||
val textBackground = if (colorFromImage.luminance() > 0.5) {
|
||||
lightColors().onBackground
|
||||
} else {
|
||||
darkColors().onBackground
|
||||
}
|
||||
|
||||
backgroundFromImage = Pair(colorFromImage, textBackground)
|
||||
}
|
||||
}
|
||||
@@ -1708,47 +1714,65 @@ fun BadgeDisplay(baseNote: Note) {
|
||||
)
|
||||
.background(backgroundFromImage.first)
|
||||
) {
|
||||
Column {
|
||||
image.let {
|
||||
AsyncImage(
|
||||
model = it,
|
||||
contentDescription = stringResource(
|
||||
R.string.badge_award_image_for,
|
||||
name ?: ""
|
||||
),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onSuccess = {
|
||||
imageResult = it
|
||||
}
|
||||
)
|
||||
RenderBadge(
|
||||
image,
|
||||
name,
|
||||
backgroundFromImage.second,
|
||||
description
|
||||
) {
|
||||
if (imageResult == null) {
|
||||
imageResult = it.result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.body1,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp, end = 10.dp),
|
||||
color = backgroundFromImage.second
|
||||
)
|
||||
}
|
||||
@Composable
|
||||
private fun RenderBadge(
|
||||
image: String?,
|
||||
name: String?,
|
||||
backgroundFromImage: Color,
|
||||
description: String?,
|
||||
onSuccess: (AsyncImagePainter.State.Success) -> Unit
|
||||
) {
|
||||
Column {
|
||||
image.let {
|
||||
AsyncImage(
|
||||
model = it,
|
||||
contentDescription = stringResource(
|
||||
R.string.badge_award_image_for,
|
||||
name ?: ""
|
||||
),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onSuccess = onSuccess
|
||||
)
|
||||
}
|
||||
|
||||
description?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
|
||||
color = Color.Gray,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
name?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.body1,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp, end = 10.dp),
|
||||
color = backgroundFromImage
|
||||
)
|
||||
}
|
||||
|
||||
description?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
|
||||
color = Color.Gray,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@ package com.vitorpamplona.amethyst.ui.note
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.model.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@@ -95,6 +95,7 @@ import com.vitorpamplona.amethyst.ui.screen.RelayFeedView
|
||||
import com.vitorpamplona.amethyst.ui.screen.RelayFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.UserFeedView
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -736,7 +737,7 @@ private fun DisplayBadges(
|
||||
if (list.isNullOrEmpty()) {
|
||||
null
|
||||
} else {
|
||||
list
|
||||
list.toImmutableList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -759,9 +760,8 @@ private fun LoadAndRenderBadge(badgeAwardEventHex: String, nav: (String) -> Unit
|
||||
mutableStateOf<Note?>(null)
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
LaunchedEffect(key1 = badgeAwardEventHex) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
launch(Dispatchers.IO) {
|
||||
baseNote = LocalCache.getOrCreateNote(badgeAwardEventHex)
|
||||
}
|
||||
}
|
||||
@@ -788,7 +788,7 @@ fun BadgeThumb(
|
||||
pictureModifier: Modifier = Modifier
|
||||
) {
|
||||
BadgeThumb(note, size, pictureModifier) {
|
||||
nav("Note/$it")
|
||||
nav("Note/${note.idHex}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -808,7 +808,7 @@ fun BadgeThumb(
|
||||
) {
|
||||
val noteState by baseNote.live().metadata.observeAsState()
|
||||
val event = remember(noteState) { noteState?.note?.event as? BadgeDefinitionEvent } ?: return
|
||||
val image = remember(noteState) { event.thumb() ?: event.image() }
|
||||
val image = remember(noteState) { event.thumb()?.ifBlank { null } ?: event.image()?.ifBlank { null } }
|
||||
|
||||
if (image == null) {
|
||||
RobohashAsyncImage(
|
||||
|
||||
@@ -122,6 +122,7 @@ fun SearchScreen(
|
||||
fun WatchAccountForSearchScreen(searchFeedViewModel: NostrGlobalFeedViewModel, accountViewModel: AccountViewModel) {
|
||||
LaunchedEffect(accountViewModel) {
|
||||
NostrGlobalDataSource.resetFilters()
|
||||
NostrSearchEventOrUserDataSource.start()
|
||||
searchFeedViewModel.invalidateData(true)
|
||||
}
|
||||
}
|
||||
@@ -130,7 +131,7 @@ class SearchBarViewModel : ViewModel() {
|
||||
var account: Account? = null
|
||||
|
||||
var searchValue by mutableStateOf("")
|
||||
val searchResults = mutableStateOf<List<User>>(emptyList())
|
||||
val searchResultsUsers = mutableStateOf<List<User>>(emptyList())
|
||||
val searchResultsNotes = mutableStateOf<List<Note>>(emptyList())
|
||||
val searchResultsChannels = mutableStateOf<List<Channel>>(emptyList())
|
||||
val hashtagResults = mutableStateOf<List<String>>(emptyList())
|
||||
@@ -147,21 +148,21 @@ class SearchBarViewModel : ViewModel() {
|
||||
private fun runSearch() {
|
||||
if (searchValue.isBlank()) {
|
||||
hashtagResults.value = emptyList()
|
||||
searchResults.value = emptyList()
|
||||
searchResultsUsers.value = emptyList()
|
||||
searchResultsChannels.value = emptyList()
|
||||
searchResultsNotes.value = emptyList()
|
||||
return
|
||||
}
|
||||
|
||||
hashtagResults.value = findHashtags(searchValue)
|
||||
searchResults.value = LocalCache.findUsersStartingWith(searchValue).sortedWith(compareBy({ account?.isFollowing(it) }, { it.toBestDisplayName() })).reversed()
|
||||
searchResultsUsers.value = LocalCache.findUsersStartingWith(searchValue).sortedWith(compareBy({ account?.isFollowing(it) }, { it.toBestDisplayName() })).reversed()
|
||||
searchResultsNotes.value = LocalCache.findNotesStartingWith(searchValue).sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
|
||||
searchResultsChannels.value = LocalCache.findChannelsStartingWith(searchValue)
|
||||
}
|
||||
|
||||
fun clean() {
|
||||
searchValue = ""
|
||||
searchResults.value = emptyList()
|
||||
searchResultsUsers.value = emptyList()
|
||||
searchResultsChannels.value = emptyList()
|
||||
searchResultsNotes.value = emptyList()
|
||||
}
|
||||
@@ -183,7 +184,7 @@ class SearchBarViewModel : ViewModel() {
|
||||
@Composable
|
||||
private fun SearchBar(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val searchBarViewModel: SearchBarViewModel = viewModel()
|
||||
searchBarViewModel.account = accountViewModel.accountLiveData.value?.account
|
||||
searchBarViewModel.account = accountViewModel.account
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val listState = rememberLazyListState()
|
||||
@@ -196,7 +197,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, nav: (String) -> Unit)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
launch(Dispatchers.IO) {
|
||||
LocalCache.live.newEventBundles.collect {
|
||||
if (searchBarViewModel.isSearching()) {
|
||||
searchBarViewModel.invalidateData()
|
||||
@@ -306,7 +307,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, nav: (String) -> Unit)
|
||||
}
|
||||
}
|
||||
|
||||
itemsIndexed(searchBarViewModel.searchResults.value, key = { _, item -> "u" + item.pubkeyHex }) { _, item ->
|
||||
itemsIndexed(searchBarViewModel.searchResultsUsers.value, key = { _, item -> "u" + item.pubkeyHex }) { _, item ->
|
||||
UserCompose(item, accountViewModel = accountViewModel, nav = nav)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user