diff --git a/.claude/skills/kotlin-expert/references/common-utilities.md b/.claude/skills/kotlin-expert/references/common-utilities.md index c2e0de633e..138cc44312 100644 --- a/.claude/skills/kotlin-expert/references/common-utilities.md +++ b/.claude/skills/kotlin-expert/references/common-utilities.md @@ -47,7 +47,6 @@ Under `quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/`: - **`commons/src/commonMain/.../threading/Threading.kt`** — shared dispatchers and `CoroutineScope` helpers for commonMain code. - **`amethyst/src/main/java/.../service/CoroutinesExt.kt`** — Android-only helpers: `launchIO(block)`, `launchMain(block)` built on top of `Dispatchers.IO` / `Dispatchers.Main`. Use these in ViewModels and services to stop re-spelling the dispatcher every time. -- **`amethyst/src/main/java/.../service/MainThreadChecker.kt`** — debug assertion helper for catching main-thread misuse during dev. ## Quartz iterables & JSON diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt index a0c3ec1830..4b97f1a2f8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt @@ -24,7 +24,6 @@ import com.vitorpamplona.amethyst.commons.model.cache.FileSystemNip95BlobStore import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.cache.LocalCacheHost import com.vitorpamplona.amethyst.commons.model.cache.Nip95BlobStore -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @@ -63,6 +62,4 @@ class AmethystLocalCacheHost( // The zap path's LNURL cache is a quartz-side singleton the outbound-zap resolver fills. override fun lnurlEndpoint(lnurlpUrl: String): LnurlEndpointInfo? = LnurlEndpointCache.get(lnurlpUrl) - - override fun assertNotMainThread() = checkNotInMainThread() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index aa9a38f897..5aaf087f22 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -64,7 +64,6 @@ import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthPolicy import com.vitorpamplona.amethyst.model.backups.BackupConflictStorage import com.vitorpamplona.amethyst.model.nip60Cashu.CashuPreferences import com.vitorpamplona.amethyst.model.preferences.UiSharedPreferences -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListEvent import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListEvent import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent @@ -674,17 +673,14 @@ object LocalPreferences { } } - private fun encryptedPreferences(npub: String? = null): SharedPreferences { - checkNotInMainThread() - - return if (BuildConfig.DEBUG && DEBUG_PLAINTEXT_PREFERENCES) { + private fun encryptedPreferences(npub: String? = null): SharedPreferences = + if (BuildConfig.DEBUG && DEBUG_PLAINTEXT_PREFERENCES) { val preferenceFile = if (npub == null) DEBUG_PREFERENCES_NAME else "${DEBUG_PREFERENCES_NAME}_$npub" Amethyst.instance.appContext.getSharedPreferences(preferenceFile, Context.MODE_PRIVATE) } else { Amethyst.instance.encryptedStorage(npub) } - } /** * Clears the preferences for a given npub, deletes the preferences xml file, and switches the diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/MainThreadChecker.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/MainThreadChecker.kt deleted file mode 100644 index 229a23f3db..0000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/MainThreadChecker.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2025 Vitor Pamplona - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.vitorpamplona.amethyst.service - -import android.os.Looper -import com.vitorpamplona.amethyst.BuildConfig - -fun checkNotInMainThread() { - if (BuildConfig.DEBUG && isMainThread()) { - throw OnMainThreadException("It should not be in the MainThread") - } -} - -fun isMainThread() = Looper.myLooper() == Looper.getMainLooper() - -class OnMainThreadException( - str: String, -) : RuntimeException(str) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/cashu/CashuParser.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/cashu/CashuParser.kt index 42f09e77dd..55ef6e04b6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/cashu/CashuParser.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/cashu/CashuParser.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.service.cashu import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.nip60Cashu.token.CashuToken import com.vitorpamplona.quartz.nip60Cashu.token.CashuTokenB64Parser import kotlinx.collections.immutable.ImmutableList @@ -34,8 +33,6 @@ import kotlinx.collections.immutable.toImmutableList */ class CashuParser { fun parse(cashuToken: String): GenericLoadable> { - checkNotInMainThread() - val parsed = CashuTokenB64Parser.parse(cashuToken) ?: return GenericLoadable.Error("Could not parse this cashu token") diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt index 1c8bb3e12a..c72093f6e8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt @@ -26,7 +26,6 @@ import android.net.Uri import androidx.core.net.toUri import androidx.media3.common.MimeTypes import com.davotoula.lightcompressor.video.GifToMp4Converter -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.components.util.MediaCompressorFileUtils import com.vitorpamplona.quartz.utils.Log import id.zelory.compressor.Compressor @@ -50,8 +49,6 @@ class MediaCompressor { useH265: Boolean = false, convertGifToMp4: Boolean = false, ): MediaCompressorResult { - checkNotInMainThread() - // Convert GIF to MP4 if requested. The GIF converter already produces a well-compressed // H.264 MP4 so no additional video compression step is needed. if (convertGifToMp4 && contentType?.contains("gif", ignoreCase = true) == true) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt index 3367d8b7ab..7656472d23 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt @@ -31,7 +31,6 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_to_server_w import com.vitorpamplona.amethyst.commons.service.HttpStatusMessages import com.vitorpamplona.amethyst.commons.service.upload.BlossomPaymentException import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult import com.vitorpamplona.amethyst.service.uploads.PreviewMetadataCalculator import com.vitorpamplona.amethyst.service.uploads.extensionFromMimeType @@ -86,8 +85,6 @@ class BlossomUploader { useMediaEndpoint: Boolean = false, onProgress: ((bytesWritten: Long, totalBytes: Long) -> Unit)? = null, ): MediaUploadResult { - checkNotInMainThread() - val contentResolver = context.contentResolver val myContentType = contentType ?: contentResolver.getType(uri) val fileName = context.getFileName(uri) @@ -141,8 +138,6 @@ class BlossomUploader { useMediaEndpoint: Boolean = false, onProgress: ((bytesWritten: Long, totalBytes: Long) -> Unit)? = null, ): MediaUploadResult { - checkNotInMainThread() - val fileName = baseFileName ?: RandomInstance.randomChars(16) val extension = contentType?.let { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt index 5b5fa59daa..bd01fb4a5a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt @@ -32,7 +32,6 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_delete_with_messag import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_to_server_with_message import com.vitorpamplona.amethyst.commons.service.HttpStatusMessages import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.uploads.AVIF_EXTENSION import com.vitorpamplona.amethyst.service.uploads.AVIF_MIME import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult @@ -105,8 +104,6 @@ class Nip96Uploader { httpAuth: suspend (String, String, ByteArray?) -> HTTPAuthorizationEvent?, context: Context, ): MediaUploadResult { - checkNotInMainThread() - val contentResolver = context.contentResolver val myContentType = contentType ?: contentResolver.getType(uri) val length = size ?: contentResolver.querySize(uri) ?: fileSize(uri) ?: 0 @@ -145,8 +142,6 @@ class Nip96Uploader { httpAuth: suspend (String, String, ByteArray?) -> HTTPAuthorizationEvent?, context: Context, ): MediaUploadResult { - checkNotInMainThread() - val fileName = RandomInstance.randomChars(16) val extension = contentType?.let { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index 7f01e6ba76..9e9e28d409 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -32,7 +32,6 @@ import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.service.BundledUpdate import com.vitorpamplona.amethyst.commons.util.equalImmutableLists -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.utils.Log import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -56,8 +55,6 @@ open class UserFeedViewModel( override val isRefreshing: MutableState = mutableStateOf(false) private fun refreshSuspended() { - checkNotInMainThread() - try { isRefreshing.value = true diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt index fba0c1263d..04b4e5f8f2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt @@ -1039,13 +1039,10 @@ private suspend fun sendAttachment( try { // The media-quality slider. // - // Off the main thread, like the strip and the read below it. + // Off the main thread, like the strip and the read below it: // `sendAttachment` is called from the composition's scope, so it - // inherits Main, and `MediaCompressor.compress` opens with - // `checkNotInMainThread()` — so every image attachment threw - // `OnMainThreadException` before it ever reached the uploader. The - // voice path never hit it because it posts its recording already - // encoded and skips compression entirely. + // inherits Main, and compression blocks. The voice path posts its + // recording already encoded and skips compression entirely. val compressed = withContext(Dispatchers.IO) { item.orchestrator.compressIfNeeded( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt index 9c3c5470ac..8d9cc8de9e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt @@ -54,10 +54,8 @@ class ConcordImageUploader( /** * Compresses, strips, AES-256-GCM-encrypts and uploads the picked [uri], returning its pointer. * - * Runs on [Dispatchers.IO]: the compression/upload pipeline asserts it is off the main thread - * ([MediaCompressor] calls `checkNotInMainThread`), and callers launch this from a Compose - * `rememberCoroutineScope()`, which is Main-dispatched — so without this switch the first step - * throws before any bytes leave the device. + * Runs on [Dispatchers.IO]: compression, encryption and the upload all block, and callers launch + * this from a Compose `rememberCoroutineScope()`, which is Main-dispatched. */ suspend fun uploadEncrypted( uri: Uri, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt index b4a28274ee..74fa8e87ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt @@ -31,7 +31,6 @@ import com.vitorpamplona.amethyst.commons.feeds.StringFeedState import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.service.BundledUpdate import com.vitorpamplona.amethyst.commons.util.equalImmutableLists -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.utils.Log import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -55,8 +54,6 @@ open class StringFeedViewModel( } private fun refreshSuspended() { - checkNotInMainThread() - try { isRefreshing.value = true diff --git a/amethyst/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt b/amethyst/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt index 9f5d9f0087..9bc5ac07a9 100644 --- a/amethyst/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt +++ b/amethyst/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt @@ -30,7 +30,6 @@ import com.google.mlkit.nl.translate.TranslateLanguage import com.google.mlkit.nl.translate.Translation import com.google.mlkit.nl.translate.Translator import com.google.mlkit.nl.translate.TranslatorOptions -import com.vitorpamplona.amethyst.service.checkNotInMainThread import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -91,7 +90,6 @@ object LanguageTranslatorService { source: String, target: String, ): Task { - checkNotInMainThread() val sourceLangCode = TranslateLanguage.fromLanguageTag(source) val targetLangCode = TranslateLanguage.fromLanguageTag(target) @@ -110,8 +108,6 @@ object LanguageTranslatorService { val translator = translators[options] return translator.downloadModelIfNeeded().onSuccessTask(executorService) { - checkNotInMainThread() - val dict = TranslationDictionary.build(text) val encoded = TranslationDictionary.encode(text, dict) diff --git a/commons/plans/2026-09-27-one-ui-android-desktop.md b/commons/plans/2026-09-27-one-ui-android-desktop.md index 2ee47b6ae1..ca598c88ff 100644 --- a/commons/plans/2026-09-27-one-ui-android-desktop.md +++ b/commons/plans/2026-09-27-one-ui-android-desktop.md @@ -119,7 +119,7 @@ Each leaving edge is a seam to cut before the group can move. | `Amethyst.kt` | `keyCache` (Account), `encryptedStorage` (Geohash), `appContext` (Nip46 bridge), `notificationDispatcher` (EventProcessor) | constructor parameters / ports; the notification dispatcher gets an interface. **Done 2026-09-28**: `keyCache` → `Account.encryptionKeyCache`; the notifications → `MarmotGroupNotifier`, which `NotificationDispatcher` implements; `encryptedStorage` and `appContext` left with the Geohash and Nip46 cuts. | | `LocalPreferences.kt` | `saveToEncryptedStorage(accountSettings)` on settings change | **Done 2026-09-28** as a constructor lambda, `Account.saveSettings: suspend (AccountSettings) -> Unit`, rather than a named port. | | `DebugUtils.kt` | `logTime` (2 sites) | **Done 2026-09-28.** `commons/util` already had an identical `logTime`; its Android `isDebug` was hard-coded `false`, so it now reads `AndroidDebugFlag.enabled`, which `Amethyst.onCreate` sets. The app copy is gone. | -| `service/MainThreadChecker.kt` | `checkNotInMainThread` (HiddenUsersState) | **Done 2026-09-28**: `LocalCache.appHost.assertNotMainThread()`, which the app host delegates to the same check. | +| `service/MainThreadChecker.kt` | `checkNotInMainThread` (HiddenUsersState) | **Done 2026-09-28**: `LocalCache.appHost.assertNotMainThread()`. **Deleted 2026-09-29**: a debug-only Android assertion that shared code could no longer rely on; the checker, its call sites and the host hook are gone. | | `service/location/LocationState.kt` | the `LocationResult` type in `geolocationFlow` and the around-me feed | **Done 2026-09-28**: `commons/model/location/LocationResult.kt`; 25 files repointed. | | `service/uploads/FileHeader.kt` | the `FileHeader` data type in three send methods | **Done 2026-09-28**: `commons/service/upload/FileHeader.kt` (with `BlurhashWrapper`/`ThumbhashWrapper` moved from commonsUI, same package); `prepare` stays in the app as `FileHeader.Companion` extensions. | | `service/relayClient/…/BuzzMembershipEoseManager.kt` | the `MembershipNotificationKinds` constant | **Done 2026-09-28**: `commons/model/buzz/BuzzMembershipKinds.kt`. | diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/ChannelFeedContentState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/ChannelFeedContentState.kt index 296893376c..b08f4a27d2 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/ChannelFeedContentState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/ChannelFeedContentState.kt @@ -25,7 +25,6 @@ import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateOf import com.vitorpamplona.amethyst.commons.model.Channel import com.vitorpamplona.amethyst.commons.model.Note -import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.service.BundledInsert import com.vitorpamplona.amethyst.commons.service.BundledUpdate import com.vitorpamplona.amethyst.commons.util.equalImmutableLists @@ -73,8 +72,6 @@ class ChannelFeedContentState( } fun refreshSuspended() { - LocalCache.appHost.assertNotMainThread() - isRefreshing.value = true try { lastFeedKey = localFilter.feedKey() diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CachePruner.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CachePruner.kt index 119f2ff33e..056c4e3018 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CachePruner.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CachePruner.kt @@ -162,8 +162,6 @@ class CachePruner( } fun pruneOldMessages() { - cache.appHost.assertNotMainThread() - cache.ephemeralChannels.forEach { _, channel -> pruneOldMessagesChannel(channel) } @@ -310,8 +308,6 @@ class CachePruner( } fun pruneRepliesAndReactions(accounts: Set) { - cache.appHost.assertNotMainThread() - val toBeRemoved = cache.notes.filter { _, note -> ( @@ -448,8 +444,6 @@ class CachePruner( } fun pruneExpiredEvents() { - cache.appHost.assertNotMainThread() - val now = TimeUtils.now() val versionsToBeRemoved = cache.notes.filter { _, it -> it.event?.isExpirationBefore(now) == true } val addressesToBeRemoved = cache.addressables.filter { _, it -> it.event?.isExpirationBefore(now) == true } @@ -481,8 +475,6 @@ class CachePruner( * authors the reader never muted. */ fun pruneHiddenEvents(hidden: LiveHiddenUsers) { - cache.appHost.assertNotMainThread() - val childrenToBeRemoved = mutableListOf() val toBeRemoved = diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CacheSearch.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CacheSearch.kt index 6abcdaefd3..9537f83e0e 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CacheSearch.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CacheSearch.kt @@ -69,8 +69,6 @@ class CacheSearch( ): List { if (username.isBlank()) return emptyList() - cache.appHost.assertNotMainThread() - val key = decodePublicKeyAsHexOrNull(username) if (key != null) { @@ -164,8 +162,6 @@ class CacheSearch( filters: List, hidden: LiveHiddenUsers, ): List { - cache.appHost.assertNotMainThread() - if (filters.isEmpty()) return emptyList() // Distinct across filters: a union of arms (a hashtag asks #t, #l and the comment tags) @@ -201,8 +197,6 @@ class CacheSearch( text: String, hidden: LiveHiddenUsers, ): List { - cache.appHost.assertNotMainThread() - if (text.isBlank()) return emptyList() val key = decodeEventIdAsHexOrNull(text) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/EventCache.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/EventCache.kt index 992eb25bcf..c6615120cc 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/EventCache.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/EventCache.kt @@ -3357,8 +3357,6 @@ open class EventCache : var verifyMeter: ((elapsedNanos: Long, valid: Boolean) -> Unit)? = null fun justVerify(event: Event): Boolean { - appHost.assertNotMainThread() - val meter = verifyMeter if (meter == null) return justVerifyInner(event) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt index e004e23b69..be7628aff7 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt @@ -98,12 +98,6 @@ interface LocalCacheHost { */ fun lnurlEndpoint(lnurlpUrl: String): LnurlEndpointInfo? = null - /** - * Throws if called on the platform's main thread. Signature verification and the cache - * sweeps are far too slow to run there. A no-op by default and on release builds. - */ - fun assertNotMainThread() {} - /** The neutral host: an own IO scope and nothing else. */ companion object Default : LocalCacheHost { override val scope: CoroutineScope by lazy { CoroutineScope(Dispatchers.IO + SupervisorJob()) } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nip51Lists/HiddenUsersState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nip51Lists/HiddenUsersState.kt index 7f50f68545..bad5eaa0a9 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nip51Lists/HiddenUsersState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nip51Lists/HiddenUsersState.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.commons.model.nip51Lists import com.vitorpamplona.amethyst.commons.model.AccountSettings import com.vitorpamplona.amethyst.commons.model.LiveHiddenUsers -import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip51Lists.muteList.tags.EventTag import com.vitorpamplona.quartz.nip51Lists.muteList.tags.HashtagTag @@ -84,7 +83,6 @@ class HiddenUsersState( settings.syncedSettings.security.showSensitiveContent, settings.syncedSettings.security.maxHashtagLimit, ) { blockList, muteList, transientHiddenUsers, showSensitiveContent, maxHashtagLimit -> - LocalCache.appHost.assertNotMainThread() emit(assembleLiveHiddenUsers(blockList, muteList, transientHiddenUsers, showSensitiveContent, maxHashtagLimit)) }.onStart { emit( diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/CardFeedContentState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/CardFeedContentState.kt index 7fdd14bc57..0abaeb2f2d 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/CardFeedContentState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/CardFeedContentState.kt @@ -123,8 +123,6 @@ class CardFeedContentState( private fun refreshSuspended() = refreshLock.withLock { refreshLocked() } private fun refreshLocked() { - LocalCache.appHost.assertNotMainThread() - try { isRefreshing.value = true @@ -170,8 +168,6 @@ class CardFeedContentState( } private fun convertToCard(notes: Collection): List { - LocalCache.appHost.assertNotMainThread() - val reactionsPerEvent = mutableMapOf>() notes .filter { it.event is ReactionEvent } @@ -544,8 +540,6 @@ class CardFeedContentState( } fun updateFeedWith(newNotes: Set) { - LocalCache.appHost.assertNotMainThread() - if (localFilter is AdditiveFeedFilter && _feedContent.value is CardFeedState.Loaded) { invalidateInsertData(newNotes) } else { diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/TopNavFilterState.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/TopNavFilterState.kt index c370932351..98f0917163 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/TopNavFilterState.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/TopNavFilterState.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.AddressableNote -import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.model.nip51Lists.interestSets.InterestSet import com.vitorpamplona.amethyst.commons.model.topNavFeeds.TopFilter @@ -295,7 +294,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, aroundMe, teleport, globalFollow), @@ -308,7 +306,6 @@ class TopNavFilterState( private val _badgeRoutes = livePeopleListsFlow.transform { peopleLists -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, globalFollow, mineFollow), @@ -320,7 +317,6 @@ class TopNavFilterState( private val _communityRoutes = livePeopleListsFlow.transform { peopleLists -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, globalFollow, mineFollow), @@ -335,7 +331,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( // Same content-style catalog as kind3GlobalPeopleRoutes, plus "Mine" so the @@ -353,7 +348,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( // Git repository announcements can be narrowed by author, hashtag and geohash, @@ -371,7 +365,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( // Workout records can be narrowed by author, hashtag and geohash, so this @@ -389,7 +382,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( // Highlights can be narrowed by author, hashtag and geohash, so this mirrors @@ -408,7 +400,6 @@ class TopNavFilterState( liveInterestFlows, account.relayGroupList.liveRelayGroupServers, ) { peopleLists, interests, joinedServers -> - LocalCache.appHost.assertNotMainThread() // A relay chip per host relay of every group the user joined (kind-10009), so they can // browse the OTHER groups on those relays without first having to Favorite them. Deduped // against relays that are already chips because they were favorited (kind-10012, already @@ -442,7 +433,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( // Same content-style catalog as kind3GlobalPeopleRoutes, plus "Mine" so the @@ -457,7 +447,6 @@ class TopNavFilterState( private val _kind3GlobalPeople = livePeopleListsFlow.transform { peopleLists -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, aroundMe, teleport, globalFollow), @@ -473,7 +462,6 @@ class TopNavFilterState( // and AroundMe — none of which can match a tag-less manifest — are deliberately left out. private val _authorOnlyRoutes = livePeopleListsFlow.transform { peopleLists -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, globalFollow, mineFollow), @@ -485,7 +473,6 @@ class TopNavFilterState( private val _notificationLists = livePeopleListsFlow.transform { peopleLists -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, aroundMe, teleport, selectedFollow, globalFollow), diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/NotificationSummaryState.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/NotificationSummaryState.kt index 5dd59a4eb6..be93ba96be 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/NotificationSummaryState.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/NotificationSummaryState.kt @@ -89,8 +89,6 @@ class NotificationSummaryState( fun today(): String = LocalClock.today().ymd() suspend fun initializeSuspend() { - LocalCache.appHost.assertNotMainThread() - val days = LocalClock.epochDayCounter() val currentUser = user.pubkeyHex @@ -182,8 +180,6 @@ class NotificationSummaryState( } suspend fun addToStatsSuspend(newBlockNotes: Set>) { - LocalCache.appHost.assertNotMainThread() - val days = LocalClock.epochDayCounter() val currentUser = user.pubkeyHex @@ -286,8 +282,6 @@ class NotificationSummaryState( } private suspend fun refreshChartModel() { - LocalCache.appHost.assertNotMainThread() - val today = LocalClock.today() val dataAxisLabelIndexes = listOf(-6, -5, -4, -3, -2, -1, 0) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountFeedContentStates.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountFeedContentStates.kt index 3242eeb674..2fab0a5664 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountFeedContentStates.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountFeedContentStates.kt @@ -373,8 +373,6 @@ class AccountFeedContentStates( } fun updateFeedsWith(newNotes: Set) { - LocalCache.appHost.assertNotMainThread() - homeLive.updateFeedWith(newNotes) homeNewThreads.updateFeedWith(newNotes) homeReplies.updateFeedWith(newNotes) @@ -447,8 +445,6 @@ class AccountFeedContentStates( } fun deleteNotes(newNotes: Set) { - LocalCache.appHost.assertNotMainThread() - homeLive.deleteFromFeed(newNotes) homeNewThreads.deleteFromFeed(newNotes) homeReplies.deleteFromFeed(newNotes) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModel.kt index 54f5d4a7aa..3a10ba5a4f 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModel.kt @@ -694,8 +694,6 @@ class AccountViewModel( accountChoices: LiveHiddenUsers, followUsers: Set, ): NoteComposeReportState { - LocalCache.appHost.assertNotMainThread() - val isFromLoggedIn = note.author?.pubkeyHex == userProfile().pubkeyHex val isFromLoggedInFollow = note.author?.let { followUsers.contains(it.pubkeyHex) } ?: true val isPostHidden = note.isHiddenFor(accountChoices)