mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-16 00:30:54 -05:00
add search bar
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import com.owenlejeune.tvtime.buildsrc.Config
|
||||
import com.owenlejeune.tvtime.buildsrc.Versions
|
||||
import com.owenlejeune.tvtime.buildsrc.*
|
||||
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
@@ -84,6 +83,8 @@ dependencies {
|
||||
//Coroutines
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
|
||||
|
||||
implementation "me.onebone:toolbar-compose:2.3.1"
|
||||
|
||||
testImplementation "junit:junit:${Versions.junit}"
|
||||
androidTestImplementation "androidx.test.ext:junit:${Versions.androidx_junit}"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:${Versions.espresso_core}"
|
||||
|
||||
@@ -4,22 +4,22 @@ import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.owenlejeune.tvtime.ui.navigation.MainNavigationRoutes
|
||||
import com.owenlejeune.tvtime.ui.theme.TVTimeTheme
|
||||
import com.owenlejeune.tvtime.utils.KeyBoardManager
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
// private val appNavControllerProvider: (@Composable () -> NavHostController) by inject(named(NavControllers.APP))
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
AppKeyboardFocusManager()
|
||||
val displayUnderStatusBar = remember { mutableStateOf(false) }
|
||||
// WindowCompat.setDecorFitsSystemWindows(window, !displayUnderStatusBar.value)
|
||||
// val statusBarColor = if (displayUnderStatusBar.value) {
|
||||
@@ -53,4 +53,19 @@ fun MyApp(
|
||||
@Composable
|
||||
fun MyAppPreview() {
|
||||
MyApp()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppKeyboardFocusManager() {
|
||||
val context = LocalContext.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
DisposableEffect(key1 = context) {
|
||||
val keyboardManager = KeyBoardManager(context)
|
||||
keyboardManager.attachKeyboardDismissListener {
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
onDispose {
|
||||
keyboardManager.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,15 @@ package com.owenlejeune.tvtime.ui.components
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.selection.toggleable
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
@@ -23,6 +26,7 @@ import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
@@ -294,8 +298,57 @@ fun RatingRing(
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun RatingRingPreview() {
|
||||
RatingRing(progress = 0.5f)
|
||||
fun RoundedTextField(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
placeHolder: String = "",
|
||||
placeHolderTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
backgroundColor: Color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
textStyle: TextStyle = MaterialTheme.typography.bodySmall,
|
||||
cursorColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
singleLine: Boolean = true,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(50.dp),
|
||||
color = backgroundColor
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
if (value.isEmpty() && placeHolder.isNotEmpty()) {
|
||||
Text(
|
||||
text = placeHolder,
|
||||
style = textStyle,
|
||||
color = placeHolderTextColor
|
||||
)
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
BasicTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
singleLine = singleLine,
|
||||
textStyle = textStyle.copy(color = textColor),
|
||||
cursorBrush = SolidColor(cursorColor),
|
||||
maxLines = maxLines,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,19 +48,24 @@ fun MainNavigationRoutes(navController: NavHostController, displayUnderStatusBar
|
||||
@Composable
|
||||
fun BottomNavigationRoutes(
|
||||
appNavController: NavHostController,
|
||||
navController: NavHostController
|
||||
navController: NavHostController,
|
||||
shouldShowSearch: MutableState<Boolean>
|
||||
) {
|
||||
NavHost(navController = navController, startDestination = BottomNavItem.Movies.route) {
|
||||
composable(BottomNavItem.Movies.route) {
|
||||
shouldShowSearch.value = true
|
||||
MediaTab(appNavController = appNavController, mediaType = MediaViewType.MOVIE)
|
||||
}
|
||||
composable(BottomNavItem.TV.route) {
|
||||
shouldShowSearch.value = true
|
||||
MediaTab(appNavController = appNavController, mediaType = MediaViewType.TV)
|
||||
}
|
||||
composable(BottomNavItem.Favourites.route) {
|
||||
shouldShowSearch.value = false
|
||||
FavouritesTab()
|
||||
}
|
||||
composable(BottomNavItem.Settings.route) {
|
||||
shouldShowSearch.value = false
|
||||
SettingsTab()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
package com.owenlejeune.tvtime.ui.screens
|
||||
|
||||
import androidx.compose.animation.rememberSplineBasedDecay
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.owenlejeune.tvtime.ui.components.RoundedTextField
|
||||
import com.owenlejeune.tvtime.ui.components.SearchFab
|
||||
import com.owenlejeune.tvtime.ui.navigation.BottomNavItem
|
||||
import com.owenlejeune.tvtime.ui.navigation.BottomNavigationRoutes
|
||||
@@ -31,6 +34,8 @@ fun MainAppView(appNavController: NavHostController) {
|
||||
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec)
|
||||
}
|
||||
|
||||
val shouldShowSearch = remember { mutableStateOf(true) }
|
||||
|
||||
// todo - scroll state not remember when returing from detail screen
|
||||
|
||||
Scaffold(
|
||||
@@ -45,7 +50,8 @@ fun MainAppView(appNavController: NavHostController) {
|
||||
topBar = {
|
||||
TopBar(
|
||||
title = appBarTitle,
|
||||
scrollBehavior = scrollBehavior
|
||||
scrollBehavior = scrollBehavior,
|
||||
shouldShowSearch = shouldShowSearch
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
@@ -55,16 +61,53 @@ fun MainAppView(appNavController: NavHostController) {
|
||||
}
|
||||
) { innerPadding ->
|
||||
Box(modifier = Modifier.padding(innerPadding)) {
|
||||
BottomNavigationRoutes(appNavController = appNavController, navController = navController)
|
||||
BottomNavigationRoutes(appNavController = appNavController, navController = navController, shouldShowSearch = shouldShowSearch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopBar(title: MutableState<String>, scrollBehavior: TopAppBarScrollBehavior) {
|
||||
LargeTopAppBar(
|
||||
title = { Text(text = title.value) },
|
||||
scrollBehavior = scrollBehavior
|
||||
private fun TopBar(
|
||||
title: MutableState<String>,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
hasSearchFocus: MutableState<Boolean> = remember { mutableStateOf(false) },
|
||||
shouldShowSearch: MutableState<Boolean> = remember { mutableStateOf(true) }
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (!hasSearchFocus.value) {
|
||||
Text(text = title.value)
|
||||
}
|
||||
if (shouldShowSearch.value) {
|
||||
var textState by remember { mutableStateOf("") }
|
||||
val basePadding = 8.dp
|
||||
RoundedTextField(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
end = if (hasSearchFocus.value) (basePadding * 2) else basePadding,
|
||||
start = if (hasSearchFocus.value) 0.dp else basePadding
|
||||
)
|
||||
|
||||
.height(35.dp)
|
||||
.onFocusChanged { focusState ->
|
||||
hasSearchFocus.value = focusState.isFocused
|
||||
},
|
||||
value = textState,
|
||||
onValueChange = { textState = it },
|
||||
placeHolder = "Search ${title.value.lowercase()}"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
colors = TopAppBarDefaults
|
||||
.largeTopAppBarColors(
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
||||
titleContentColor = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,11 @@ fun SettingsTab(preferences: AppPreferences = get(AppPreferences::class.java)) {
|
||||
val shouldShowPalette = remember { mutableStateOf(false) }
|
||||
Text(
|
||||
text = "Show material palette",
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
color = if (usePreferences.value) {
|
||||
MaterialTheme.colorScheme.onBackground
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(12.dp)
|
||||
.clickable(
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.owenlejeune.tvtime.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
|
||||
class KeyBoardManager(context: Context) {
|
||||
|
||||
private val activity = context as Activity
|
||||
private var keyboardDismissListener: KeyboardDismissListener? = null
|
||||
|
||||
private abstract class KeyboardDismissListener(
|
||||
private val rootView: View,
|
||||
private val onKeyboardDismiss: () -> Unit
|
||||
) : ViewTreeObserver.OnGlobalLayoutListener {
|
||||
private var isKeyboardClosed: Boolean = false
|
||||
override fun onGlobalLayout() {
|
||||
val r = Rect()
|
||||
rootView.getWindowVisibleDisplayFrame(r)
|
||||
val screenHeight = rootView.rootView.height
|
||||
val keypadHeight = screenHeight - r.bottom
|
||||
if (keypadHeight > screenHeight * 0.15) {
|
||||
// 0.15 ratio is right enough to determine keypad height.
|
||||
isKeyboardClosed = false
|
||||
} else if (!isKeyboardClosed) {
|
||||
isKeyboardClosed = true
|
||||
onKeyboardDismiss.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun attachKeyboardDismissListener(onKeyboardDismiss: () -> Unit) {
|
||||
val rootView = activity.findViewById<View>(android.R.id.content)
|
||||
keyboardDismissListener = object : KeyboardDismissListener(rootView, onKeyboardDismiss) {}
|
||||
keyboardDismissListener?.let {
|
||||
rootView.viewTreeObserver.addOnGlobalLayoutListener(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
val rootView = activity.findViewById<View>(android.R.id.content)
|
||||
keyboardDismissListener?.let {
|
||||
rootView.viewTreeObserver.removeOnGlobalLayoutListener(it)
|
||||
}
|
||||
keyboardDismissListener = null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user