Reducing recompositions on images.

This commit is contained in:
Vitor Pamplona
2023-05-31 21:32:46 -04:00
parent 8575ea018d
commit 6072e5977e
11 changed files with 105 additions and 104 deletions
@@ -145,7 +145,7 @@ private fun RenderSeach(
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
scope.launch(Dispatchers.IO) { launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect { LocalCache.live.newEventBundles.collect {
if (searchBarViewModel.isSearching()) { if (searchBarViewModel.isSearching()) {
searchBarViewModel.invalidateData() searchBarViewModel.invalidateData()
@@ -117,8 +117,6 @@ fun isNIP94Server(selectedServer: ServersAvailable?): Boolean {
@Composable @Composable
fun ImageVideoPost(postViewModel: NewMediaModel, acc: Account) { fun ImageVideoPost(postViewModel: NewMediaModel, acc: Account) {
val scope = rememberCoroutineScope()
val fileServers = listOf( val fileServers = listOf(
// Triple(ServersAvailable.IMGUR_NIP_94, stringResource(id = R.string.upload_server_imgur_nip94), stringResource(id = R.string.upload_server_imgur_nip94_explainer)), // Triple(ServersAvailable.IMGUR_NIP_94, stringResource(id = R.string.upload_server_imgur_nip94), stringResource(id = R.string.upload_server_imgur_nip94_explainer)),
Triple(ServersAvailable.NOSTRIMG_NIP_94, stringResource(id = R.string.upload_server_nostrimg_nip94), stringResource(id = R.string.upload_server_nostrimg_nip94_explainer)), Triple(ServersAvailable.NOSTRIMG_NIP_94, stringResource(id = R.string.upload_server_nostrimg_nip94), stringResource(id = R.string.upload_server_nostrimg_nip94_explainer)),
@@ -152,7 +150,7 @@ fun ImageVideoPost(postViewModel: NewMediaModel, acc: Account) {
var bitmap by remember { mutableStateOf<Bitmap?>(null) } var bitmap by remember { mutableStateOf<Bitmap?>(null) }
LaunchedEffect(key1 = postViewModel.galleryUri) { LaunchedEffect(key1 = postViewModel.galleryUri) {
scope.launch(Dispatchers.IO) { launch(Dispatchers.IO) {
postViewModel.galleryUri?.let { postViewModel.galleryUri?.let {
try { try {
bitmap = resolver.loadThumbnail(it, Size(1200, 1000), null) bitmap = resolver.loadThumbnail(it, Size(1200, 1000), null)
@@ -727,7 +727,6 @@ fun ImageVideoDescription(
) { ) {
val resolver = LocalContext.current.contentResolver val resolver = LocalContext.current.contentResolver
val mediaType = resolver.getType(uri) ?: "" val mediaType = resolver.getType(uri) ?: ""
val scope = rememberCoroutineScope()
val isImage = mediaType.startsWith("image") val isImage = mediaType.startsWith("image")
val isVideo = mediaType.startsWith("video") val isVideo = mediaType.startsWith("video")
@@ -830,7 +829,7 @@ fun ImageVideoDescription(
var bitmap by remember { mutableStateOf<Bitmap?>(null) } var bitmap by remember { mutableStateOf<Bitmap?>(null) }
LaunchedEffect(key1 = uri) { LaunchedEffect(key1 = uri) {
scope.launch(Dispatchers.IO) { launch(Dispatchers.IO) {
try { try {
bitmap = resolver.loadThumbnail(uri, Size(1200, 1000), null) bitmap = resolver.loadThumbnail(uri, Size(1200, 1000), null)
} catch (e: Exception) { } catch (e: Exception) {
@@ -46,6 +46,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.service.nip19.Nip19
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
@@ -132,7 +133,7 @@ private fun DisplayNote(
var noteBase by remember(nip19) { mutableStateOf<Note?>(null) } var noteBase by remember(nip19) { mutableStateOf<Note?>(null) }
LaunchedEffect(key1 = nip19.hex) { LaunchedEffect(key1 = nip19.hex) {
withContext(Dispatchers.IO) { launch(Dispatchers.IO) {
noteBase = LocalCache.checkGetOrCreateNote(nip19.hex) noteBase = LocalCache.checkGetOrCreateNote(nip19.hex)
} }
} }
@@ -188,7 +189,7 @@ private fun DisplayAddress(
var noteBase by remember(nip19) { mutableStateOf<Note?>(null) } var noteBase by remember(nip19) { mutableStateOf<Note?>(null) }
LaunchedEffect(key1 = nip19.hex) { LaunchedEffect(key1 = nip19.hex) {
withContext(Dispatchers.IO) { launch(Dispatchers.IO) {
noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex) noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex)
} }
} }
@@ -220,7 +221,7 @@ private fun DisplayUser(
var userBase by remember(nip19) { mutableStateOf<User?>(null) } var userBase by remember(nip19) { mutableStateOf<User?>(null) }
LaunchedEffect(key1 = nip19.hex) { LaunchedEffect(key1 = nip19.hex) {
withContext(Dispatchers.IO) { launch(Dispatchers.IO) {
userBase = LocalCache.checkGetOrCreateUser(nip19.hex) userBase = LocalCache.checkGetOrCreateUser(nip19.hex)
} }
} }
@@ -88,10 +88,9 @@ fun LoadThumbAndThenVideoView(videoUri: String, description: String? = null, thu
var loadingFinished by remember { mutableStateOf<Pair<Boolean, Drawable?>>(Pair(false, null)) } var loadingFinished by remember { mutableStateOf<Pair<Boolean, Drawable?>>(Pair(false, null)) }
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
scope.launch(Dispatchers.IO) { launch(Dispatchers.IO) {
try { try {
val request = ImageRequest.Builder(context).data(thumbUri).build() val request = ImageRequest.Builder(context).data(thumbUri).build()
val myCover = context.imageLoader.execute(request).drawable val myCover = context.imageLoader.execute(request).drawable
@@ -214,12 +214,12 @@ private fun LocalImageView(
content: ZoomableLocalImage, content: ZoomableLocalImage,
mainImageModifier: Modifier mainImageModifier: Modifier
) { ) {
// store the dialog open or close state
var imageState by remember {
mutableStateOf<AsyncImagePainter.State?>(null)
}
BoxWithConstraints(contentAlignment = Alignment.Center) { BoxWithConstraints(contentAlignment = Alignment.Center) {
// store the dialog open or close state
var imageLoadingState by remember {
mutableStateOf<Boolean?>(null)
}
val myModifier = remember { val myModifier = remember {
mainImageModifier mainImageModifier
.widthIn(max = maxWidth) .widthIn(max = maxWidth)
@@ -241,17 +241,22 @@ private fun LocalImageView(
contentDescription = content.description, contentDescription = content.description,
contentScale = contentScale, contentScale = contentScale,
modifier = myModifier, modifier = myModifier,
onLoading = { onError = {
imageState = it if (imageLoadingState != false)
imageLoadingState = false
}, },
onSuccess = { onSuccess = {
imageState = it if (imageLoadingState != true)
imageLoadingState = true
} }
) )
} }
if (imageState is AsyncImagePainter.State.Success) { if (imageLoadingState == true) {
HashVerificationSymbol(content.isVerified, Modifier.align(Alignment.TopEnd)) if (content.isVerified != null)
HashVerificationSymbol(content.isVerified, Modifier.align(Alignment.TopEnd))
} else if (imageLoadingState == false || content.localFile == null || !content.localFile.exists()) {
BlankNote()
} else { } else {
if (content.blurhash != null) { if (content.blurhash != null) {
DisplayBlurHash(content.blurhash, content.description, contentScale, myModifier) DisplayBlurHash(content.blurhash, content.description, contentScale, myModifier)
@@ -261,10 +266,6 @@ private fun LocalImageView(
} }
} }
} }
if (imageState is AsyncImagePainter.State.Error || content.localFile == null || !content.localFile.exists()) {
BlankNote()
}
} }
} }
@@ -273,27 +274,20 @@ private fun UrlImageView(
content: ZoomableUrlImage, content: ZoomableUrlImage,
mainImageModifier: Modifier mainImageModifier: Modifier
) { ) {
val scope = rememberCoroutineScope()
BoxWithConstraints(contentAlignment = Alignment.Center) { BoxWithConstraints(contentAlignment = Alignment.Center) {
val scope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
// store the dialog open or close state // store the dialog open or close state
var imageState by remember { var imageLoadingStatus by remember {
mutableStateOf<AsyncImagePainter.State?>(null) mutableStateOf<Boolean?>(null)
} }
var verifiedHash by remember { var verifiedHash by remember {
mutableStateOf<Boolean?>(null) mutableStateOf<Boolean?>(null)
} }
LaunchedEffect(key1 = content.url, key2 = imageState) {
if (imageState is AsyncImagePainter.State.Success) {
scope.launch(Dispatchers.IO) {
verifiedHash = verifyHash(content, context)
}
}
}
val myModifier = remember { val myModifier = remember {
mainImageModifier mainImageModifier
.widthIn(max = maxWidth) .widthIn(max = maxWidth)
@@ -314,16 +308,28 @@ private fun UrlImageView(
contentDescription = content.description, contentDescription = content.description,
contentScale = contentScale, contentScale = contentScale,
modifier = myModifier, modifier = myModifier,
onLoading = { onError = {
imageState = it if (imageLoadingStatus != false)
imageLoadingStatus = false
}, },
onSuccess = { onSuccess = {
imageState = it if (verifiedHash == null) {
scope.launch(Dispatchers.IO) {
verifiedHash = verifyHash(content, context)
}
}
if (imageLoadingStatus != true) {
imageLoadingStatus = true
}
} }
) )
if (imageState is AsyncImagePainter.State.Success) { if (imageLoadingStatus == true) {
HashVerificationSymbol(verifiedHash, Modifier.align(Alignment.TopEnd)) verifiedHash?.let {
HashVerificationSymbol(it, Modifier.align(Alignment.TopEnd))
}
} else if (imageLoadingStatus == false) {
ClickableUrl(urlText = "${content.url} ", url = content.url)
} else { } else {
if (content.blurhash != null) { if (content.blurhash != null) {
DisplayBlurHash(content.blurhash, content.description, contentScale, myModifier) DisplayBlurHash(content.blurhash, content.description, contentScale, myModifier)
@@ -333,10 +339,6 @@ private fun UrlImageView(
} }
} }
} }
if (imageState is AsyncImagePainter.State.Error) {
ClickableUrl(urlText = "${content.url} ", url = content.url)
}
} }
} }
@@ -420,10 +422,12 @@ private fun DisplayBlurHash(
val context = LocalContext.current val context = LocalContext.current
AsyncImage( AsyncImage(
model = BlurHashRequester.imageRequest( model = remember {
context, BlurHashRequester.imageRequest(
blurhash context,
), blurhash
)
},
contentDescription = description, contentDescription = description,
contentScale = contentScale, contentScale = contentScale,
modifier = modifier modifier = modifier
@@ -522,9 +526,7 @@ private fun verifyHash(content: ZoomableUrlContent, context: Context): Boolean?
} }
@Composable @Composable
private fun HashVerificationSymbol(verifiedHash: Boolean?, modifier: Modifier) { private fun HashVerificationSymbol(verifiedHash: Boolean, modifier: Modifier) {
if (verifiedHash == null) return
val localContext = LocalContext.current val localContext = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -110,8 +110,6 @@ private fun RowScope.HasNewItemsIcon(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavHostController navController: NavHostController
) { ) {
val scope = rememberCoroutineScope()
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return val account = remember(accountState) { accountState?.account } ?: return
@@ -121,7 +119,7 @@ private fun RowScope.HasNewItemsIcon(
var hasNewItems by remember { mutableStateOf<Boolean>(false) } var hasNewItems by remember { mutableStateOf<Boolean>(false) }
LaunchedEffect(key1 = notifState, key2 = accountState) { LaunchedEffect(key1 = notifState, key2 = accountState) {
scope.launch(Dispatchers.IO) { launch(Dispatchers.IO) {
val newHasNewItems = route.hasNewItems(account, notif, emptySet()) val newHasNewItems = route.hasNewItems(account, notif, emptySet())
if (newHasNewItems != hasNewItems) { if (newHasNewItems != hasNewItems) {
hasNewItems = newHasNewItems hasNewItems = newHasNewItems
@@ -129,8 +127,8 @@ private fun RowScope.HasNewItemsIcon(
} }
} }
LaunchedEffect(accountState) { LaunchedEffect(Unit) {
scope.launch(Dispatchers.IO) { launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect { LocalCache.live.newEventBundles.collect {
val newHasNewItems = route.hasNewItems(account, notif, it) val newHasNewItems = route.hasNewItems(account, notif, it)
if (newHasNewItems != hasNewItems) { if (newHasNewItems != hasNewItems) {
@@ -140,6 +138,8 @@ private fun RowScope.HasNewItemsIcon(
} }
} }
val scope = rememberCoroutineScope()
BottomIcon( BottomIcon(
icon = route.icon, icon = route.icon,
size = if ("Home" == route.base) 25.dp else 23.dp, size = if ("Home" == route.base) 25.dp else 23.dp,
@@ -51,7 +51,6 @@ fun AppNavigation(
nextPage: String? = null nextPage: String? = null
) { ) {
var actionableNextPage by remember { mutableStateOf<String?>(nextPage) } var actionableNextPage by remember { mutableStateOf<String?>(nextPage) }
val scope = rememberCoroutineScope()
val nav = remember { val nav = remember {
{ route: String -> { route: String ->
@@ -68,7 +67,7 @@ fun AppNavigation(
LaunchedEffect(key1 = it) { LaunchedEffect(key1 = it) {
if (scrollToTop) { if (scrollToTop) {
scope.launch { launch {
videoFeedViewModel.sendToTop() videoFeedViewModel.sendToTop()
it.arguments?.remove("scrollToTop") it.arguments?.remove("scrollToTop")
} }
@@ -89,7 +88,7 @@ fun AppNavigation(
LaunchedEffect(key1 = it) { LaunchedEffect(key1 = it) {
if (scrollToTop) { if (scrollToTop) {
scope.launch { launch {
searchFeedViewModel.sendToTop() searchFeedViewModel.sendToTop()
it.arguments?.remove("scrollToTop") it.arguments?.remove("scrollToTop")
} }
@@ -111,7 +110,7 @@ fun AppNavigation(
LaunchedEffect(key1 = it) { LaunchedEffect(key1 = it) {
if (scrollToTop) { if (scrollToTop) {
scope.launch { launch {
homeFeedViewModel.sendToTop() homeFeedViewModel.sendToTop()
repliesFeedViewModel.sendToTop() repliesFeedViewModel.sendToTop()
it.arguments?.remove("scrollToTop") it.arguments?.remove("scrollToTop")
@@ -129,7 +128,7 @@ fun AppNavigation(
if (nip47 != null) { if (nip47 != null) {
LaunchedEffect(key1 = Unit) { LaunchedEffect(key1 = Unit) {
scope.launch { launch {
delay(1000) delay(1000)
it.arguments?.remove("nip47") it.arguments?.remove("nip47")
} }
@@ -144,7 +143,7 @@ fun AppNavigation(
LaunchedEffect(key1 = it) { LaunchedEffect(key1 = it) {
if (scrollToTop) { if (scrollToTop) {
scope.launch { launch {
notifFeedViewModel.clear() notifFeedViewModel.clear()
notifFeedViewModel.sendToTop() notifFeedViewModel.sendToTop()
it.arguments?.remove("scrollToTop") it.arguments?.remove("scrollToTop")
@@ -44,7 +44,6 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -1785,18 +1785,20 @@ fun FileHeaderDisplay(note: Note) {
var content by remember { mutableStateOf<ZoomableContent?>(null) } var content by remember { mutableStateOf<ZoomableContent?>(null) }
LaunchedEffect(key1 = event.id) { LaunchedEffect(key1 = event.id) {
withContext(Dispatchers.IO) { if (content == null) {
val blurHash = event.blurhash() launch(Dispatchers.IO) {
val hash = event.hash() val blurHash = event.blurhash()
val dimensions = event.dimensions() val hash = event.hash()
val description = event.content val dimensions = event.dimensions()
val removedParamsFromUrl = fullUrl.split("?")[0].lowercase() val description = event.content
val isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) } val removedParamsFromUrl = fullUrl.split("?")[0].lowercase()
val uri = "nostr:" + note.toNEvent() val isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) }
content = if (isImage) { val uri = "nostr:" + note.toNEvent()
ZoomableUrlImage(fullUrl, description, hash, blurHash, dimensions, uri) content = if (isImage) {
} else { ZoomableUrlImage(fullUrl, description, hash, blurHash, dimensions, uri)
ZoomableUrlVideo(fullUrl, description, hash, uri) } else {
ZoomableUrlVideo(fullUrl, description, hash, uri)
}
} }
} }
} }
@@ -1814,7 +1816,7 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
var fileNote by remember { mutableStateOf<Note?>(null) } var fileNote by remember { mutableStateOf<Note?>(null) }
LaunchedEffect(key1 = eventHeader.id) { LaunchedEffect(key1 = eventHeader.id) {
withContext(Dispatchers.IO) { launch(Dispatchers.IO) {
fileNote = eventHeader.dataEventId()?.let { LocalCache.checkGetOrCreateNote(it) } fileNote = eventHeader.dataEventId()?.let { LocalCache.checkGetOrCreateNote(it) }
} }
} }
@@ -1826,33 +1828,35 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
var content by remember { mutableStateOf<ZoomableContent?>(null) } var content by remember { mutableStateOf<ZoomableContent?>(null) }
LaunchedEffect(key1 = eventHeader.id, key2 = noteState, key3 = note?.event) { LaunchedEffect(key1 = eventHeader.id, key2 = noteState, key3 = note?.event) {
withContext(Dispatchers.IO) { if (content == null) {
val uri = "nostr:" + baseNote.toNEvent() launch(Dispatchers.IO) {
val localDir = note?.idHex?.let { File(File(appContext.externalCacheDir, "NIP95"), it) } val uri = "nostr:" + baseNote.toNEvent()
val blurHash = eventHeader.blurhash() val localDir = note?.idHex?.let { File(File(appContext.externalCacheDir, "NIP95"), it) }
val dimensions = eventHeader.dimensions() val blurHash = eventHeader.blurhash()
val description = eventHeader.content val dimensions = eventHeader.dimensions()
val mimeType = eventHeader.mimeType() val description = eventHeader.content
val mimeType = eventHeader.mimeType()
content = if (mimeType?.startsWith("image") == true) { content = if (mimeType?.startsWith("image") == true) {
ZoomableLocalImage( ZoomableLocalImage(
localFile = localDir, localFile = localDir,
mimeType = mimeType, mimeType = mimeType,
description = description, description = description,
blurhash = blurHash, blurhash = blurHash,
dim = dimensions, dim = dimensions,
isVerified = true, isVerified = true,
uri = uri uri = uri
) )
} else { } else {
ZoomableLocalVideo( ZoomableLocalVideo(
localFile = localDir, localFile = localDir,
mimeType = mimeType, mimeType = mimeType,
description = description, description = description,
dim = dimensions, dim = dimensions,
isVerified = true, isVerified = true,
uri = uri uri = uri
) )
}
} }
} }
} }
@@ -87,7 +87,7 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
var zapAmount by remember { mutableStateOf<BigDecimal?>(null) } var zapAmount by remember { mutableStateOf<BigDecimal?>(null) }
LaunchedEffect(key1 = noteZap) { LaunchedEffect(key1 = noteZap) {
coroutineScope.launch(Dispatchers.IO) { launch(Dispatchers.IO) {
zapAmount = (noteZap.event as? LnZapEvent)?.amount zapAmount = (noteZap.event as? LnZapEvent)?.amount
} }
} }