diff --git a/build.gradle b/build.gradle index 18a9ea5733..2c4179efb1 100644 --- a/build.gradle +++ b/build.gradle @@ -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' } diff --git a/lawnchair/src/app/lawnchair/ui/preferences/PreferenceActivity.kt b/lawnchair/src/app/lawnchair/ui/preferences/PreferenceActivity.kt index 8a3507ed5d..e444d68807 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/PreferenceActivity.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/PreferenceActivity.kt @@ -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() + } } } } -} \ No newline at end of file +} diff --git a/lawnchair/src/app/lawnchair/ui/preferences/Preferences.kt b/lawnchair/src/app/lawnchair/ui/preferences/Preferences.kt index 8786e87cb8..80e0e81289 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/Preferences.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/Preferences.kt @@ -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 { @ExperimentalAnimationApi @Composable -fun Preferences(interactor: PreferenceInteractor = viewModel(), window: Window) { +fun Preferences(interactor: PreferenceInteractor = viewModel()) { 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 PreferenceCategoryListItem( label = item.label, diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/PreferenceLayout.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/PreferenceLayout.kt index 1f3039f94f..7612a93390 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/components/PreferenceLayout.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/PreferenceLayout.kt @@ -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) +} diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/SystemUi.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/SystemUi.kt index f4bd9aa032..f2f4958552 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/components/SystemUi.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/SystemUi.kt @@ -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 - } - } \ No newline at end of file + // 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 +// } +// } diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/TopBar.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/TopBar.kt index aeceac659e..24ca268a9d 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/components/TopBar.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/TopBar.kt @@ -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 diff --git a/lawnchair/src/app/lawnchair/util/pageMeta.kt b/lawnchair/src/app/lawnchair/util/pageMeta.kt index 045f8be470..4e60ad9a48 100644 --- a/lawnchair/src/app/lawnchair/util/pageMeta.kt +++ b/lawnchair/src/app/lawnchair/util/pageMeta.kt @@ -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): String { - return propsList.lastOrNull()?.title ?: "" +private fun getFromPropsList(propsList: List, defaultValue: T, extractor: (meta: Meta) -> T?): T { + return propsList + .asSequence() + .map(extractor) + .lastOrNull { it != null } ?: defaultValue } val pageMeta = createSideEffect { propsList -> - MetaState(title = getTitleFromPropsList(propsList)) + MetaState( + title = getFromPropsList(propsList, "", Meta::title), + topBarFloating = getFromPropsList(propsList, false, Meta::topBarFloating), + ) }