diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt index c609d58d4..5cd641bdf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt @@ -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() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index efd74fb55..969a075e9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -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(null) } LaunchedEffect(key1 = postViewModel.galleryUri) { - scope.launch(Dispatchers.IO) { + launch(Dispatchers.IO) { postViewModel.galleryUri?.let { try { bitmap = resolver.loadThumbnail(it, Size(1200, 1000), null) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 927667d90..1ea101d50 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -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(null) } LaunchedEffect(key1 = uri) { - scope.launch(Dispatchers.IO) { + launch(Dispatchers.IO) { try { bitmap = resolver.loadThumbnail(uri, Size(1200, 1000), null) } catch (e: Exception) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt index 15d560fe3..3a963cab0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt @@ -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(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(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(null) } LaunchedEffect(key1 = nip19.hex) { - withContext(Dispatchers.IO) { + launch(Dispatchers.IO) { userBase = LocalCache.checkGetOrCreateUser(nip19.hex) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index 6f78164d3..b51c28ecc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -88,10 +88,9 @@ fun LoadThumbAndThenVideoView(videoUri: String, description: String? = null, thu var loadingFinished by remember { mutableStateOf>(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 diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 79f89dffc..c3ca40e9b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -214,12 +214,12 @@ private fun LocalImageView( content: ZoomableLocalImage, mainImageModifier: Modifier ) { - // store the dialog open or close state - var imageState by remember { - mutableStateOf(null) - } - BoxWithConstraints(contentAlignment = Alignment.Center) { + // store the dialog open or close state + var imageLoadingState by remember { + mutableStateOf(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(null) + var imageLoadingStatus by remember { + mutableStateOf(null) } var verifiedHash by remember { mutableStateOf(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() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt index e09773d74..664311755 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt @@ -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(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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index ae940e7b7..b681aa763 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -51,7 +51,6 @@ fun AppNavigation( nextPage: String? = null ) { var actionableNextPage by remember { mutableStateOf(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") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt index dc4edaeda..0e3287ce5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt @@ -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() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 4b54b9485..578388d2e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -1785,18 +1785,20 @@ fun FileHeaderDisplay(note: Note) { var content by remember { mutableStateOf(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(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(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 + ) + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt index 759b3d846..cdf7d4865 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt @@ -87,7 +87,7 @@ fun ZapNoteCompose(baseNote: Pair, accountViewModel: AccountViewMode var zapAmount by remember { mutableStateOf(null) } LaunchedEffect(key1 = noteZap) { - coroutineScope.launch(Dispatchers.IO) { + launch(Dispatchers.IO) { zapAmount = (noteZap.event as? LnZapEvent)?.amount } }