diff --git a/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt b/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt index 26a75dd..6a883e9 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt @@ -6,6 +6,7 @@ import android.os.Bundle import android.widget.Toast import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost @@ -96,7 +97,10 @@ class MainActivity : MonetCompatActivity() { ) { innerPadding -> val windowSize = rememberWindowSizeClass() val appNavController = rememberNavController() - Box(modifier = Modifier.padding(innerPadding.copy(bottom = 0.dp, top = 0.dp))) { + Box(modifier = Modifier + .padding(innerPadding.copy(bottom = 0.dp, top = 0.dp)) + .fillMaxSize() + ) { AppNavigationHost( appNavController = appNavController, mainNavStartRoute = mainNavStartRoute, diff --git a/app/src/main/java/com/owenlejeune/tvtime/preferences/AppPreferences.kt b/app/src/main/java/com/owenlejeune/tvtime/preferences/AppPreferences.kt index 4aed1bf..3f2a407 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/preferences/AppPreferences.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/preferences/AppPreferences.kt @@ -37,6 +37,7 @@ class AppPreferences(context: Context) { private val SHOW_NEXT_MCU = "show_next_mcu" private val STORED_TEST_ROUTE = "stored_test_route" private val FLOATING_BOTTOM_BAR = "floating_bottom_bar" + private val RECENT_SEARCHES = "recent_searches" } private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE) @@ -147,6 +148,11 @@ class AppPreferences(context: Context) { get() = preferences.getString(STORED_TEST_ROUTE, storedTestRouteDefault) ?: storedTestRouteDefault set(value) { preferences.put(STORED_TEST_ROUTE, value) } + /******** General Storage ********/ + var recentSearches: Set + get() = preferences.getStringSet(RECENT_SEARCHES, emptySet()) ?: emptySet() + set(value) { preferences.put(RECENT_SEARCHES, value) } + /********* Helpers ********/ private fun SharedPreferences.put(key: String, value: Any?) { edit().apply { @@ -157,6 +163,7 @@ class AppPreferences(context: Context) { is Float -> putFloat(key, value) is Double -> putFloat(key, value.toFloat()) is String -> putString(key, value) + is Set<*> -> putStringSet(key, value as Set) else -> throw UnsupportedTypeError() } apply() diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt index a870644..7d42836 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt @@ -231,7 +231,11 @@ fun SearchView( onClick = { appNavController.navigate(route) }, - modifier = Modifier.offset(y = if (preferences.floatingBottomBar) -(HomeScreen.FLOATING_NAV_BAR_HEIGHT - HomeScreen.FLOATING_NAV_BAR_OFFSET) else 0.dp) + modifier = Modifier + .navigationBarsPadding() + .offset( + y = if (preferences.floatingBottomBar) -(HomeScreen.FLOATING_NAV_BAR_HEIGHT - HomeScreen.FLOATING_NAV_BAR_OFFSET) else 0.dp + ) ) { Icon(Icons.Filled.Search, stringResource(id = R.string.preference_heading_search)) } diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/HomeScreen.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/HomeScreen.kt index 3f602aa..e40867d 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/HomeScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight @@ -37,6 +38,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext @@ -62,7 +64,7 @@ import org.koin.java.KoinJavaComponent.get object HomeScreen { val FLOATING_NAV_BAR_HEIGHT = 80.dp - val FLOATING_NAV_BAR_OFFSET = (-24).dp + val FLOATING_NAV_BAR_OFFSET = (-12).dp } @OptIn(ExperimentalMaterial3Api::class) @@ -116,7 +118,10 @@ fun HomeScreen( } } ) { innerPadding -> - Box(modifier = Modifier.padding(innerPadding)) { + Box(modifier = Modifier + .padding(innerPadding) + .fillMaxSize() + ) { MainContent( windowSize = windowSize, appNavController = appNavController, @@ -167,9 +172,11 @@ private fun FloatingBottomNavBar( modifier = modifier.then( Modifier .fillMaxWidth() - .height(HomeScreen.FLOATING_NAV_BAR_HEIGHT) + .navigationBarsPadding() .padding(horizontal = 24.dp) - .offset(y = HomeScreen.FLOATING_NAV_BAR_OFFSET) + .padding(bottom = 12.dp) + .height(HomeScreen.FLOATING_NAV_BAR_HEIGHT) +// .offset(y = HomeScreen.FLOATING_NAV_BAR_OFFSET) .clip(RoundedCornerShape(50)) .background(MaterialTheme.colorScheme.defaultNavBarColor()) ) @@ -181,7 +188,9 @@ private fun FloatingBottomNavBar( HomeScreenNavItem.SortedItems.forEach { item -> val isSelected = currentRoute == item.route NavigationBarItem( - modifier = Modifier.clip(RoundedCornerShape(25.dp)), + modifier = Modifier + .clip(RoundedCornerShape(25.dp)) + .padding(top = 4.dp), icon = { Icon(imageVector = item.icon, contentDescription = null) }, label = { if (preferences.showBottomTabLabels) { diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/SearchScreen.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/SearchScreen.kt index 776b6f6..4bd1bab 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/SearchScreen.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/SearchScreen.kt @@ -1,13 +1,17 @@ +@file:Suppress("UNCHECKED_CAST") + package com.owenlejeune.tvtime.ui.screens import android.content.Context import android.widget.Toast import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear +import androidx.compose.material.icons.outlined.Restore import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -17,11 +21,13 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.paging.LoadState +import androidx.paging.PagingData import androidx.paging.compose.collectAsLazyPagingItems import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService @@ -29,6 +35,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* import com.owenlejeune.tvtime.extensions.getCalendarYear import com.owenlejeune.tvtime.extensions.lazyPagingItems +import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.ui.components.BackButton import com.owenlejeune.tvtime.ui.components.MediaResultCard import com.owenlejeune.tvtime.ui.components.PillSegmentedControl @@ -38,6 +45,8 @@ import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel import com.owenlejeune.tvtime.ui.viewmodel.SearchViewModel import com.owenlejeune.tvtime.utils.TmdbUtils import com.owenlejeune.tvtime.utils.types.MediaViewType +import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException +import kotlinx.coroutines.flow.Flow import org.koin.java.KoinJavaComponent.get @OptIn(ExperimentalMaterial3Api::class) @@ -45,7 +54,8 @@ import org.koin.java.KoinJavaComponent.get fun SearchScreen( appNavController: NavHostController, title: String, - mediaViewType: MediaViewType + mediaViewType: MediaViewType, + preferences: AppPreferences = get(AppPreferences::class.java) ) { val searchViewModel = viewModel() val applicationViewModel = viewModel() @@ -104,7 +114,15 @@ fun SearchScreen( Divider(thickness = 2.dp, color = MaterialTheme.colorScheme.surfaceVariant) val searchTypes = listOf(MediaViewType.MOVIE, MediaViewType.TV, MediaViewType.PERSON, MediaViewType.MIXED) - val selected = remember { mutableStateOf(searchTypes[0]) } + val selected = remember { + mutableStateOf( + if (preferences.multiSearch) { + MediaViewType.MIXED + } else { + mediaViewType + } + ) + } val context = LocalContext.current PillSegmentedControl( @@ -121,9 +139,90 @@ fun SearchScreen( onItemSelected = { _, i -> selected.value = i }, + defaultSelectedItemIndex = searchTypes.indexOf(selected.value), modifier = Modifier.padding(start = 12.dp, top = 12.dp, end = 12.dp) ) + Spacer(modifier = Modifier.height(12.dp)) + + val results = remember { searchViewModel.produceSearchResultsFor(viewType.value) } + results.value?.let { + val pagingItems = (results.value as Flow>).collectAsLazyPagingItems() + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + handleLoadState(context, pagingItems.loadState.refresh) + item { + if (pagingItems.itemCount == 0) { + 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)) + } + } + } + lazyPagingItems( + lazyPagingItems = pagingItems, + key = { i -> pagingItems[i]?.id ?: -1 } + ) { item -> + 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 -> {} + } + } + item { Spacer(modifier = Modifier.height(12.dp)) } + handleLoadState(context, pagingItems.loadState.refresh) + } + } ?: run { + listOf("Michael Meyers", "Mutant", "Deadpool", "Ryan Reynolds", "Boardwalk Empire")//preferences.recentSearches + .forEach { + Row( + modifier = Modifier + .padding(vertical = 12.dp, horizontal = 16.dp) + .clickable { + searchValue.value = it + }, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Icon( + imageVector = Icons.Outlined.Restore, + contentDescription = null + ) + Text( + text = it, + fontStyle = FontStyle.Italic + ) + } + } + } + when (viewType.value) { MediaViewType.TV -> { TvResultsView(appNavController = appNavController, searchViewModel = searchViewModel) diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/SearchViewModel.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/SearchViewModel.kt index d113216..1af02fa 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/SearchViewModel.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/SearchViewModel.kt @@ -9,6 +9,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchResultProvider import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchService import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Searchable import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SortableSearchResult +import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem import com.owenlejeune.tvtime.utils.types.MediaViewType import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException import kotlinx.coroutines.flow.Flow @@ -41,6 +42,16 @@ class SearchViewModel: ViewModel(), KoinComponent { } } + fun produceSearchResultsFor(type: MediaViewType): MutableState>?> { + return when (type) { + MediaViewType.MOVIE -> movieResults + MediaViewType.TV -> tvResults + MediaViewType.PERSON -> peopleResults + MediaViewType.MIXED -> multiResults + else -> throw ViewableMediaTypeException(type) + } + } + fun searchForMovies(query: String) { movieResults.value = createPagingSource { service.searchMovies(query, it) } }