show/hide search bar with fab and settings

This commit is contained in:
Owen LeJeune
2022-02-18 17:17:57 -05:00
parent 0da9f01551
commit e346f1ebd7
4 changed files with 73 additions and 24 deletions

View File

@@ -12,7 +12,7 @@ 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
import com.owenlejeune.tvtime.utils.KeyboardManager
class MainActivity : ComponentActivity() {
@@ -60,7 +60,7 @@ private fun AppKeyboardFocusManager() {
val context = LocalContext.current
val focusManager = LocalFocusManager.current
DisposableEffect(key1 = context) {
val keyboardManager = KeyBoardManager(context)
val keyboardManager = KeyboardManager.getInstance(context)
keyboardManager.attachKeyboardDismissListener {
focusManager.clearFocus()
}

View File

@@ -18,13 +18,12 @@ import androidx.compose.material.Switch
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.*
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
@@ -274,10 +273,15 @@ fun TopLevelSwitchPreview() {
}
@Composable
fun SearchFab() {
fun SearchFab(
focusSearchBar: MutableState<Boolean> = mutableStateOf(false),
focusRequester: FocusRequester = remember { FocusRequester() }
) {
val context = LocalContext.current
FloatingActionButton(onClick = {
Toast.makeText(context, "Search Clicked!", Toast.LENGTH_SHORT).show()
focusSearchBar.value = true
// focusRequester.requestFocus()
// Toast.makeText(context, "Search Clicked!", Toast.LENGTH_SHORT).show()
}) {
Icon(Icons.Filled.Search, "")
}
@@ -422,6 +426,8 @@ fun RoundedTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
focusRequester: FocusRequester = remember { FocusRequester() },
requestFocus: Boolean = false,
placeHolder: String = "",
placeHolderTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -455,7 +461,9 @@ fun RoundedTextField(
verticalAlignment = Alignment.CenterVertically
) {
BasicTextField(
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.focusRequester(focusRequester),
value = value,
onValueChange = onValueChange,
singleLine = singleLine,
@@ -470,4 +478,10 @@ fun RoundedTextField(
}
}
}
if (requestFocus) {
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
}

View File

@@ -10,8 +10,10 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
@@ -24,6 +26,7 @@ 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
import com.owenlejeune.tvtime.utils.KeyboardManager
import org.koin.java.KoinJavaComponent.get
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class)
@@ -39,7 +42,8 @@ fun MainAppView(appNavController: NavHostController, preferences: AppPreferences
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec)
}
val shouldShowSearch = remember { mutableStateOf(false) }
val focusRequester = remember { FocusRequester() }
val focusSearchBar = remember { mutableStateOf(false) }
val searchableScreens = listOf(BottomNavItem.Movies.route, BottomNavItem.TV.route)
// todo - scroll state not remember when returing from detail screen
@@ -58,7 +62,8 @@ fun MainAppView(appNavController: NavHostController, preferences: AppPreferences
SearchTopBar(
title = appBarTitle,
scrollBehavior = scrollBehavior,
shouldShowSearch = shouldShowSearch
requestSearchFocus = focusSearchBar,
focusRequester = focusRequester
)
} else {
TopBar(
@@ -68,8 +73,11 @@ fun MainAppView(appNavController: NavHostController, preferences: AppPreferences
}
},
floatingActionButton = {
if (currentRoute in searchableScreens && !preferences.persistentSearch) {
SearchFab()
if (currentRoute in searchableScreens && !preferences.persistentSearch && !focusSearchBar.value) {
SearchFab(
focusSearchBar = focusSearchBar,
focusRequester = focusRequester
)
}
}
) { innerPadding ->
@@ -99,8 +107,8 @@ private fun TopBar(
private fun SearchTopBar(
title: MutableState<String>,
scrollBehavior: TopAppBarScrollBehavior,
hasSearchFocus: MutableState<Boolean> = remember { mutableStateOf(false) },
shouldShowSearch: MutableState<Boolean> = remember { mutableStateOf(false) },
requestSearchFocus: MutableState<Boolean> = remember { mutableStateOf(false) },
focusRequester: FocusRequester = remember { FocusRequester() },
preferences: AppPreferences = get(AppPreferences::class.java)
) {
SmallTopAppBar(
@@ -108,10 +116,11 @@ private fun SearchTopBar(
Row(
verticalAlignment = Alignment.CenterVertically
) {
if (!hasSearchFocus.value && !(preferences.persistentSearch && preferences.hideTitle)) {
val hasSearchFocus = remember { mutableStateOf(requestSearchFocus.value) }
if (!requestSearchFocus.value && !hasSearchFocus.value && !(preferences.persistentSearch && preferences.hideTitle)) {
Text(text = title.value)
}
if (shouldShowSearch.value || preferences.persistentSearch) {
if (requestSearchFocus.value || preferences.persistentSearch) {
var textState by remember { mutableStateOf("") }
val basePadding = 8.dp
RoundedTextField(
@@ -120,11 +129,12 @@ private fun SearchTopBar(
end = if (hasSearchFocus.value || preferences.hideTitle) (basePadding * 3) else basePadding,
start = if (hasSearchFocus.value || preferences.hideTitle) 0.dp else basePadding
)
.height(35.dp)
.onFocusChanged { focusState ->
hasSearchFocus.value = focusState.isFocused
},
requestFocus = requestSearchFocus.value,
focusRequester = focusRequester,
value = textState,
onValueChange = { textState = it },
placeHolder = "Search ${title.value}"
@@ -139,6 +149,12 @@ private fun SearchTopBar(
titleContentColor = MaterialTheme.colorScheme.primary
)
)
val context = LocalContext.current
val keyboardManager = KeyboardManager.getInstance(context)
keyboardManager.attachKeyboardDismissListener {
requestSearchFocus.value = false
}
}
@Composable

View File

@@ -1,15 +1,33 @@
package com.owenlejeune.tvtime.utils
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.graphics.Rect
import android.view.View
import android.view.ViewTreeObserver
class KeyBoardManager(context: Context) {
class KeyboardManager private constructor(context: Context) {
companion object {
@SuppressLint("StaticFieldLeak")
private var INSTANCE: KeyboardManager? = null
private val lock = Object()
fun getInstance(context: Context): KeyboardManager {
if (INSTANCE == null) {
synchronized(lock) {
if (INSTANCE == null) {
INSTANCE = KeyboardManager(context)
}
}
}
return INSTANCE!!
}
}
private val activity = context as Activity
private var keyboardDismissListener: KeyboardDismissListener? = null
private var keyboardDismissListeners: MutableList<KeyboardDismissListener> = ArrayList()
private abstract class KeyboardDismissListener(
private val rootView: View,
@@ -33,17 +51,18 @@ class KeyBoardManager(context: Context) {
fun attachKeyboardDismissListener(onKeyboardDismiss: () -> Unit) {
val rootView = activity.findViewById<View>(android.R.id.content)
keyboardDismissListener = object : KeyboardDismissListener(rootView, onKeyboardDismiss) {}
keyboardDismissListener?.let {
keyboardDismissListeners.add(object : KeyboardDismissListener(rootView, onKeyboardDismiss) {})
keyboardDismissListeners.forEach {
rootView.viewTreeObserver.addOnGlobalLayoutListener(it)
}
}
fun release() {
val rootView = activity.findViewById<View>(android.R.id.content)
keyboardDismissListener?.let {
keyboardDismissListeners.forEach {
rootView.viewTreeObserver.removeOnGlobalLayoutListener(it)
}
keyboardDismissListener = null
keyboardDismissListeners.clear()
}
}