fix floating bar for 3-button nav + start work for displaying recent searches

This commit is contained in:
Owen LeJeune
2023-07-30 12:21:05 -04:00
parent fdccd0b9bf
commit a688d043ff
6 changed files with 143 additions and 9 deletions

View File

@@ -6,6 +6,7 @@ import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
@@ -96,7 +97,10 @@ class MainActivity : MonetCompatActivity() {
) { innerPadding -> ) { innerPadding ->
val windowSize = rememberWindowSizeClass() val windowSize = rememberWindowSizeClass()
val appNavController = rememberNavController() 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( AppNavigationHost(
appNavController = appNavController, appNavController = appNavController,
mainNavStartRoute = mainNavStartRoute, mainNavStartRoute = mainNavStartRoute,

View File

@@ -37,6 +37,7 @@ class AppPreferences(context: Context) {
private val SHOW_NEXT_MCU = "show_next_mcu" private val SHOW_NEXT_MCU = "show_next_mcu"
private val STORED_TEST_ROUTE = "stored_test_route" private val STORED_TEST_ROUTE = "stored_test_route"
private val FLOATING_BOTTOM_BAR = "floating_bottom_bar" 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) 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 get() = preferences.getString(STORED_TEST_ROUTE, storedTestRouteDefault) ?: storedTestRouteDefault
set(value) { preferences.put(STORED_TEST_ROUTE, value) } set(value) { preferences.put(STORED_TEST_ROUTE, value) }
/******** General Storage ********/
var recentSearches: Set<String>
get() = preferences.getStringSet(RECENT_SEARCHES, emptySet()) ?: emptySet()
set(value) { preferences.put(RECENT_SEARCHES, value) }
/********* Helpers ********/ /********* Helpers ********/
private fun SharedPreferences.put(key: String, value: Any?) { private fun SharedPreferences.put(key: String, value: Any?) {
edit().apply { edit().apply {
@@ -157,6 +163,7 @@ class AppPreferences(context: Context) {
is Float -> putFloat(key, value) is Float -> putFloat(key, value)
is Double -> putFloat(key, value.toFloat()) is Double -> putFloat(key, value.toFloat())
is String -> putString(key, value) is String -> putString(key, value)
is Set<*> -> putStringSet(key, value as Set<String>)
else -> throw UnsupportedTypeError() else -> throw UnsupportedTypeError()
} }
apply() apply()

View File

@@ -231,7 +231,11 @@ fun SearchView(
onClick = { onClick = {
appNavController.navigate(route) 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)) Icon(Icons.Filled.Search, stringResource(id = R.string.preference_heading_search))
} }

View File

@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
@@ -37,6 +38,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -62,7 +64,7 @@ import org.koin.java.KoinJavaComponent.get
object HomeScreen { object HomeScreen {
val FLOATING_NAV_BAR_HEIGHT = 80.dp val FLOATING_NAV_BAR_HEIGHT = 80.dp
val FLOATING_NAV_BAR_OFFSET = (-24).dp val FLOATING_NAV_BAR_OFFSET = (-12).dp
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -116,7 +118,10 @@ fun HomeScreen(
} }
} }
) { innerPadding -> ) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) { Box(modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
MainContent( MainContent(
windowSize = windowSize, windowSize = windowSize,
appNavController = appNavController, appNavController = appNavController,
@@ -167,9 +172,11 @@ private fun FloatingBottomNavBar(
modifier = modifier.then( modifier = modifier.then(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.height(HomeScreen.FLOATING_NAV_BAR_HEIGHT) .navigationBarsPadding()
.padding(horizontal = 24.dp) .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)) .clip(RoundedCornerShape(50))
.background(MaterialTheme.colorScheme.defaultNavBarColor()) .background(MaterialTheme.colorScheme.defaultNavBarColor())
) )
@@ -181,7 +188,9 @@ private fun FloatingBottomNavBar(
HomeScreenNavItem.SortedItems.forEach { item -> HomeScreenNavItem.SortedItems.forEach { item ->
val isSelected = currentRoute == item.route val isSelected = currentRoute == item.route
NavigationBarItem( NavigationBarItem(
modifier = Modifier.clip(RoundedCornerShape(25.dp)), modifier = Modifier
.clip(RoundedCornerShape(25.dp))
.padding(top = 4.dp),
icon = { Icon(imageVector = item.icon, contentDescription = null) }, icon = { Icon(imageVector = item.icon, contentDescription = null) },
label = { label = {
if (preferences.showBottomTabLabels) { if (preferences.showBottomTabLabels) {

View File

@@ -1,13 +1,17 @@
@file:Suppress("UNCHECKED_CAST")
package com.owenlejeune.tvtime.ui.screens package com.owenlejeune.tvtime.ui.screens
import android.content.Context import android.content.Context
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.background import androidx.compose.foundation.background
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.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.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable 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.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.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
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.paging.LoadState import androidx.paging.LoadState
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService 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.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.extensions.getCalendarYear import com.owenlejeune.tvtime.extensions.getCalendarYear
import com.owenlejeune.tvtime.extensions.lazyPagingItems 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.BackButton
import com.owenlejeune.tvtime.ui.components.MediaResultCard import com.owenlejeune.tvtime.ui.components.MediaResultCard
import com.owenlejeune.tvtime.ui.components.PillSegmentedControl 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.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 org.koin.java.KoinJavaComponent.get import org.koin.java.KoinJavaComponent.get
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -45,7 +54,8 @@ import org.koin.java.KoinJavaComponent.get
fun SearchScreen( fun SearchScreen(
appNavController: NavHostController, appNavController: NavHostController,
title: String, title: String,
mediaViewType: MediaViewType mediaViewType: MediaViewType,
preferences: AppPreferences = get(AppPreferences::class.java)
) { ) {
val searchViewModel = viewModel<SearchViewModel>() val searchViewModel = viewModel<SearchViewModel>()
val applicationViewModel = viewModel<ApplicationViewModel>() val applicationViewModel = viewModel<ApplicationViewModel>()
@@ -104,7 +114,15 @@ fun SearchScreen(
Divider(thickness = 2.dp, color = MaterialTheme.colorScheme.surfaceVariant) Divider(thickness = 2.dp, color = MaterialTheme.colorScheme.surfaceVariant)
val searchTypes = listOf(MediaViewType.MOVIE, MediaViewType.TV, MediaViewType.PERSON, MediaViewType.MIXED) 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 val context = LocalContext.current
PillSegmentedControl( PillSegmentedControl(
@@ -121,9 +139,90 @@ fun SearchScreen(
onItemSelected = { _, i -> onItemSelected = { _, i ->
selected.value = i selected.value = i
}, },
defaultSelectedItemIndex = searchTypes.indexOf(selected.value),
modifier = Modifier.padding(start = 12.dp, top = 12.dp, end = 12.dp) 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<PagingData<SortableSearchResult>>).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) { when (viewType.value) {
MediaViewType.TV -> { MediaViewType.TV -> {
TvResultsView(appNavController = appNavController, searchViewModel = searchViewModel) TvResultsView(appNavController = appNavController, searchViewModel = searchViewModel)

View File

@@ -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.SearchService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Searchable 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.SortableSearchResult
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
import com.owenlejeune.tvtime.utils.types.MediaViewType import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -41,6 +42,16 @@ class SearchViewModel: ViewModel(), KoinComponent {
} }
} }
fun produceSearchResultsFor(type: MediaViewType): MutableState<out Flow<PagingData<out SortableSearchResult>>?> {
return when (type) {
MediaViewType.MOVIE -> movieResults
MediaViewType.TV -> tvResults
MediaViewType.PERSON -> peopleResults
MediaViewType.MIXED -> multiResults
else -> throw ViewableMediaTypeException(type)
}
}
fun searchForMovies(query: String) { fun searchForMovies(query: String) {
movieResults.value = createPagingSource { service.searchMovies(query, it) } movieResults.value = createPagingSource { service.searchMovies(query, it) }
} }