mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
fix: preload favorited nsite/napplet manifests in the browser
Favorites store only the addressable coordinate (kind:pubkey:dtag); the launch path re-resolves the live event from LocalCache at tap time. When that event hadn't streamed in yet, tapping a favorited nsite/napplet showed "isn't loaded yet" and never recovered on its own — the only thing that pulled the event into the cache was visiting the nsite/napplet feed (it subscribes by author), which is why opening that feed and coming back made the favorite suddenly launchable. Add PreloadFavoriteNostrApps, which subscribes each favorited coordinate to the shared EventFinder (the same lifecycle-aware loader observeNote uses) so the manifests fetch via the author's outbox relays as soon as the launcher opens. Wire it into the Browser tab and the Favorite Apps tab. The loader drops each coordinate once its event arrives, so this is a one-shot fetch, not a standing feed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XH7D6xUhHDKUbrYHoBnZyL
This commit is contained in:
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.favorites
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
/**
|
||||
* Pre-fetches the addressable events behind the user's favorited [FavoriteApp.NostrApp]s (nSites /
|
||||
* nApplets) while a screen that can launch them is on screen.
|
||||
*
|
||||
* A favorite stores only the addressable coordinate `kind:pubkey:dtag`, never the event. The launch
|
||||
* path ([FavoriteAppLauncher.launchNostrApp] / [FavoriteAppLauncher.embedParams]) re-resolves the live
|
||||
* event from [LocalCache] at tap time, so a favorite whose event hasn't streamed in yet can't launch —
|
||||
* the user gets the "isn't loaded yet" toast / unavailable tab. Before this preloader, the only thing
|
||||
* that pulled those events into the cache was visiting the nsite/napplet feed (it subscribes by author),
|
||||
* which is why opening that feed and coming back made a favorite suddenly launchable.
|
||||
*
|
||||
* This subscribes each favorited coordinate to the shared
|
||||
* [EventFinder][com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssembler]
|
||||
* — the same lifecycle-aware loader [observeNote][com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote]
|
||||
* uses — so the manifests fetch (via the author's outbox relays) as soon as the launcher opens and are
|
||||
* already in [LocalCache] by the time the user taps. The loader drops each coordinate from its filter
|
||||
* once the event arrives, so this is a one-shot fetch, not a standing feed.
|
||||
*/
|
||||
@Composable
|
||||
fun PreloadFavoriteNostrApps(
|
||||
apps: List<FavoriteApp>,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val account = accountViewModel.account
|
||||
// Reuse the same query-state instances across recompositions (the manager ref-counts by identity),
|
||||
// recomputing only when the favorite list or account changes. Events that already loaded are still
|
||||
// included; the loader simply skips them because their note already has an event.
|
||||
val states =
|
||||
remember(apps, account) {
|
||||
apps
|
||||
.filterIsInstance<FavoriteApp.NostrApp>()
|
||||
.mapNotNull { LocalCache.checkGetOrCreateAddressableNote(it.coordinate) }
|
||||
.map { EventFinderQueryState(it, account) }
|
||||
}
|
||||
|
||||
LifecycleAwareKeyDataSourceSubscription(states, accountViewModel.dataSources().eventFinder)
|
||||
}
|
||||
+5
@@ -82,6 +82,7 @@ import com.vitorpamplona.amethyst.favorites.BrowserHistoryRegistry
|
||||
import com.vitorpamplona.amethyst.favorites.BrowserIconRegistry
|
||||
import com.vitorpamplona.amethyst.favorites.FavoriteAppLauncher
|
||||
import com.vitorpamplona.amethyst.favorites.FavoriteAppsRegistry
|
||||
import com.vitorpamplona.amethyst.favorites.PreloadFavoriteNostrApps
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
@@ -125,6 +126,10 @@ private fun BrowserLauncher(
|
||||
val history by BrowserHistoryRegistry.history.collectAsStateWithLifecycle()
|
||||
val iconKeys by BrowserIconRegistry.keys.collectAsStateWithLifecycle()
|
||||
|
||||
// Fetch favorited nsite/napplet manifests up front so tapping one launches immediately instead of
|
||||
// showing "isn't loaded yet" until the user happens to visit the nsite/napplet feed.
|
||||
PreloadFavoriteNostrApps(apps, accountViewModel)
|
||||
|
||||
var field by remember { mutableStateOf(TextFieldValue("")) }
|
||||
|
||||
// Favorites + visit history flattened into the neutral candidate shape the ranker consumes.
|
||||
|
||||
+5
@@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.favorites.BrowserIconRegistry
|
||||
import com.vitorpamplona.amethyst.favorites.FavoriteAppLauncher
|
||||
import com.vitorpamplona.amethyst.favorites.FavoriteAppsRegistry
|
||||
import com.vitorpamplona.amethyst.favorites.PreloadFavoriteNostrApps
|
||||
import com.vitorpamplona.amethyst.favorites.rememberNappletIconModel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
@@ -90,6 +91,10 @@ fun FavoriteAppsScreen(
|
||||
val context = LocalContext.current
|
||||
val apps by FavoriteAppsRegistry.favorites.collectAsStateWithLifecycle()
|
||||
|
||||
// Fetch favorited nsite/napplet manifests up front so a tap launches immediately instead of showing
|
||||
// "isn't loaded yet" until the user happens to visit the nsite/napplet feed.
|
||||
PreloadFavoriteNostrApps(apps, accountViewModel)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(title = { Text(stringResource(R.string.favorite_apps)) })
|
||||
|
||||
Reference in New Issue
Block a user