Make settings layout span under system bars

This commit is contained in:
Suphon Thanakornpakapong
2021-05-18 00:06:23 +07:00
parent d2ae5e4ad3
commit 13acd20f60
9 changed files with 148 additions and 68 deletions

View File

@@ -2,6 +2,7 @@ buildscript {
ext {
kotlin_version = '1.4.32'
compose_version = '1.0.0-beta05'
accompanist_version = '0.9.1'
}
repositories {
mavenCentral()
@@ -290,7 +291,9 @@ dependencies {
implementation "androidx.compose.runtime:runtime-rxjava2:$compose_version"
implementation "androidx.compose.compiler:compiler:$compose_version"
implementation "androidx.navigation:navigation-compose:1.0.0-alpha10"
implementation "com.google.accompanist:accompanist-glide:0.7.1"
implementation "com.google.accompanist:accompanist-glide:$accompanist_version"
implementation "com.google.accompanist:accompanist-insets:$accompanist_version"
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
}

View File

@@ -20,16 +20,21 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.core.view.WindowCompat
import app.lawnchair.ui.theme.LawnchairTheme
import com.google.accompanist.insets.ProvideWindowInsets
class PreferenceActivity : ComponentActivity() {
@ExperimentalAnimationApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
LawnchairTheme {
Preferences(window = this.window)
ProvideWindowInsets {
Preferences()
}
}
}
}
}
}

View File

@@ -17,24 +17,19 @@
package app.lawnchair.ui.preferences
import android.content.Context
import android.view.Window
import androidx.annotation.DrawableRes
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import app.lawnchair.ui.preferences.about.About
import app.lawnchair.ui.preferences.about.aboutGraph
import app.lawnchair.ui.preferences.components.PreferenceCategoryList
import app.lawnchair.ui.preferences.components.SystemUi
@@ -122,20 +117,15 @@ val LocalPreferenceInteractor = compositionLocalOf<PreferenceInteractor> {
@ExperimentalAnimationApi
@Composable
fun Preferences(interactor: PreferenceInteractor = viewModel<PreferenceViewModel>(), window: Window) {
fun Preferences(interactor: PreferenceInteractor = viewModel<PreferenceViewModel>()) {
val navController = rememberNavController()
SystemUi(window = window)
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
) {
SystemUi()
Surface(color = MaterialTheme.colors.background) {
CompositionLocalProvider(
LocalNavController provides navController,
LocalPreferenceInteractor provides interactor,
) {
TopBar()
NavHost(navController = navController, startDestination = "preferences") {
composable(route = Routes.PREFERENCES) {
pageMeta.provide(Meta(title = stringResource(id = R.string.settings)))
@@ -148,6 +138,7 @@ fun Preferences(interactor: PreferenceInteractor = viewModel<PreferenceViewModel
folderGraph(route = Routes.FOLDERS)
aboutGraph(route = Routes.ABOUT)
}
TopBar()
}
}
}

View File

@@ -22,9 +22,7 @@ import androidx.annotation.StringRes
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -36,8 +34,9 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavGraphBuilder
import app.lawnchair.ui.preferences.components.PreferenceGroup
import app.lawnchair.ui.preferences.components.ClickListenerPreference
import app.lawnchair.ui.preferences.components.PreferenceGroup
import app.lawnchair.ui.preferences.components.PreferenceLayout
import app.lawnchair.ui.preferences.preferenceGraph
import app.lawnchair.util.Meta
import app.lawnchair.util.pageMeta
@@ -143,11 +142,8 @@ fun About() {
val context = LocalContext.current
pageMeta.provide(Meta(title = stringResource(id = R.string.about_label)))
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally
PreferenceLayout(
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.requiredHeight(24.dp))
Image(
@@ -239,6 +235,5 @@ fun About() {
}
})
}
Spacer(modifier = Modifier.requiredHeight(16.dp))
}
}

View File

@@ -16,12 +16,9 @@
package app.lawnchair.ui.preferences.components
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
import androidx.navigation.compose.navigate
@@ -32,9 +29,7 @@ fun PreferenceCategoryList(navController: NavController) {
val context = LocalContext.current
val categories = remember { getPreferenceCategories(context) }
LazyColumn(
modifier = Modifier.fillMaxHeight()
) {
PreferenceLayoutLazyColumn {
items(categories) { item ->
PreferenceCategoryListItem(
label = item.label,

View File

@@ -1,21 +1,39 @@
package app.lawnchair.ui.preferences.components
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import app.lawnchair.util.Meta
import app.lawnchair.util.pageMeta
import com.google.accompanist.insets.LocalWindowInsets
import com.google.accompanist.insets.toPaddingValues
@Composable
fun PreferenceLayout(content: @Composable () -> Unit) {
fun PreferenceLayout(
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable () -> Unit
) {
val scrollState = rememberScrollState()
ProvideTopBarFloatingState(scrollState.value > 0)
Column(
modifier = Modifier
.fillMaxHeight()
.verticalScroll(rememberScrollState())
.padding(bottom = 16.dp)
.verticalScroll(scrollState)
.padding(preferenceLayoutPadding()),
verticalArrangement = verticalArrangement,
horizontalAlignment = horizontalAlignment
) {
content()
}
@@ -23,8 +41,25 @@ fun PreferenceLayout(content: @Composable () -> Unit) {
@Composable
fun PreferenceLayoutLazyColumn(modifier: Modifier = Modifier, content: LazyListScope.() -> Unit) {
LazyColumn(modifier = modifier.fillMaxHeight()) {
val scrollState = rememberLazyListState()
ProvideTopBarFloatingState(scrollState.firstVisibleItemIndex > 0 || scrollState.firstVisibleItemScrollOffset > 0)
LazyColumn(
modifier = modifier.fillMaxHeight(),
contentPadding = preferenceLayoutPadding(),
state = scrollState
) {
content()
item { Spacer(modifier = Modifier.height(16.dp)) }
}
}
@Composable
fun preferenceLayoutPadding() = LocalWindowInsets.current.systemBars.toPaddingValues(
additionalTop = topBarSize,
additionalBottom = 16.dp
)
@Composable
private fun ProvideTopBarFloatingState(scrolled: Boolean) {
val meta = remember(scrolled) { Meta(topBarFloating = scrolled) }
pageMeta.provide(meta)
}

View File

@@ -16,30 +16,41 @@
package app.lawnchair.ui.preferences.components
import android.os.Build
import android.view.View
import android.view.Window
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.graphics.toArgb
import app.lawnchair.ui.theme.LawnchairTheme
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Composable
fun SystemUi(window: Window) =
LawnchairTheme {
window.statusBarColor = MaterialTheme.colors.background.toArgb()
window.navigationBarColor = MaterialTheme.colors.background.toArgb()
fun SystemUi() {
val systemUiController = rememberSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight
@Suppress("DEPRECATION")
if (MaterialTheme.colors.background.luminance() > 0.5f) {
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
SideEffect {
// Update all of the system bar colors to be transparent, and use
// dark icons if we're in light theme
systemUiController.setSystemBarsColor(
color = Color.Transparent,
darkIcons = useDarkIcons
)
@Suppress("DEPRECATION")
if (MaterialTheme.colors.background.luminance() > 0.5f && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
}
}
// setStatusBarsColor() and setNavigationBarsColor() also exist
}
}
// LawnchairTheme {
// window.statusBarColor = MaterialTheme.colors.background.toArgb()
// window.navigationBarColor = MaterialTheme.colors.background.toArgb()
//
// @Suppress("DEPRECATION")
// if (MaterialTheme.colors.background.luminance() > 0.5f) {
// window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or
// View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
// }
//
// @Suppress("DEPRECATION")
// if (MaterialTheme.colors.background.luminance() > 0.5f && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or
// View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
// }
// }

View File

@@ -18,8 +18,11 @@ package app.lawnchair.ui.preferences.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon
@@ -30,10 +33,15 @@ import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.ArrowForward
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
@@ -42,21 +50,21 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import app.lawnchair.ui.preferences.LocalNavController
import app.lawnchair.ui.preferences.Routes
import app.lawnchair.util.pageMeta
import com.google.accompanist.insets.statusBarsPadding
@ExperimentalAnimationApi
@Composable
fun TopBar() {
fun TopBar() = pageMeta.consume { state ->
val navController = LocalNavController.current
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
pageMeta.consume { state ->
TopBarSurface(floating = state.topBarFloating) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.background(MaterialTheme.colors.background)
.height(topBarSize)
) {
AnimatedVisibility(visible = currentRoute != Routes.PREFERENCES && currentRoute != null) {
Box(
@@ -85,6 +93,35 @@ fun TopBar() {
}
}
val shadowColors = listOf(Color(0, 0, 0, 31), Color.Transparent)
@Composable
fun TopBarSurface(floating: Boolean, content: @Composable () -> Unit) {
val normalColor = MaterialTheme.colors.background.copy(alpha = 0.9f)
val floatingColor = MaterialTheme.colors.surface.copy(alpha = 0.9f)
val color by animateColorAsState(if (floating) floatingColor else normalColor)
val shadowAlpha by animateFloatAsState(if (floating) 1f else 0f)
Column {
Box(
modifier = Modifier
.background(color)
.statusBarsPadding()
.pointerInput(remember { MutableInteractionSource() }) {
// consume touch
}
) {
content()
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(3.dp)
.alpha(shadowAlpha)
.background(Brush.verticalGradient(shadowColors))
)
}
}
@Composable
fun backIcon(): ImageVector {
return if (LocalLayoutDirection.current == LayoutDirection.Ltr) {
@@ -93,3 +130,5 @@ fun backIcon(): ImageVector {
Icons.Rounded.ArrowForward
}
}
val topBarSize = 56.dp

View File

@@ -1,12 +1,18 @@
package app.lawnchair.util
class MetaState(val title: String)
class Meta(val title: String?)
class MetaState(val title: String, val topBarFloating: Boolean)
class Meta(val title: String? = null, val topBarFloating: Boolean? = null)
private fun getTitleFromPropsList(propsList: List<Meta>): String {
return propsList.lastOrNull()?.title ?: ""
private fun <T> getFromPropsList(propsList: List<Meta>, defaultValue: T, extractor: (meta: Meta) -> T?): T {
return propsList
.asSequence()
.map(extractor)
.lastOrNull { it != null } ?: defaultValue
}
val pageMeta = createSideEffect<MetaState, Meta> { propsList ->
MetaState(title = getTitleFromPropsList(propsList))
MetaState(
title = getFromPropsList(propsList, "", Meta::title),
topBarFloating = getFromPropsList(propsList, false, Meta::topBarFloating),
)
}