From b0b791f3253271b1340100a537c77215cbbd73be Mon Sep 17 00:00:00 2001 From: Damontecres Date: Sun, 5 Oct 2025 17:47:13 -0400 Subject: [PATCH] Barebones search page --- .../damontecres/dolphin/ui/main/SearchPage.kt | 181 ++++++++++++++++++ .../dolphin/ui/nav/DestinationContent.kt | 8 +- 2 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/github/damontecres/dolphin/ui/main/SearchPage.kt diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/main/SearchPage.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/main/SearchPage.kt new file mode 100644 index 00000000..93046791 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/main/SearchPage.kt @@ -0,0 +1,181 @@ +package com.github.damontecres.dolphin.ui.main + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.github.damontecres.dolphin.data.model.BaseItem +import com.github.damontecres.dolphin.preferences.UserPreferences +import com.github.damontecres.dolphin.ui.cards.ItemRow +import com.github.damontecres.dolphin.ui.components.EditTextBox +import com.github.damontecres.dolphin.ui.isNotNullOrBlank +import com.github.damontecres.dolphin.ui.nav.NavigationManager +import com.github.damontecres.dolphin.ui.tryRequestFocus +import com.github.damontecres.dolphin.util.ApiRequestPager +import com.github.damontecres.dolphin.util.ExceptionHandler +import com.github.damontecres.dolphin.util.GetItemsRequestHandler +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jellyfin.sdk.api.client.ApiClient +import org.jellyfin.sdk.model.api.BaseItemKind +import org.jellyfin.sdk.model.api.request.GetItemsRequest +import javax.inject.Inject + +@HiltViewModel +class SearchViewModel + @Inject + constructor( + val api: ApiClient, + ) : ViewModel() { + val movies = MutableLiveData>(listOf()) + val series = MutableLiveData>(listOf()) + val episodes = MutableLiveData>(listOf()) + + fun search(query: String?) { + movies.value = listOf() + series.value = listOf() + episodes.value = listOf() + if (query.isNotNullOrBlank()) { + viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { + val request = + GetItemsRequest( + searchTerm = query, + recursive = true, + includeItemTypes = listOf(BaseItemKind.MOVIE), + limit = 25, + ) + val pager = ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope) + pager.init() + withContext(Dispatchers.Main) { + movies.value = pager + } + } + viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { + val request = + GetItemsRequest( + searchTerm = query, + recursive = true, + includeItemTypes = listOf(BaseItemKind.SERIES), + limit = 25, + ) + val pager = ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope) + pager.init() + withContext(Dispatchers.Main) { + series.value = pager + } + } + viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { + val request = + GetItemsRequest( + searchTerm = query, + recursive = true, + includeItemTypes = listOf(BaseItemKind.EPISODE), + limit = 25, + ) + val pager = ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope) + pager.init() + withContext(Dispatchers.Main) { + episodes.value = pager + } + } + } + } + + fun getHints(query: String) { + // TODO +// api.searchApi.getSearchHints() + } + } + +@Composable +fun SearchPage( + navigationManager: NavigationManager, + userPreferences: UserPreferences, + modifier: Modifier = Modifier, + viewModel: SearchViewModel = hiltViewModel(), +) { + val movies by viewModel.movies.observeAsState(listOf()) + val series by viewModel.series.observeAsState(listOf()) + val episodes by viewModel.episodes.observeAsState(listOf()) + + var query by remember { mutableStateOf("") } + val searchFocusRequester = remember { FocusRequester() } + val resultsFocusRequester = remember { FocusRequester() } + + LaunchedEffect(query) { + delay(750L) + viewModel.search(query) + } + LaunchedEffect(Unit) { + // TODO focus on back to results when available + searchFocusRequester.tryRequestFocus() + } + + LazyColumn( + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier, + ) { + item { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxWidth(), + ) { + EditTextBox( + value = query, + onValueChange = { + query = it + }, + keyboardActions = + KeyboardActions( + onSearch = { + viewModel.search(query) + }, + ), + modifier = Modifier.focusRequester(searchFocusRequester), + ) + } + } + itemsIndexed(listOf(movies, series, episodes)) { index, items -> + if (items.isNotEmpty()) { + ItemRow( + title = + when (index) { + 0 -> "Movies" + 1 -> "Series" + 2 -> "Episodes" + else -> "" + }, + items = items, + onClickItem = { + navigationManager.navigateTo(it.destination()) + }, + onLongClickItem = {}, + modifier = Modifier.focusRequester(resultsFocusRequester), + ) + } + } + } +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/nav/DestinationContent.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/DestinationContent.kt index c29734d5..c56b3df6 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/nav/DestinationContent.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/DestinationContent.kt @@ -13,6 +13,7 @@ import com.github.damontecres.dolphin.ui.detail.VideoDetails import com.github.damontecres.dolphin.ui.detail.movie.MovieDetails import com.github.damontecres.dolphin.ui.detail.series.SeriesOverview import com.github.damontecres.dolphin.ui.main.HomePage +import com.github.damontecres.dolphin.ui.main.SearchPage import com.github.damontecres.dolphin.ui.playback.PlaybackContent import com.github.damontecres.dolphin.ui.preferences.PreferenceScreenOption import com.github.damontecres.dolphin.ui.preferences.PreferencesPage @@ -124,7 +125,12 @@ fun DestinationContent( Destination.License -> LicenseInfo(modifier) - Destination.Search -> TODO() + Destination.Search -> + SearchPage( + navigationManager = navigationManager, + userPreferences = preferences, + modifier = modifier, + ) Destination.Setup -> TODO() } }