Add alternative layout

This commit is contained in:
MM20 2022-05-10 20:24:58 +02:00
parent 78593e0391
commit 7fda28aedd
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
19 changed files with 264 additions and 60 deletions

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" />

View File

@ -71,7 +71,7 @@ class LauncherActivity : BaseActivity() {
contentAlignment = Alignment.BottomCenter
) {
NavBarEffects(modifier = Modifier.fillMaxSize())
PullDownScaffold(
PagerScaffold(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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