mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 01:07:46 +00:00
feat: add a search box to the App Recommendations screen
The Recommended Apps screen listed every discovered app definition in a single sorted LazyColumn with no way to filter, so finding a specific app to recommend meant scrolling the whole list. Users couldn't find a search because there wasn't one. Add an always-visible outlined search field below the description that filters the list by app name (case-insensitive). Rows whose kind 31990 definition hasn't arrived yet have no name to match, so they only appear when the box is empty. A dedicated empty state shows when a query matches nothing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EjuK6g3KNkyEEnti6JtDEi
This commit is contained in:
+65
-2
@@ -32,7 +32,9 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
@@ -62,11 +64,15 @@ import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
|
||||
import com.vitorpamplona.amethyst.ui.note.ClearTextIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.SearchIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.types.ByAuthorChip
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.apps.recommendations.datasource.ProfileAppRecommendationsFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.kindDisplayName
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -156,6 +162,29 @@ fun ProfileAppRecommendationsScreen(
|
||||
.map { LocalCache.getOrCreateAddressableNote(it) }
|
||||
}
|
||||
|
||||
// Full candidate list in display order; the search box filters this view.
|
||||
val allApps =
|
||||
remember(missingRecommended, apps) {
|
||||
missingRecommended + apps
|
||||
}
|
||||
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
|
||||
// Matches on the app's name; rows whose definition hasn't arrived yet have no
|
||||
// name to match, so they only show when the search box is empty.
|
||||
val visibleApps =
|
||||
remember(allApps, searchQuery) {
|
||||
val query = searchQuery.trim()
|
||||
if (query.isEmpty()) {
|
||||
allApps
|
||||
} else {
|
||||
allApps.filter { note ->
|
||||
val event = note.event as? AppDefinitionEvent
|
||||
event?.appMetaData()?.anyName()?.contains(query, ignoreCase = true) == true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBarWithBackButton(stringRes(id = R.string.profile_app_recommendations_title), nav)
|
||||
@@ -168,19 +197,53 @@ fun ProfileAppRecommendationsScreen(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = 20.dp, vertical = 12.dp),
|
||||
)
|
||||
|
||||
if (allApps.isNotEmpty()) {
|
||||
OutlinedTextField(
|
||||
value = searchQuery,
|
||||
onValueChange = { searchQuery = it },
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp, vertical = 4.dp),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.profile_app_recommendations_search),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
leadingIcon = { SearchIcon(modifier = Size20Modifier, MaterialTheme.colorScheme.placeholderText) },
|
||||
trailingIcon = {
|
||||
if (searchQuery.isNotEmpty()) {
|
||||
IconButton(onClick = { searchQuery = "" }) {
|
||||
ClearTextIcon()
|
||||
}
|
||||
}
|
||||
},
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
if (apps.isEmpty() && missingRecommended.isEmpty()) {
|
||||
if (allApps.isEmpty()) {
|
||||
Text(
|
||||
text = stringRes(R.string.profile_app_recommendations_empty),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(20.dp),
|
||||
)
|
||||
} else if (visibleApps.isEmpty()) {
|
||||
Text(
|
||||
text = stringRes(R.string.profile_app_recommendations_search_empty),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(20.dp),
|
||||
)
|
||||
} else {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
items(
|
||||
items = missingRecommended + apps,
|
||||
items = visibleApps,
|
||||
key = { it.idHex },
|
||||
) { appNote ->
|
||||
AppRow(
|
||||
|
||||
@@ -604,6 +604,8 @@
|
||||
<string name="profile_app_recommendations_title">Recommended apps</string>
|
||||
<string name="profile_app_recommendations_description">Choose which Nostr apps you publicly recommend. Recommendations appear on your profile and help others discover apps for content this client can\'t open.</string>
|
||||
<string name="profile_app_recommendations_empty">No apps found yet. Apps will appear here as they are discovered on your relays.</string>
|
||||
<string name="profile_app_recommendations_search">Search apps by name</string>
|
||||
<string name="profile_app_recommendations_search_empty">No apps match your search.</string>
|
||||
<string name="app_definition_untitled">Unnamed app</string>
|
||||
<string name="app_definition_no_supported_kinds">Doesn\'t announce what content it handles</string>
|
||||
<string name="app_definition_recommend">Recommend</string>
|
||||
|
||||
Reference in New Issue
Block a user