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