diff --git a/ui/src/main/java/de/mm20/launcher2/ui/assistant/AssistantScaffold.kt b/ui/src/main/java/de/mm20/launcher2/ui/assistant/AssistantScaffold.kt new file mode 100644 index 00000000..1378825b --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/assistant/AssistantScaffold.kt @@ -0,0 +1,151 @@ +package de.mm20.launcher2.ui.assistant + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.accompanist.systemuicontroller.rememberSystemUiController +import de.mm20.launcher2.preferences.Settings +import de.mm20.launcher2.ui.launcher.LauncherScaffoldVM +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 kotlinx.coroutines.flow.map + +@Composable +fun AssistantScaffold( + modifier: Modifier = Modifier, + darkStatusBarIcons: Boolean = false, + darkNavBarIcons: Boolean = false, +) { + val viewModel: LauncherScaffoldVM = viewModel() + + val bottomSearchBar by remember { + viewModel.dataStore.data.map { it.appearance.layout != Settings.AppearanceSettings.Layout.PullDown } + }.collectAsState(null) + + val reverseResults by remember { + viewModel.dataStore.data.map { it.appearance.layout != Settings.AppearanceSettings.Layout.PullDown } + }.collectAsState(null) + + val searchBarFocused by viewModel.searchBarFocused.observeAsState(false) + + val searchState = rememberLazyListState() + + val isSearchAtStart by remember { + derivedStateOf { + searchState.firstVisibleItemIndex == 0 && searchState.firstVisibleItemScrollOffset == 0 + } + } + + val isSearchAtEnd by remember { + derivedStateOf { + val lastItem = searchState.layoutInfo.visibleItemsInfo.lastOrNull() ?: return@derivedStateOf true + lastItem.offset + lastItem.size <= searchState.layoutInfo.viewportEndOffset - searchState.layoutInfo.afterContentPadding + } + } + + if (reverseResults == null || bottomSearchBar == null) return + + + val searchBarLevel by remember { + derivedStateOf { + when { + reverseResults == bottomSearchBar && isSearchAtStart -> SearchBarLevel.Active + reverseResults != bottomSearchBar && isSearchAtEnd -> SearchBarLevel.Active + else -> SearchBarLevel.Raised + } + } + } + + val systemUiController = rememberSystemUiController() + val showStatusBarScrim by remember { + derivedStateOf { + if (reverseResults == true) { + !isSearchAtEnd + } else { + !isSearchAtStart + } + } + } + val showNavBarScrim by remember { + derivedStateOf { + if (reverseResults == true) { + !isSearchAtStart + } else { + !isSearchAtEnd + } + } + } + + + val colorSurface = MaterialTheme.colorScheme.surface + LaunchedEffect(darkStatusBarIcons, colorSurface, showStatusBarScrim) { + if (showStatusBarScrim) { + systemUiController.setStatusBarColor( + colorSurface.copy(0.7f), + ) + } else { + systemUiController.setStatusBarColor( + Color.Transparent, + darkIcons = darkStatusBarIcons + ) + } + } + + LaunchedEffect(darkNavBarIcons, showNavBarScrim) { + if (showNavBarScrim) { + systemUiController.setNavigationBarColor( + colorSurface.copy(0.7f), + ) + } else { + systemUiController.setNavigationBarColor( + Color.Transparent, + darkIcons = darkNavBarIcons, + navigationBarContrastEnforced = false + ) + } + } + + val searchVM: SearchVM = viewModel() + val websearches by searchVM.websearchResults.observeAsState(emptyList()) + val webSearchPadding by animateDpAsState( + if (websearches.isEmpty()) 0.dp else 48.dp + ) + val windowInsets = WindowInsets.safeDrawing.asPaddingValues() + Box(modifier = Modifier.fillMaxSize()) { + SearchColumn( + modifier = Modifier.fillMaxSize(), + paddingValues = PaddingValues( + top = (if (bottomSearchBar == true) 0.dp else 56.dp + webSearchPadding) + 4.dp + windowInsets.calculateTopPadding(), + bottom = (if (bottomSearchBar == true) 56.dp + webSearchPadding else 0.dp) + 4.dp + windowInsets.calculateBottomPadding() + ), + reverse = reverseResults == true, + state = searchState + ) + + SearchBar( + level = { searchBarLevel }, + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .align(if (bottomSearchBar == true) Alignment.BottomCenter else Alignment.TopCenter) + .windowInsetsPadding(WindowInsets.safeDrawing) + .padding(8.dp), + focused = searchBarFocused, + onFocusChange = { + if (it) viewModel.openSearch() + viewModel.setSearchbarFocus(it) + }, + reverse = bottomSearchBar == true + ) + } +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt index f6b3c2a6..f30906d3 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt @@ -73,7 +73,7 @@ fun PagerScaffold( val isSearchAtEnd by remember { derivedStateOf { - val lastItem = searchState.layoutInfo.visibleItemsInfo.last() + val lastItem = searchState.layoutInfo.visibleItemsInfo.lastOrNull() ?: return@derivedStateOf true lastItem.offset + lastItem.size <= searchState.layoutInfo.viewportEndOffset - searchState.layoutInfo.afterContentPadding } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt index 19477641..da72f853 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt @@ -69,7 +69,7 @@ fun PullDownScaffold( val isSearchAtEnd by remember { derivedStateOf { - val lastItem = searchState.layoutInfo.visibleItemsInfo.last() + val lastItem = searchState.layoutInfo.visibleItemsInfo.lastOrNull() ?: return@derivedStateOf true lastItem.offset + lastItem.size <= searchState.layoutInfo.viewportEndOffset - searchState.layoutInfo.afterContentPadding } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt index a943a32e..c69a4124 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt @@ -36,6 +36,7 @@ import com.android.launcher3.GestureNavContract import com.google.accompanist.systemuicontroller.rememberSystemUiController import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.ui.R +import de.mm20.launcher2.ui.assistant.AssistantScaffold import de.mm20.launcher2.ui.base.BaseActivity import de.mm20.launcher2.ui.base.ProvideCurrentTime import de.mm20.launcher2.ui.base.ProvideSettings @@ -127,36 +128,45 @@ abstract class SharedLauncherActivity( contentAlignment = Alignment.BottomCenter ) { NavBarEffects(modifier = Modifier.fillMaxSize()) - when (layout) { - Settings.AppearanceSettings.Layout.PullDown -> { - PullDownScaffold( - modifier = Modifier - .fillMaxSize() - .graphicsLayer { - scaleX = 0.5f + enterTransition.value * 0.5f - scaleY = 0.5f + enterTransition.value * 0.5f - alpha = enterTransition.value - }, - darkStatusBarIcons = lightStatus, - darkNavBarIcons = lightNav, - ) + if (mode == LauncherActivityMode.Assistant) { + AssistantScaffold( + modifier = Modifier + .fillMaxSize(), + darkStatusBarIcons = lightStatus, + darkNavBarIcons = lightNav, + ) + } else { + when (layout) { + Settings.AppearanceSettings.Layout.PullDown -> { + PullDownScaffold( + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + scaleX = 0.5f + enterTransition.value * 0.5f + scaleY = 0.5f + enterTransition.value * 0.5f + alpha = enterTransition.value + }, + darkStatusBarIcons = lightStatus, + darkNavBarIcons = lightNav, + ) + } + Settings.AppearanceSettings.Layout.Pager, + Settings.AppearanceSettings.Layout.PagerReversed -> { + PagerScaffold( + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + scaleX = enterTransition.value + scaleY = enterTransition.value + alpha = enterTransition.value + }, + darkStatusBarIcons = lightStatus, + darkNavBarIcons = lightNav, + reverse = layout == Settings.AppearanceSettings.Layout.PagerReversed + ) + } + else -> {} } - Settings.AppearanceSettings.Layout.Pager, - Settings.AppearanceSettings.Layout.PagerReversed -> { - PagerScaffold( - modifier = Modifier - .fillMaxSize() - .graphicsLayer { - scaleX = enterTransition.value - scaleY = enterTransition.value - alpha = enterTransition.value - }, - darkStatusBarIcons = lightStatus, - darkNavBarIcons = lightNav, - reverse = layout == Settings.AppearanceSettings.Layout.PagerReversed - ) - } - else -> {} } SnackbarHost( snackbarHostState,