mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
feat: show curated descriptions for discover web apps
Render the browser "Discover" section as full-width rows (icon + name + one-line description) instead of bare icon cells, matching the Recent row layout which already carries a subtitle. Each suggestion now has a short curated description (trimmed from the app's own meta description) so unfamiliar apps explain themselves; tapping a row opens the app, and a trailing star pins it to favorites. Auto-pulling page <title>/description was rejected: many of these apps are client-rendered SPAs that serve an empty <title>, and several titles are long marketing strings — curated short names read better in the list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0151Uczec41LhTogxkgoAhKa
This commit is contained in:
+58
-4
@@ -74,7 +74,9 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.browser.DefaultWebClients
|
||||
import com.vitorpamplona.amethyst.commons.browser.OmniboxInput
|
||||
import com.vitorpamplona.amethyst.commons.browser.OmniboxSuggestions
|
||||
import com.vitorpamplona.amethyst.commons.browser.SuggestedWebApp
|
||||
import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp
|
||||
import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppIcon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.rememberMaterialSymbolPainter
|
||||
@@ -90,7 +92,6 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.favoriteAppItems
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.suggestedAppItems
|
||||
import com.vitorpamplona.amethyst.commons.R as CommonsR
|
||||
|
||||
/** How many of the most recent history entries the idle browser home surfaces under "Recent". */
|
||||
@@ -214,7 +215,7 @@ private fun BrowserLauncher(
|
||||
else -> {
|
||||
val favoriteUrls = remember(apps) { apps.filterIsInstance<FavoriteApp.WebApp>().mapTo(HashSet()) { it.url } }
|
||||
// Hardcoded starter web apps, minus any the user already pinned (those show under Favorites).
|
||||
val suggested = remember(favoriteUrls) { DefaultWebClients.list.filter { it.url !in favoriteUrls } }
|
||||
val suggested = remember(favoriteUrls) { DefaultWebClients.list.filter { it.app.url !in favoriteUrls } }
|
||||
BrowserHome(
|
||||
apps = apps,
|
||||
history = history,
|
||||
@@ -369,7 +370,7 @@ private fun BrowserHome(
|
||||
history: List<BrowserHistoryEntry>,
|
||||
iconKeys: Set<String>,
|
||||
favoriteUrls: Set<String>,
|
||||
suggested: List<FavoriteApp.WebApp>,
|
||||
suggested: List<SuggestedWebApp>,
|
||||
onOpenApp: (FavoriteApp) -> Unit,
|
||||
onRemoveApp: (FavoriteApp) -> Unit,
|
||||
onAddApp: (FavoriteApp) -> Unit,
|
||||
@@ -405,7 +406,60 @@ private fun BrowserHome(
|
||||
}
|
||||
if (suggested.isNotEmpty()) {
|
||||
item(span = { GridItemSpan(maxLineSpan) }, key = "h-sug") { SectionHeader(stringResource(R.string.browser_suggested)) }
|
||||
suggestedAppItems(suggested, onOpenApp, onAddApp)
|
||||
items(suggested, span = { GridItemSpan(maxLineSpan) }, key = { "s:" + it.app.url }) { entry ->
|
||||
SuggestedRow(
|
||||
entry = entry,
|
||||
iconKeys = iconKeys,
|
||||
onClick = { onOpenApp(entry.app) },
|
||||
onAddFavorite = { onAddApp(entry.app) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A Discover row: the app's own icon, its name, and a one-line description, with a star to pin it. */
|
||||
@Composable
|
||||
private fun SuggestedRow(
|
||||
entry: SuggestedWebApp,
|
||||
iconKeys: Set<String>,
|
||||
onClick: () -> Unit,
|
||||
onAddFavorite: () -> Unit,
|
||||
) {
|
||||
val iconModel = remember(entry, iconKeys) { OmniboxInput.hostOf(entry.app.url)?.let(BrowserIconRegistry::iconModelFor) }
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.clickable(onClick = onClick)
|
||||
.padding(start = 8.dp, top = 4.dp, bottom = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
FavoriteAppIcon(
|
||||
app = entry.app,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(28.dp),
|
||||
iconModel = iconModel,
|
||||
)
|
||||
Spacer(Modifier.width(16.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
entry.app.label,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
entry.description,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onAddFavorite) {
|
||||
Icon(MaterialSymbols.StarBorder, contentDescription = stringResource(R.string.favorite_app_add))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-41
@@ -167,45 +167,7 @@ fun LazyGridScope.favoriteAppItems(
|
||||
FavoriteAppCell(
|
||||
app = app,
|
||||
onOpen = { onOpen(app) },
|
||||
menu = { dismiss ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.favorite_app_remove)) },
|
||||
leadingIcon = { Icon(MaterialSymbols.Delete, contentDescription = null) },
|
||||
onClick = {
|
||||
dismiss()
|
||||
onRemove(app)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the same launch cells for a list of *suggested* web apps (the hardcoded defaults the browser
|
||||
* home offers under "Discover web apps"). Identical to [favoriteAppItems] except the long-press menu offers
|
||||
* "Add to favorites" instead of "Remove" — these aren't pinned yet, so tapping star is what the user
|
||||
* would want next.
|
||||
*/
|
||||
fun LazyGridScope.suggestedAppItems(
|
||||
apps: List<FavoriteApp>,
|
||||
onOpen: (FavoriteApp) -> Unit,
|
||||
onAddFavorite: (FavoriteApp) -> Unit,
|
||||
) {
|
||||
items(apps, key = { "suggested:" + it.id }) { app ->
|
||||
FavoriteAppCell(
|
||||
app = app,
|
||||
onOpen = { onOpen(app) },
|
||||
menu = { dismiss ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.favorite_app_add)) },
|
||||
leadingIcon = { Icon(MaterialSymbols.StarBorder, contentDescription = null) },
|
||||
onClick = {
|
||||
dismiss()
|
||||
onAddFavorite(app)
|
||||
},
|
||||
)
|
||||
},
|
||||
onRemove = { onRemove(app) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -215,7 +177,7 @@ fun LazyGridScope.suggestedAppItems(
|
||||
internal fun FavoriteAppCell(
|
||||
app: FavoriteApp,
|
||||
onOpen: () -> Unit,
|
||||
menu: @Composable (dismiss: () -> Unit) -> Unit,
|
||||
onRemove: () -> Unit,
|
||||
) {
|
||||
var menuOpen by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -268,7 +230,14 @@ internal fun FavoriteAppCell(
|
||||
)
|
||||
|
||||
DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
|
||||
menu { menuOpen = false }
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.favorite_app_remove)) },
|
||||
leadingIcon = { Icon(MaterialSymbols.Delete, contentDescription = null) },
|
||||
onClick = {
|
||||
menuOpen = false
|
||||
onRemove()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+90
-76
@@ -20,117 +20,131 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.browser
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp
|
||||
|
||||
/** One suggested web app: the launchable [app] plus a short [description] of what it does. */
|
||||
@Immutable
|
||||
data class SuggestedWebApp(
|
||||
val app: FavoriteApp.WebApp,
|
||||
val description: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* The Nostr **web apps** offered as starting points in the browser launcher when the user hasn't
|
||||
* pinned or visited anything of their own yet. They are shown under a "Discover" section below Recent,
|
||||
* so they're discoverable without ever getting in the way of the user's own favorites/history.
|
||||
*
|
||||
* The list is drawn from the [nostrapps.com](https://nostrapps.com) directory — every entry that ships
|
||||
* a browser-openable web version — grouped here by what it does. Entries that are browser *extensions*
|
||||
* or signer-only tools (nos2x, Nostrame, …) are intentionally left out: they aren't something you open
|
||||
* in an in-app browser. URLs are the apps' own canonical domains.
|
||||
* The list is drawn from the [nostrapps.com](https://nostrapps.com) directory (cross-checked against
|
||||
* [awesome-nostr](https://github.com/aljazceru/awesome-nostr)) — every entry that ships a
|
||||
* browser-openable web version — grouped here by what it does. Entries that are browser *extensions* or
|
||||
* signer-only tools (nos2x, Nostrame, …) are intentionally left out: they aren't something you open in
|
||||
* an in-app browser. URLs are the apps' own canonical domains.
|
||||
*
|
||||
* Each [icon] is the app's **own** logo (the PNG/SVG declared in its `<link rel="apple-touch-icon">` /
|
||||
* `<link rel="icon">`), so the grid matches the favicon look of the Favorites/Recent rows without
|
||||
* routing through any third-party favicon service. Only PNG/SVG are used — Coil has no ICO decoder, so
|
||||
* an apps whose only icon is a `.ico` (and a few that couldn't be resolved) are left [icon]-less and
|
||||
* fall back to the globe glyph until their real favicon is captured the normal way on first visit.
|
||||
* Each entry carries:
|
||||
* - a short curated [SuggestedWebApp.description] (a trimmed version of the app's own meta description)
|
||||
* shown as the row subtitle, since these are apps the user likely hasn't seen before;
|
||||
* - the app's **own** logo as [FavoriteApp.iconUrl] (the PNG/SVG declared in its
|
||||
* `<link rel="apple-touch-icon">` / `<link rel="icon">`), so it matches the favicon look of the
|
||||
* Favorites/Recent rows without routing through any third-party favicon service. Only PNG/SVG are
|
||||
* used — Coil has no ICO decoder — so apps whose only icon is a `.ico` (and a few that couldn't be
|
||||
* resolved) are left icon-less and fall back to the globe glyph until their real favicon is captured
|
||||
* the normal way on first visit.
|
||||
*
|
||||
* These are plain [FavoriteApp.WebApp] entries, so tapping one opens it like any other web favorite and
|
||||
* the user can star it to pin it for real. They are **not** persisted as favorites: the list is
|
||||
* hardcoded, device-local, and never becomes account state.
|
||||
* The [app]s are plain [FavoriteApp.WebApp] entries, so tapping one opens it like any other web
|
||||
* favorite and the user can star it to pin it for real. They are **not** persisted as favorites: the
|
||||
* list is hardcoded, device-local, and never becomes account state.
|
||||
*/
|
||||
object DefaultWebClients {
|
||||
val list: List<FavoriteApp.WebApp> =
|
||||
val list: List<SuggestedWebApp> =
|
||||
buildList {
|
||||
// Social / microblogging clients
|
||||
webApp("Primal", "https://primal.net", "https://primal.net/assets/apple-touch-icon-a536f430.png")
|
||||
webApp("Coracle", "https://coracle.social", "https://coracle.social/icons/apple-touch-icon-76x76.png")
|
||||
webApp("Snort", "https://snort.social", "https://snort.social/img/apple-touch-icon.png")
|
||||
webApp("noStrudel", "https://nostrudel.ninja", "https://nostrudel.ninja/apple-touch-icon.png")
|
||||
webApp("Iris", "https://iris.to", "https://iris.to/img/apple-touch-icon.png")
|
||||
webApp("Nostter", "https://nostter.app", "https://nostter.app/apple-touch-icon.png")
|
||||
webApp("Jumble", "https://jumble.social", "https://jumble.social/favicon.svg")
|
||||
webApp("Nostria", "https://nostria.app", "https://nostria.app/icons/icon-192x192-maskable.png")
|
||||
webApp("Nosotros", "https://nosotros.app", "https://nosotros.app/apple-touch-icon-144x144.png")
|
||||
webApp("lumilumi", "https://lumilumi.app", "https://lumilumi.app/apple-touch-icon-180x180.png")
|
||||
webApp("Phoenix", "https://phoenix.social", "https://phoenix.social/img/apple-touch-icon.png")
|
||||
webApp("Shosho", "https://shosho.live", "https://shosho.live/apple-touch-icon.png")
|
||||
webApp("ants", "https://ants.sh", "https://ants.sh/apple-touch-icon.png")
|
||||
webApp("YakiHonne", "https://yakihonne.com")
|
||||
webApp("Ditto", "https://ditto.pub", "https://ditto.pub/apple-touch-icon.png")
|
||||
webApp("x21", "https://x21.social", "https://x21.social/apple-touch-icon.png?v=4")
|
||||
webApp("Ghostr", "https://ghostr.org", "https://ghostr.org/favicon/apple-touch-icon.png")
|
||||
webApp("Mutable", "https://mutable.top", "https://mutable.top/mutable_logo.svg")
|
||||
webApp("Primal", "https://primal.net", "All-in-one client with a built-in wallet", "https://primal.net/assets/apple-touch-icon-a536f430.png")
|
||||
webApp("Coracle", "https://coracle.social", "Relay-savvy client for regular people", "https://coracle.social/icons/apple-touch-icon-76x76.png")
|
||||
webApp("Snort", "https://snort.social", "Fast, feature-packed social client", "https://snort.social/img/apple-touch-icon.png")
|
||||
webApp("noStrudel", "https://nostrudel.ninja", "Power-user client for exploring Nostr", "https://nostrudel.ninja/apple-touch-icon.png")
|
||||
webApp("Iris", "https://iris.to", "Simple, fast social client", "https://iris.to/img/apple-touch-icon.png")
|
||||
webApp("Nostter", "https://nostter.app", "Lightweight web social client", "https://nostter.app/apple-touch-icon.png")
|
||||
webApp("Jumble", "https://jumble.social", "Explore feeds relay by relay", "https://jumble.social/favicon.svg")
|
||||
webApp("Nostria", "https://nostria.app", "Social without the noise", "https://nostria.app/icons/icon-192x192-maskable.png")
|
||||
webApp("Nosotros", "https://nosotros.app", "A weirdly fast social client", "https://nosotros.app/apple-touch-icon-144x144.png")
|
||||
webApp("lumilumi", "https://lumilumi.app", "Lightweight Nostr client", "https://lumilumi.app/apple-touch-icon-180x180.png")
|
||||
webApp("Phoenix", "https://phoenix.social", "Snort-based social client", "https://phoenix.social/img/apple-touch-icon.png")
|
||||
webApp("Shosho", "https://shosho.live", "Live-streaming marketplace", "https://shosho.live/apple-touch-icon.png")
|
||||
webApp("ants", "https://ants.sh", "Advanced Nostr text search", "https://ants.sh/apple-touch-icon.png")
|
||||
webApp("YakiHonne", "https://yakihonne.com", "Decentralized media & long-form")
|
||||
webApp("Ditto", "https://ditto.pub", "Your content, your vibe, your rules", "https://ditto.pub/apple-touch-icon.png")
|
||||
webApp("x21", "https://x21.social", "Relay feed explorer", "https://x21.social/apple-touch-icon.png?v=4")
|
||||
webApp("Ghostr", "https://ghostr.org", "Draft & delegated publishing", "https://ghostr.org/favicon/apple-touch-icon.png")
|
||||
webApp("Mutable", "https://mutable.top", "Your mute list manager", "https://mutable.top/mutable_logo.svg")
|
||||
|
||||
// Reading / long-form / feeds
|
||||
webApp("Habla", "https://habla.news")
|
||||
webApp("Highlighter", "https://highlighter.com", "https://highlighter.com/apple-touch-icon-180x180.png")
|
||||
webApp("Boris", "https://readwithboris.com", "https://readwithboris.com/apple-touch-icon.png")
|
||||
webApp("Noflux", "https://noflux.nostr.technology")
|
||||
webApp("Habla", "https://habla.news", "Long-form articles & blogs")
|
||||
webApp("Highlighter", "https://highlighter.com", "Articles, highlights & communities", "https://highlighter.com/apple-touch-icon-180x180.png")
|
||||
webApp("Boris", "https://readwithboris.com", "Distraction-free reading & highlights", "https://readwithboris.com/apple-touch-icon.png")
|
||||
webApp("Noflux", "https://noflux.nostr.technology", "RSS-style feed reader")
|
||||
|
||||
// Communities / chat
|
||||
webApp("Flotilla", "https://flotilla.social", "https://framerusercontent.com/images/8UjnVxSvRkmvY2lEYU5z8OMOw0M.png")
|
||||
webApp("Chachi", "https://chachi.chat")
|
||||
webApp("NostrChat", "https://www.nostrchat.io", "https://www.nostrchat.io/logo192.png")
|
||||
webApp("NymChat", "https://www.nymchat.com")
|
||||
webApp("Flotilla", "https://flotilla.social", "Community spaces & chat", "https://framerusercontent.com/images/8UjnVxSvRkmvY2lEYU5z8OMOw0M.png")
|
||||
webApp("Chachi", "https://chachi.chat", "Group chat & communities")
|
||||
webApp("NostrChat", "https://www.nostrchat.io", "Decentralized chat", "https://www.nostrchat.io/logo192.png")
|
||||
webApp("NymChat", "https://www.nymchat.com", "Anonymous, ephemeral chat")
|
||||
|
||||
// Media — video, photo, audio, podcasts, files
|
||||
webApp("zap.stream", "https://zap.stream", "https://zap.stream/logo.png")
|
||||
webApp("Divine Video", "https://divine.video", "https://divine.video/app_icon.png")
|
||||
webApp("Olas", "https://olas.app", "https://olas.app/favicon.png")
|
||||
webApp("Zappix", "https://zappix.app", "https://zappix.app/icon-192.png")
|
||||
webApp("Slidestr", "https://slidestr.net", "https://slidestr.net/slidestr.svg")
|
||||
webApp("Bouquet", "https://bouquet.slidestr.net", "https://bouquet.slidestr.net/bouquet.png")
|
||||
webApp("YakBak", "https://yakbak.app", "https://yakbak.app/yakbak-logo.png")
|
||||
webApp("ZapTrax", "https://zaptrax.app", "https://zaptrax.app/icon-192.png")
|
||||
webApp("Podstr", "https://podstr.org", "https://podstr.org/favicon.svg")
|
||||
webApp("Nests", "https://nostrnests.com", "https://nostrnests.com/apple-touch-icon.png")
|
||||
webApp("zap.stream", "https://zap.stream", "Live streaming with Lightning", "https://zap.stream/logo.png")
|
||||
webApp("Divine Video", "https://divine.video", "6-second looping videos", "https://divine.video/app_icon.png")
|
||||
webApp("Olas", "https://olas.app", "Photo & media sharing", "https://olas.app/favicon.png")
|
||||
webApp("Zappix", "https://zappix.app", "Share & discover images", "https://zappix.app/icon-192.png")
|
||||
webApp("Slidestr", "https://slidestr.net", "Media slideshow viewer", "https://slidestr.net/slidestr.svg")
|
||||
webApp("Bouquet", "https://bouquet.slidestr.net", "Blossom media manager", "https://bouquet.slidestr.net/bouquet.png")
|
||||
webApp("YakBak", "https://yakbak.app", "Voice messages", "https://yakbak.app/yakbak-logo.png")
|
||||
webApp("ZapTrax", "https://zaptrax.app", "Music streaming with Wavlake", "https://zaptrax.app/icon-192.png")
|
||||
webApp("Podstr", "https://podstr.org", "Podcasts on Nostr", "https://podstr.org/favicon.svg")
|
||||
webApp("Nests", "https://nostrnests.com", "Live audio rooms", "https://nostrnests.com/apple-touch-icon.png")
|
||||
|
||||
// Knowledge / wiki
|
||||
webApp("Wikifreedia", "https://wikifreedia.xyz", "https://wikifreedia.xyz/favicon.svg")
|
||||
webApp("Wikistr", "https://wikistr.com", "https://wikistr.com/favicon.png")
|
||||
webApp("Wikifreedia", "https://wikifreedia.xyz", "Decentralized encyclopedia", "https://wikifreedia.xyz/favicon.svg")
|
||||
webApp("Wikistr", "https://wikistr.com", "A wiki built on Nostr", "https://wikistr.com/favicon.png")
|
||||
|
||||
// Marketplace / food
|
||||
webApp("Shopstr", "https://shopstr.store")
|
||||
webApp("Plebeian Market", "https://plebeian.market", "https://plebeian.market/logo-st5zpap9.svg")
|
||||
webApp("Zap Cooking", "https://zap.cooking", "https://zap.cooking/favicon.svg")
|
||||
webApp("Shopstr", "https://shopstr.store", "Bitcoin-native marketplace")
|
||||
webApp("Plebeian Market", "https://plebeian.market", "Decentralized marketplace", "https://plebeian.market/logo-st5zpap9.svg")
|
||||
webApp("Zap Cooking", "https://zap.cooking", "Recipes & food culture", "https://zap.cooking/favicon.svg")
|
||||
|
||||
// Tools / utilities
|
||||
webApp("Emojito", "https://emojito.meme")
|
||||
webApp("Formstr", "https://formstr.app", "https://formstr.app/logo192.png")
|
||||
webApp("Nostree", "https://nostree.me")
|
||||
webApp("Badges", "https://badges.page")
|
||||
webApp("Nstart", "https://nstart.me", "https://nstart.me/favicon.png")
|
||||
webApp("alphaama", "https://alphaama.com")
|
||||
webApp("Treasures", "https://treasures.to", "https://treasures.to/apple-touch-icon.png")
|
||||
webApp("Yondar", "https://yondar.me", "https://yondar.me/apple-touch-icon.png")
|
||||
webApp("DTAN", "https://dtan.xyz")
|
||||
webApp("Nostrocket", "https://nostrocket.org")
|
||||
webApp("Plektos", "https://plektos.app", "https://plektos.app/icon-180.png")
|
||||
webApp("Zaplytics", "https://zaplytics.app")
|
||||
webApp("Brainstorm", "https://brainstorm.world", "https://brainstorm.world/brainstorm.svg")
|
||||
webApp("MAKIMONO", "https://makimono.lumilumi.app", "https://makimono.lumilumi.app/favicon3.png")
|
||||
webApp("Primal Studio", "https://studio.primal.net")
|
||||
webApp("nostr.build", "https://nostr.build", "https://nostr.build/apple-touch-icon.png")
|
||||
webApp("nostrcheck", "https://nostrcheck.me", "https://nostrcheck.me/apple-touch-icon.png")
|
||||
webApp("Metadata", "https://metadata.nostr.com")
|
||||
webApp("Emojito", "https://emojito.meme", "Custom emoji sets")
|
||||
webApp("Formstr", "https://formstr.app", "Decentralized forms", "https://formstr.app/logo192.png")
|
||||
webApp("Nostree", "https://nostree.me", "Link-in-bio pages")
|
||||
webApp("Badges", "https://badges.page", "Create & award badges")
|
||||
webApp("Nstart", "https://nstart.me", "Guided account onboarding", "https://nstart.me/favicon.png")
|
||||
webApp("alphaama", "https://alphaama.com", "Nostr tools & experiments")
|
||||
webApp("Treasures", "https://treasures.to", "Geocaching on Nostr", "https://treasures.to/apple-touch-icon.png")
|
||||
webApp("Yondar", "https://yondar.me", "Places & maps", "https://yondar.me/apple-touch-icon.png")
|
||||
webApp("DTAN", "https://dtan.xyz", "Torrents on Nostr")
|
||||
webApp("Nostrocket", "https://nostrocket.org", "Project coordination")
|
||||
webApp("Plektos", "https://plektos.app", "Decentralized meetup events", "https://plektos.app/icon-180.png")
|
||||
webApp("Zaplytics", "https://zaplytics.app", "Zap analytics for creators")
|
||||
webApp("Brainstorm", "https://brainstorm.world", "Web-of-trust explorer", "https://brainstorm.world/brainstorm.svg")
|
||||
webApp("MAKIMONO", "https://makimono.lumilumi.app", "Long-form article editor", "https://makimono.lumilumi.app/favicon3.png")
|
||||
webApp("Primal Studio", "https://studio.primal.net", "Schedule & publish content")
|
||||
webApp("nostr.build", "https://nostr.build", "Media & image uploads", "https://nostr.build/apple-touch-icon.png")
|
||||
webApp("nostrcheck", "https://nostrcheck.me", "Media hosting & NIP-05", "https://nostrcheck.me/apple-touch-icon.png")
|
||||
webApp("Metadata", "https://metadata.nostr.com", "Edit your profile metadata")
|
||||
|
||||
// Games
|
||||
webApp("Plebs vs Zombies", "https://www.plebsvszombies.cc", "https://www.plebsvszombies.cc/favicon.svg")
|
||||
webApp("Blobbi", "https://www.blobbi.pet", "https://www.blobbi.pet/icons/apple-touch-icon.png")
|
||||
webApp("Plebs vs Zombies", "https://www.plebsvszombies.cc", "Clean up your follow list", "https://www.plebsvszombies.cc/favicon.svg")
|
||||
webApp("Blobbi", "https://www.blobbi.pet", "A virtual pet game", "https://www.blobbi.pet/icons/apple-touch-icon.png")
|
||||
}
|
||||
|
||||
// addedAt is 0L: these are hardcoded suggestions, not user-added favorites, so they never need a
|
||||
// real "added" timestamp for ordering — the curated order in `list` is what matters. `icon` is the
|
||||
// app's own logo URL, or null to fall back to the globe glyph until a favicon is captured on visit.
|
||||
private fun MutableList<FavoriteApp.WebApp>.webApp(
|
||||
private fun MutableList<SuggestedWebApp>.webApp(
|
||||
label: String,
|
||||
url: String,
|
||||
description: String,
|
||||
icon: String? = null,
|
||||
) {
|
||||
add(FavoriteApp.WebApp(url = url, label = label, addedAt = 0L, iconUrl = icon))
|
||||
add(SuggestedWebApp(FavoriteApp.WebApp(url = url, label = label, addedAt = 0L, iconUrl = icon), description))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user