add search bar

This commit is contained in:
Owen LeJeune
2022-02-17 16:43:47 -05:00
parent c90c699a27
commit 399795cb54
7 changed files with 192 additions and 22 deletions

View File

@@ -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}"

View File

@@ -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()
}
}
}

View File

@@ -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
)
}
}
}
}

View File

@@ -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()
}
}

View File

@@ -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
)
)
}

View File

@@ -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(

View File

@@ -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
}
}