finish up recent searches work

This commit is contained in:
Owen LeJeune
2023-07-30 17:09:53 -04:00
parent a688d043ff
commit 97c108f44c
4 changed files with 54 additions and 237 deletions

View File

@@ -2,6 +2,7 @@ package com.owenlejeune.tvtime.preferences
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.compose.runtime.toMutableStateList
import com.google.gson.Gson import com.google.gson.Gson
import com.kieronquinn.monetcompat.core.MonetCompat import com.kieronquinn.monetcompat.core.MonetCompat
import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.utils.SessionManager
@@ -149,9 +150,9 @@ class AppPreferences(context: Context) {
set(value) { preferences.put(STORED_TEST_ROUTE, value) } set(value) { preferences.put(STORED_TEST_ROUTE, value) }
/******** General Storage ********/ /******** General Storage ********/
var recentSearches: Set<String> var recentSearches: MutableList<String>
get() = preferences.getStringSet(RECENT_SEARCHES, emptySet()) ?: emptySet() get() = preferences.getStringSet(RECENT_SEARCHES, emptySet())?.toMutableList() ?: emptySet<String>().toMutableList()
set(value) { preferences.put(RECENT_SEARCHES, value) } set(value) { preferences.put(RECENT_SEARCHES, value.toSet()) }
/********* Helpers ********/ /********* Helpers ********/
private fun SharedPreferences.put(key: String, value: Any?) { private fun SharedPreferences.put(key: String, value: Any?) {

View File

@@ -37,7 +37,8 @@ fun MediaResultCard(
title: String, title: String,
additionalDetails: List<String>, additionalDetails: List<String>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
rating: Float? = null rating: Float? = null,
additionalOnClick: () -> Unit = {}
) { ) {
Card( Card(
shape = RoundedCornerShape(10.dp), shape = RoundedCornerShape(10.dp),
@@ -47,6 +48,7 @@ fun MediaResultCard(
.fillMaxWidth() .fillMaxWidth()
.clickable( .clickable(
onClick = { onClick = {
additionalOnClick()
appNavController.navigate( appNavController.navigate(
AppNavItem.DetailView.withArgs(mediaViewType, id) AppNavItem.DetailView.withArgs(mediaViewType, id)
) )

View File

@@ -9,6 +9,8 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.outlined.Restore import androidx.compose.material.icons.outlined.Restore
@@ -22,6 +24,7 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@@ -45,7 +48,6 @@ import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
import com.owenlejeune.tvtime.ui.viewmodel.SearchViewModel import com.owenlejeune.tvtime.ui.viewmodel.SearchViewModel
import com.owenlejeune.tvtime.utils.TmdbUtils import com.owenlejeune.tvtime.utils.TmdbUtils
import com.owenlejeune.tvtime.utils.types.MediaViewType import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.koin.java.KoinJavaComponent.get import org.koin.java.KoinJavaComponent.get
@@ -100,11 +102,20 @@ fun SearchScreen(
singleLine = true, singleLine = true,
trailingIcon = { trailingIcon = {
if (searchValue.value.isNotEmpty()) { if (searchValue.value.isNotEmpty()) {
IconButton(onClick = { searchValue.value = "" }) { IconButton(
Icon(imageVector = Icons.Filled.Clear, contentDescription = "Clear search query") onClick = { searchValue.value = "" }
) {
Icon(imageVector = Icons.Filled.Clear, contentDescription = stringResource(R.string.clear_search_query)
)
} }
} }
} },
keyboardActions = KeyboardActions(
onSearch = { storeSearchValue(searchValue.value, preferences) }
),
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Search
)
) )
}, },
navigationIcon = { navigationIcon = {
@@ -179,30 +190,32 @@ fun SearchScreen(
MediaViewType.MOVIE -> { MediaViewType.MOVIE -> {
MovieSearchResultView( MovieSearchResultView(
appNavController = appNavController, appNavController = appNavController,
result = item as SearchResultMovie result = item as SearchResultMovie,
additionalOnClick = { storeSearchValue(searchValue.value, preferences) }
) )
} }
MediaViewType.TV -> { MediaViewType.TV -> {
TvSearchResultView( TvSearchResultView(
appNavController = appNavController, appNavController = appNavController,
result = item as SearchResultTv result = item as SearchResultTv,
additionalOnClick = { storeSearchValue(searchValue.value, preferences) }
) )
} }
MediaViewType.PERSON -> { MediaViewType.PERSON -> {
PeopleSearchResultView( PeopleSearchResultView(
appNavController = appNavController, appNavController = appNavController,
result = item as SearchResultPerson result = item as SearchResultPerson,
additionalOnClick = { storeSearchValue(searchValue.value, preferences) }
) )
} }
else -> {} else -> {}
} }
} }
item { Spacer(modifier = Modifier.height(12.dp)) } item { Spacer(modifier = Modifier.height(12.dp)) }
handleLoadState(context, pagingItems.loadState.refresh) handleLoadState(context, pagingItems.loadState.append)
} }
} ?: run { } ?: run {
listOf("Michael Meyers", "Mutant", "Deadpool", "Ryan Reynolds", "Boardwalk Empire")//preferences.recentSearches preferences.recentSearches.forEach {
.forEach {
Row( Row(
modifier = Modifier modifier = Modifier
.padding(vertical = 12.dp, horizontal = 16.dp) .padding(vertical = 12.dp, horizontal = 16.dp)
@@ -223,229 +236,12 @@ fun SearchScreen(
} }
} }
when (viewType.value) {
MediaViewType.TV -> {
TvResultsView(appNavController = appNavController, searchViewModel = searchViewModel)
}
MediaViewType.MOVIE -> {
MovieResultsView(appNavController = appNavController, searchViewModel = searchViewModel)
}
MediaViewType.PERSON -> {
PeopleResultsView(appNavController = appNavController, searchViewModel = searchViewModel)
}
MediaViewType.MIXED -> {
MultiResultsView(appNavController = appNavController, searchViewModel = searchViewModel)
}
else -> {}
}
LaunchedEffect(key1 = "") { LaunchedEffect(key1 = "") {
focusRequester.requestFocus() focusRequester.requestFocus()
} }
} }
} }
@Composable
private fun MovieResultsView(
appNavController: NavHostController,
searchViewModel: SearchViewModel
) {
val context = LocalContext.current
val results = remember { searchViewModel.movieResults }
results.value?.let {
val pagingItems = it.collectAsLazyPagingItems()
if (pagingItems.itemCount > 0) {
LazyColumn(
modifier = Modifier.padding(all = 12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
handleLoadState(context, pagingItems.loadState.refresh)
lazyPagingItems(
lazyPagingItems = pagingItems,
key = { i -> pagingItems[i]!!.id }
) { item ->
item?.let {
MovieSearchResultView(
appNavController = appNavController,
result = item
)
}
}
handleLoadState(context, pagingItems.loadState.append)
}
} else {
Column(
modifier = Modifier.fillMaxSize()
) {
Spacer(modifier = Modifier.weight(1f))
Text(
text = stringResource(R.string.no_search_results),
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.align(Alignment.CenterHorizontally),
fontSize = 18.sp
)
Spacer(modifier = Modifier.weight(1f))
}
}
}
}
@Composable
private fun TvResultsView(
appNavController: NavHostController,
searchViewModel: SearchViewModel
) {
val context = LocalContext.current
val results = remember { searchViewModel.tvResults }
results.value?.let {
val pagingItems = it.collectAsLazyPagingItems()
if (pagingItems.itemCount > 0) {
LazyColumn(
modifier = Modifier.padding(all = 12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
handleLoadState(context, pagingItems.loadState.refresh)
lazyPagingItems(
lazyPagingItems = pagingItems,
key = { i -> pagingItems[i]!!.id }
) { item ->
item?.let {
TvSearchResultView(
appNavController = appNavController,
result = item
)
}
}
handleLoadState(context, pagingItems.loadState.append)
}
} else {
Column(
modifier = Modifier.fillMaxSize()
) {
Spacer(modifier = Modifier.weight(1f))
Text(
text = stringResource(R.string.no_search_results),
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.align(Alignment.CenterHorizontally),
fontSize = 18.sp
)
Spacer(modifier = Modifier.weight(1f))
}
}
}
}
@Composable
private fun PeopleResultsView(
appNavController: NavHostController,
searchViewModel: SearchViewModel
) {
val context = LocalContext.current
val results = remember { searchViewModel.peopleResults }
results.value?.let {
val pagingItems = it.collectAsLazyPagingItems()
if (pagingItems.itemCount > 0) {
LazyColumn(
modifier = Modifier.padding(all = 12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
handleLoadState(context, pagingItems.loadState.refresh)
lazyPagingItems(
lazyPagingItems = pagingItems,
key = { i -> pagingItems[i]!!.id }
) { item ->
item?.let {
PeopleSearchResultView(
appNavController = appNavController,
result = item
)
}
}
handleLoadState(context, pagingItems.loadState.append)
}
} else {
Column(
modifier = Modifier.fillMaxSize()
) {
Spacer(modifier = Modifier.weight(1f))
Text(
text = stringResource(R.string.no_search_results),
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.align(Alignment.CenterHorizontally),
fontSize = 18.sp
)
Spacer(modifier = Modifier.weight(1f))
}
}
}
}
@Composable
private fun MultiResultsView(
appNavController: NavHostController,
searchViewModel: SearchViewModel
) {
val context = LocalContext.current
val results = remember { searchViewModel.multiResults }
results.value?.let {
val pagingItems = it.collectAsLazyPagingItems()
if (pagingItems.itemCount > 0) {
LazyColumn(
modifier = Modifier.padding(all = 12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
handleLoadState(context, pagingItems.loadState.refresh)
lazyPagingItems(
lazyPagingItems = pagingItems,
key = { i -> pagingItems[i]!!.id }
) { item ->
item?.let {
when (item.mediaType) {
MediaViewType.MOVIE -> {
MovieSearchResultView(
appNavController = appNavController,
result = item as SearchResultMovie
)
}
MediaViewType.TV -> {
TvSearchResultView(
appNavController = appNavController,
result = item as SearchResultTv
)
}
MediaViewType.PERSON -> {
PeopleSearchResultView(
appNavController = appNavController,
result = item as SearchResultPerson
)
}
else ->{}
}
}
}
handleLoadState(context, pagingItems.loadState.append)
}
} else {
Column(
modifier = Modifier.fillMaxSize()
) {
Spacer(modifier = Modifier.weight(1f))
Text(
text = stringResource(R.string.no_search_results),
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.align(Alignment.CenterHorizontally),
fontSize = 18.sp
)
Spacer(modifier = Modifier.weight(1f))
}
}
}
}
private fun LazyListScope.handleLoadState(context: Context, state: LoadState) { private fun LazyListScope.handleLoadState(context: Context, state: LoadState) {
when (state) { when (state) {
is LoadState.Loading -> { is LoadState.Loading -> {
@@ -470,7 +266,8 @@ private fun <T: SortableSearchResult> SearchResultItemView(
searchResult: T, searchResult: T,
posterModel: (T) -> Any?, posterModel: (T) -> Any?,
backdropModel: (T) -> Any?, backdropModel: (T) -> Any?,
additionalDetails: (T) -> List<String> = { emptyList() } additionalDetails: (T) -> List<String> = { emptyList() },
additionalOnClick: () -> Unit = {}
) { ) {
MediaResultCard( MediaResultCard(
appNavController = appNavController, appNavController = appNavController,
@@ -479,7 +276,8 @@ private fun <T: SortableSearchResult> SearchResultItemView(
backdropPath = backdropModel(searchResult), backdropPath = backdropModel(searchResult),
posterPath = posterModel(searchResult), posterPath = posterModel(searchResult),
title = searchResult.title, title = searchResult.title,
additionalDetails = additionalDetails(searchResult) additionalDetails = additionalDetails(searchResult),
additionalOnClick = additionalOnClick
) )
} }
@@ -487,6 +285,7 @@ private fun <T: SortableSearchResult> SearchResultItemView(
private fun MovieSearchResultView( private fun MovieSearchResultView(
appNavController: NavHostController, appNavController: NavHostController,
result: SearchResultMovie, result: SearchResultMovie,
additionalOnClick: () -> Unit = {},
service: MoviesService = get(MoviesService::class.java) service: MoviesService = get(MoviesService::class.java)
) { ) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -507,7 +306,8 @@ private fun MovieSearchResultView(
result.releaseDate?.getCalendarYear()?.toString() ?: "", result.releaseDate?.getCalendarYear()?.toString() ?: "",
cast?.joinToString(separator = ", ") { it.name } ?: "" cast?.joinToString(separator = ", ") { it.name } ?: ""
) )
} },
additionalOnClick = additionalOnClick
) )
} }
@@ -515,6 +315,7 @@ private fun MovieSearchResultView(
private fun TvSearchResultView( private fun TvSearchResultView(
appNavController: NavHostController, appNavController: NavHostController,
result: SearchResultTv, result: SearchResultTv,
additionalOnClick: () -> Unit = {},
service: TvService = get(TvService::class.java) service: TvService = get(TvService::class.java)
) { ) {
val context = LocalContext.current val context = LocalContext.current
@@ -536,14 +337,16 @@ private fun TvSearchResultView(
"${result.releaseDate?.getCalendarYear() ?: ""} ${context.getString(R.string.search_result_tv_series)}", "${result.releaseDate?.getCalendarYear() ?: ""} ${context.getString(R.string.search_result_tv_series)}",
cast?.joinToString(separator = ", ") { it.name } ?: "" cast?.joinToString(separator = ", ") { it.name } ?: ""
) )
} },
additionalOnClick = additionalOnClick
) )
} }
@Composable @Composable
private fun PeopleSearchResultView( private fun PeopleSearchResultView(
appNavController: NavHostController, appNavController: NavHostController,
result: SearchResultPerson result: SearchResultPerson,
additionalOnClick: () -> Unit = {}
) { ) {
val mostKnownFor = result.knownFor.sortedBy { it.popularity }.takeUnless { it.isEmpty() }?.get(0) val mostKnownFor = result.knownFor.sortedBy { it.popularity }.takeUnless { it.isEmpty() }?.get(0)
@@ -559,6 +362,16 @@ private fun PeopleSearchResultView(
searchResult = result, searchResult = result,
posterModel = { TmdbUtils.getFullPersonImagePath(result.posterPath) }, posterModel = { TmdbUtils.getFullPersonImagePath(result.posterPath) },
backdropModel = { TmdbUtils.getFullBackdropPath(mostKnownFor?.backdropPath) }, backdropModel = { TmdbUtils.getFullBackdropPath(mostKnownFor?.backdropPath) },
additionalDetails = { additional } additionalDetails = { additional },
additionalOnClick = additionalOnClick
) )
}
private fun storeSearchValue(search: String, preferences: AppPreferences) {
val recentSearches = preferences.recentSearches
recentSearches.add(search)
while (recentSearches.size > 5) {
recentSearches.removeAt(0)
}
preferences.recentSearches = recentSearches
} }

View File

@@ -264,4 +264,5 @@
<string name="guest_stars_label">Guest stars</string> <string name="guest_stars_label">Guest stars</string>
<string name="your_rating">Your rating: %1$d/10</string> <string name="your_rating">Your rating: %1$d/10</string>
<string name="uncredited">Uncredited</string> <string name="uncredited">Uncredited</string>
<string name="clear_search_query">Clear search query</string>
</resources> </resources>