Add alternative layout

This commit is contained in:
MM20
2022-05-10 20:24:58 +02:00
parent 78593e0391
commit 7fda28aedd
19 changed files with 264 additions and 60 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/LauncherTheme"
android:windowSoftInputMode="stateHidden">
android:windowSoftInputMode="stateHidden|adjustNothing">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -71,7 +71,7 @@ class LauncherActivity : BaseActivity() {
contentAlignment = Alignment.BottomCenter
) {
NavBarEffects(modifier = Modifier.fillMaxSize())
PullDownScaffold(
PagerScaffold(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
@@ -0,0 +1,180 @@
package de.mm20.launcher2.ui.launcher
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Done
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.ktx.toDp
import de.mm20.launcher2.ui.launcher.search.SearchBar
import de.mm20.launcher2.ui.launcher.search.SearchBarLevel
import de.mm20.launcher2.ui.launcher.search.SearchColumn
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn
@OptIn(ExperimentalPagerApi::class)
@Composable
fun PagerScaffold(
modifier: Modifier = Modifier,
darkStatusBarIcons: Boolean = false,
darkNavBarIcons: Boolean = false,
) {
val viewModel: LauncherScaffoldVM = viewModel()
val searchVM: SearchVM = viewModel()
val context = LocalContext.current
val isSearchOpen by viewModel.isSearchOpen.observeAsState(false)
val isWidgetEditMode by viewModel.isWidgetEditMode.observeAsState(false)
val pagerState = rememberPagerState()
val widgetsScrollState = rememberScrollState()
val searchScrollState = rememberScrollState()
val currentPage = pagerState.currentPage
LaunchedEffect(currentPage) {
if (currentPage == 1) viewModel.openSearch()
else viewModel.closeSearch()
}
LaunchedEffect(isSearchOpen) {
if (isSearchOpen) pagerState.animateScrollToPage(1)
else {
pagerState.animateScrollToPage(0)
searchVM.search("")
}
}
BackHandler {
when {
isSearchOpen -> {
viewModel.closeSearch()
searchVM.search("")
}
isWidgetEditMode -> {
viewModel.setWidgetEditMode(false)
}
}
}
Box(
modifier = modifier
) {
var size by remember { mutableStateOf(IntSize.Zero) }
HorizontalPager(
count = 2,
state = pagerState,
modifier = Modifier
.fillMaxSize(),
userScrollEnabled = !isWidgetEditMode
) {
if (it == 1) {
val websearches by searchVM.websearchResults.observeAsState(emptyList())
val webSearchPadding by animateDpAsState(
if (websearches.isEmpty()) 0.dp else 48.dp
)
SearchColumn(
modifier = Modifier
.fillMaxSize()
.onSizeChanged {
size = it
}
.verticalScroll(searchScrollState, reverseScrolling = true)
.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 56.dp)
.padding(bottom = webSearchPadding)
)
}
if (it == 0) {
val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp)
val showClockPadding by derivedStateOf {
widgetsScrollState.value == 0
}
val clockPadding by animateDpAsState(
if (showClockPadding) 64.dp else 0.dp
)
WidgetColumn(
modifier =
Modifier
.fillMaxSize()
.verticalScroll(widgetsScrollState)
.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 64.dp)
.padding(top = editModePadding),
clockHeight = size.height.toDp() - (64.dp - clockPadding),
clockBottomPadding = clockPadding,
editMode = isWidgetEditMode,
onEditModeChange = {
viewModel.setWidgetEditMode(it)
}
)
}
}
AnimatedVisibility(visible = isWidgetEditMode,
enter = slideIn { IntOffset(0, -it.height) },
exit = slideOut { IntOffset(0, -it.height) }
) {
CenterAlignedTopAppBar(
title = {
Text(stringResource(R.string.menu_edit_widgets))
},
navigationIcon = {
IconButton(onClick = { viewModel.setWidgetEditMode(false) }) {
Icon(imageVector = Icons.Rounded.Done, contentDescription = null)
}
},
)
}
val searchBarLevel by derivedStateOf {
when {
pagerState.isScrollInProgress -> SearchBarLevel.Raised
!isSearchOpen && widgetsScrollState.value == 0 -> SearchBarLevel.Resting
isSearchOpen && searchScrollState.value == 0 -> SearchBarLevel.Active
else -> SearchBarLevel.Raised
}
}
val focusSearchBar by viewModel.searchBarFocused.observeAsState(false)
val widgetEditModeOffset by animateDpAsState(
if (isWidgetEditMode) 128.dp else 0.dp
)
SearchBar(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(start = 8.dp, end = 8.dp, bottom = 8.dp)
.offset(y = widgetEditModeOffset),
level = searchBarLevel, focused = focusSearchBar, onFocusChange = {
if (it) viewModel.openSearch()
viewModel.setSearchbarFocus(it)
},
reverse = true
)
}
}
@@ -7,10 +7,7 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@@ -298,7 +295,10 @@ fun PullDownScaffold(
SearchBar(
level = searchBarLevel,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.clipToBounds()
.padding(8.dp)
.offset { IntOffset(0, searchBarOffset.value.toInt()) }
.offset(y = editModeSearchBarOffset),
focused = searchBarFocused,
@@ -46,6 +46,7 @@ import de.mm20.launcher2.search.data.Websearch
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.launcher.LauncherActivityVM
import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.locals.LocalCardStyle
import de.mm20.launcher2.ui.settings.SettingsActivity
import kotlinx.coroutines.awaitCancellation
@@ -58,7 +59,8 @@ fun SearchBar(
modifier: Modifier = Modifier,
level: SearchBarLevel,
focused: Boolean,
onFocusChange: (Boolean) -> Unit
onFocusChange: (Boolean) -> Unit,
reverse: Boolean = false,
) {
val searchViewModel: SearchVM = viewModel()
val activityViewModel: LauncherActivityVM = viewModel()
@@ -150,7 +152,8 @@ fun SearchBar(
},
onUnfocus = {
onFocusChange(false)
}
},
reverse = reverse
)
}
@@ -166,7 +169,8 @@ fun SearchBar(
onValueChange: (String) -> Unit,
onFocus: () -> Unit = {},
onUnfocus: () -> Unit = {},
focusRequester: FocusRequester = remember { FocusRequester() }
focusRequester: FocusRequester = remember { FocusRequester() },
reverse: Boolean = false,
) {
val context = LocalContext.current
@@ -241,14 +245,13 @@ fun SearchBar(
LauncherCard(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
.alpha(opacity)
.padding(8.dp),
.alpha(opacity),
backgroundOpacity = backgroundOpacity,
elevation = elevation
) {
Column {
Column(
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top
) {
Row(
modifier = Modifier.height(48.dp),
verticalAlignment = Alignment.CenterVertically
@@ -1,9 +1,6 @@
package de.mm20.launcher2.ui.launcher.search
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -17,23 +14,26 @@ import de.mm20.launcher2.ui.launcher.search.files.FileResults
import de.mm20.launcher2.ui.launcher.search.unitconverter.UnitConverterResults
import de.mm20.launcher2.ui.launcher.search.website.WebsiteResults
import de.mm20.launcher2.ui.launcher.search.wikipedia.WikipediaResults
import de.mm20.launcher2.ui.layout.BottomReversed
@Composable
fun SearchColumn(
modifier: Modifier = Modifier,
reverse: Boolean = false,
) {
Column(
modifier = modifier
modifier = modifier,
verticalArrangement = if (reverse) Arrangement.Top else Arrangement.BottomReversed
) {
FavoritesResults()
AppResults()
AppShortcutResults()
UnitConverterResults()
CalculatorResults()
CalendarResults()
ContactResults()
WikipediaResults()
WebsiteResults()
FileResults()
FavoritesResults(reverse)
AppResults(reverse)
AppShortcutResults(reverse)
UnitConverterResults(reverse)
CalculatorResults(reverse)
CalendarResults(reverse)
ContactResults(reverse)
WikipediaResults(reverse)
WebsiteResults(reverse)
FileResults(reverse)
}
}
@@ -14,15 +14,16 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
@Composable
fun ColumnScope.AppResults() {
fun ColumnScope.AppResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val apps by viewModel.appResults.observeAsState(emptyList())
AnimatedVisibility(apps.isNotEmpty()) {
LauncherCard(
modifier = Modifier.padding(bottom = 8.dp)
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
SearchResultGrid(items = apps)
SearchResultGrid(items = apps, reverse = reverse)
}
}
@@ -24,7 +24,7 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@Composable
fun ColumnScope.AppShortcutResults() {
fun ColumnScope.AppShortcutResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val shortcuts by viewModel.appShortcutResults.observeAsState(emptyList())
val context = LocalContext.current
@@ -34,7 +34,8 @@ fun ColumnScope.AppShortcutResults() {
AnimatedVisibility(shortcuts.isNotEmpty() || (!isSearchEmpty && missingPermission)) {
LauncherCard(
modifier = Modifier.padding(bottom = 8.dp)
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
Column {
AnimatedVisibility(!isSearchEmpty && missingPermission) {
@@ -58,7 +59,8 @@ fun ColumnScope.AppShortcutResults() {
items = shortcuts,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
.padding(12.dp),
reverse = reverse
)
}
}
@@ -14,13 +14,14 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.search.CalculatorItem
@Composable
fun ColumnScope.CalculatorResults() {
fun ColumnScope.CalculatorResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val calculator by viewModel.calculatorResult.observeAsState(null)
AnimatedVisibility(calculator != null) {
LauncherCard(
modifier = Modifier.padding(bottom = 8.dp)
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
calculator?.let { CalculatorItem(calculator = it) }
}
@@ -24,7 +24,7 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@Composable
fun ColumnScope.CalendarResults() {
fun ColumnScope.CalendarResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val calendarEvents by viewModel.calendarResults.observeAsState(emptyList())
val context = LocalContext.current
@@ -34,7 +34,7 @@ fun ColumnScope.CalendarResults() {
AnimatedVisibility(calendarEvents.isNotEmpty() || (!isSearchEmpty && missingPermission)) {
LauncherCard(
modifier = Modifier
.padding(bottom = 8.dp)
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
.fillMaxWidth()
) {
Column {
@@ -59,7 +59,8 @@ fun ColumnScope.CalendarResults() {
items = calendarEvents,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
.padding(12.dp),
reverse = reverse
)
}
}
@@ -7,6 +7,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.data.Searchable
import de.mm20.launcher2.ui.launcher.search.common.GridItem
import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.locals.LocalGridColumns
import kotlin.math.ceil
@@ -16,12 +17,14 @@ fun SearchResultGrid(
modifier: Modifier = Modifier,
showLabels: Boolean = true,
columns: Int = LocalGridColumns.current,
reverse: Boolean = false
) {
Column(
modifier = modifier
.animateContentSize()
.fillMaxWidth()
.padding(4.dp)
.padding(4.dp),
verticalArrangement = if (reverse) Arrangement.Top else Arrangement.BottomReversed
) {
for (i in 0 until ceil(items.size / columns.toFloat()).toInt()) {
Row {
@@ -1,6 +1,6 @@
package de.mm20.launcher2.ui.launcher.search.common.list
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -9,14 +9,17 @@ import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.data.Searchable
import de.mm20.launcher2.ui.layout.BottomReversed
@Composable
fun SearchResultList(
items: List<Searchable>,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
reverse: Boolean = false
) {
Column(
modifier = modifier
modifier = modifier,
verticalArrangement = if (reverse) Arrangement.Top else Arrangement.BottomReversed
) {
for (item in items) {
key(item.key) {
@@ -24,7 +24,7 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@Composable
fun ColumnScope.ContactResults() {
fun ColumnScope.ContactResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val context = LocalContext.current
val contacts by viewModel.contactResults.observeAsState(emptyList())
@@ -34,7 +34,7 @@ fun ColumnScope.ContactResults() {
AnimatedVisibility(contacts.isNotEmpty() || (!isSearchEmpty && missingPermission)) {
LauncherCard(
modifier = Modifier
.padding(bottom = 8.dp)
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
.fillMaxWidth()
) {
Column {
@@ -58,7 +58,8 @@ fun ColumnScope.ContactResults() {
items = contacts,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
.padding(12.dp),
reverse = reverse
)
}
}
@@ -14,7 +14,9 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
@Composable
fun ColumnScope.FavoritesResults() {
fun ColumnScope.FavoritesResults(
reverse: Boolean = false,
) {
val viewModel: SearchVM = viewModel()
val favorites by viewModel.favorites.observeAsState(emptyList())
@@ -22,9 +24,10 @@ fun ColumnScope.FavoritesResults() {
AnimatedVisibility(!hide && favorites.isNotEmpty()) {
LauncherCard(
modifier = Modifier.padding(bottom = 8.dp)
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
SearchResultGrid(items = favorites)
SearchResultGrid(items = favorites, reverse = reverse)
}
}
}
@@ -24,7 +24,7 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@Composable
fun ColumnScope.FileResults() {
fun ColumnScope.FileResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val files by viewModel.fileResults.observeAsState(emptyList())
val context = LocalContext.current
@@ -34,7 +34,7 @@ fun ColumnScope.FileResults() {
AnimatedVisibility(files.isNotEmpty() || (!isSearchEmpty && missingPermission)) {
LauncherCard(
modifier = Modifier
.padding(bottom = 8.dp)
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
.fillMaxWidth()
) {
Column {
@@ -58,7 +58,8 @@ fun ColumnScope.FileResults() {
items = files,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
.padding(12.dp),
reverse = reverse
)}
}
}
@@ -14,13 +14,14 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.search.UnitConverterItem
@Composable
fun ColumnScope.UnitConverterResults() {
fun ColumnScope.UnitConverterResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val unitConverter by viewModel.unitConverterResult.observeAsState(null)
AnimatedVisibility(unitConverter != null) {
LauncherCard(
modifier = Modifier.padding(bottom = 8.dp)
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
unitConverter?.let { UnitConverterItem(unitConverter = it) }
}
@@ -13,13 +13,14 @@ import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.launcher.search.SearchVM
@Composable
fun ColumnScope.WebsiteResults() {
fun ColumnScope.WebsiteResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val website by viewModel.websiteResult.observeAsState(null)
AnimatedVisibility(website != null) {
LauncherCard(
modifier = Modifier.padding(bottom = 8.dp)
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
website?.let { WebsiteItem(website = it) }
}
@@ -13,13 +13,14 @@ import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.launcher.search.SearchVM
@Composable
fun ColumnScope.WikipediaResults() {
fun ColumnScope.WikipediaResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val wikipedia by viewModel.wikipediaResult.observeAsState(null)
AnimatedVisibility(wikipedia != null) {
LauncherCard(
modifier = Modifier.padding(bottom = 8.dp)
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
wikipedia?.let { WikipediaItem(wikipedia = it) }
}
@@ -49,6 +49,7 @@ import kotlinx.coroutines.launch
fun WidgetColumn(
modifier: Modifier = Modifier,
clockHeight: Dp = 0.dp,
clockBottomPadding: Dp = 0.dp,
editMode: Boolean = false,
onEditModeChange: (Boolean) -> Unit,
) {
@@ -91,7 +92,8 @@ fun WidgetColumn(
Box(
modifier = Modifier
.fillMaxWidth()
.height(clockHeight),
.height(clockHeight)
.padding(bottom = clockBottomPadding),
contentAlignment = Alignment.BottomCenter
) {
ClockWidget(