From 720cc9d9e424cdc430507d8e9cc7cef0b643b86b Mon Sep 17 00:00:00 2001 From: jacobrein Date: Fri, 18 Oct 2024 08:28:56 -0600 Subject: [PATCH] - Refactor: Reorganize General Settings and Bottom Navigation Bar This commit refactors the general settings screen and bottom navigation bar: - Reorganizes the general settings screen for better readability and consistency. - Adds the option to configure the middle navigation action for multiple actions. - Refactors the navigation bar to be bottom or rail-style depending on screen size. - Adds support for a middle action button for navigation with multiple actions. - Improves handling of screen transition animations for the bottom navigation bar. --- .../uiviews/BaseMainActivity.kt | 347 +++++++--- .../notifications/NotificationFragment.kt | 2 +- .../uiviews/settings/GeneralSettings.kt | 626 ++++++++++++------ .../uiviews/utils/ProtoUtils.kt | 15 + .../utils/customsettings/MiddleNavAction.kt | 54 +- UIViews/src/main/proto/settings.proto | 7 + UIViews/src/main/res/values/strings.xml | 3 + 7 files changed, 747 insertions(+), 307 deletions(-) diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/BaseMainActivity.kt b/UIViews/src/main/java/com/programmersbox/uiviews/BaseMainActivity.kt index fcb9bf1fc..7056478bf 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/BaseMainActivity.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/BaseMainActivity.kt @@ -36,6 +36,7 @@ import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -46,7 +47,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.selection.selectableGroup @@ -61,6 +64,8 @@ import androidx.compose.material.icons.filled.Notifications import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.filled.UnfoldLess +import androidx.compose.material.icons.filled.UnfoldMore import androidx.compose.material.icons.outlined.History import androidx.compose.material.icons.outlined.Settings import androidx.compose.material3.Badge @@ -68,6 +73,10 @@ import androidx.compose.material3.BadgedBox import androidx.compose.material3.BottomAppBarScrollBehavior import androidx.compose.material3.BottomAppBarState import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FilledIconButton +import androidx.compose.material3.FloatingAppBarDefaults +import androidx.compose.material3.HorizontalFloatingAppBar import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -90,8 +99,11 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -174,6 +186,7 @@ import com.programmersbox.uiviews.utils.sharedelements.animatedScopeComposable import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeChild import dev.chrisbanes.haze.materials.HazeMaterials +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn @@ -275,6 +288,7 @@ abstract class BaseMainActivity : AppCompatActivity() { val showAllItem by settingsHandling.rememberShowAll() val middleNavItem by settingsHandling.rememberMiddleNavigationAction() + val multipleActions by settingsHandling.rememberMiddleMultipleActions() val navType = when (windowSize.widthSizeClass) { WindowWidthSizeClass.Expanded -> NavigationBarType.Rail @@ -311,7 +325,8 @@ abstract class BaseMainActivity : AppCompatActivity() { currentDestination = currentDestination, showBlur = showBlur, isAmoledMode = isAmoledMode, - middleNavItem = middleNavItem + middleNavItem = middleNavItem, + multipleActions = multipleActions, ) } else { HomeNavigationBar( @@ -322,6 +337,7 @@ abstract class BaseMainActivity : AppCompatActivity() { isAmoledMode = isAmoledMode, middleNavItem = middleNavItem, //scrollBehavior = null, + multipleActions = multipleActions, modifier = Modifier .padding(horizontal = 24.dp) .windowInsetsPadding(WindowInsets.navigationBars) @@ -395,7 +411,7 @@ abstract class BaseMainActivity : AppCompatActivity() { } } - @OptIn(ExperimentalMaterial3Api::class) + @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable private fun HomeNavigationBar( showNavBar: Boolean, @@ -406,7 +422,9 @@ abstract class BaseMainActivity : AppCompatActivity() { middleNavItem: MiddleNavigationAction, modifier: Modifier = Modifier, scrollBehavior: BottomAppBarScrollBehavior? = null, + multipleActions: MiddleMultipleActions, ) { + val scope = rememberCoroutineScope() AnimatedVisibility( visible = showNavBar && navType == NavigationBarType.Bottom, enter = slideInVertically { it / 2 } + expandVertically() + fadeIn(), @@ -430,99 +448,43 @@ abstract class BaseMainActivity : AppCompatActivity() { Modifier } - FloatingNavigationBar( - containerColor = when { - showBlur -> Color.Transparent - isAmoledMode -> MaterialTheme.colorScheme.surface - else -> NavigationBarDefaults.containerColor - }, - modifier = modifier - .layout { measurable, constraints -> - // Sets the app bar's height offset to collapse the entire bar's height when - // content - // is scrolled. - scrollBehavior?.state?.heightOffsetLimit = -80.dp.toPx() - - val placeable = measurable.measure(constraints) - val height = placeable.height + (scrollBehavior?.state?.heightOffset ?: 0f) - layout(placeable.width, height.roundToInt()) { placeable.place(0, 0) } - } - .then(appBarDragModifier), - ) { - val colors = NavigationBarItemDefaults.colors( - selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer, - selectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer, - unselectedIconColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), - unselectedTextColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), - ) + var showHorizontalBar by remember { mutableStateOf(false) } + var expanded by remember { mutableStateOf(false) } - @Composable - fun ScreenBottomItem( - screen: Screen, - icon: ImageVector, - label: Int, - badge: @Composable BoxScope.() -> Unit = {}, - ) { - NavigationBarItem( - icon = { BadgedBox(badge = badge) { Icon(icon, null) } }, - label = { Text(stringResource(label)) }, - selected = currentDestination.isTopLevelDestinationInHierarchy(screen), - colors = colors, - onClick = { - navController.navigate(screen) { - popUpTo(navController.graph.findStartDestination().id) { saveState = true } - launchSingleTop = true - restoreState = true - } - } - ) + val closeMultipleBar: () -> Unit = { + scope.launch { + expanded = false + delay(250) + showHorizontalBar = false } - - ScreenBottomItem( - screen = Screen.RecentScreen, - icon = if (currentDestination.isTopLevelDestinationInHierarchy(Screen.RecentScreen)) Icons.Default.History else Icons.Outlined.History, - label = R.string.recent - ) - middleNavItem.item?.ScreenBottomItem( - rowScope = this, - currentDestination = currentDestination, - navController = navController, - colors = colors - ) - ScreenBottomItem( - screen = Screen.Settings, - icon = if (currentDestination.isTopLevelDestinationInHierarchy(Screen.Settings)) Icons.Default.Settings else Icons.Outlined.Settings, - label = R.string.settings, - badge = { if (updateCheck()) Badge { Text("") } } - ) } - } - } - @Composable - private fun BottomNav( - navController: NavHostController, - showNavBar: Boolean, - navType: NavigationBarType, - currentDestination: NavDestination?, - showBlur: Boolean, - isAmoledMode: Boolean, - middleNavItem: MiddleNavigationAction, - ) { - Column { - BottomBarAdditions() - AnimatedVisibility( - visible = showNavBar && navType == NavigationBarType.Bottom, - enter = slideInVertically { it / 2 } + expandVertically() + fadeIn(), - exit = slideOutVertically { it / 2 } + shrinkVertically() + fadeOut(), - ) { - NavigationBar( + Box(modifier = Modifier.fillMaxWidth()) { + FloatingNavigationBar( containerColor = when { showBlur -> Color.Transparent isAmoledMode -> MaterialTheme.colorScheme.surface else -> NavigationBarDefaults.containerColor - } + }, + modifier = modifier + .layout { measurable, constraints -> + // Sets the app bar's height offset to collapse the entire bar's height when + // content + // is scrolled. + scrollBehavior?.state?.heightOffsetLimit = -80.dp.toPx() + + val placeable = measurable.measure(constraints) + val height = placeable.height + (scrollBehavior?.state?.heightOffset ?: 0f) + layout(placeable.width, height.roundToInt()) { placeable.place(0, 0) } + } + .then(appBarDragModifier), ) { + val colors = NavigationBarItemDefaults.colors( + selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer, + selectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer, + unselectedIconColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + unselectedTextColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) @Composable fun ScreenBottomItem( @@ -535,6 +497,7 @@ abstract class BaseMainActivity : AppCompatActivity() { icon = { BadgedBox(badge = badge) { Icon(icon, null) } }, label = { Text(stringResource(label)) }, selected = currentDestination.isTopLevelDestinationInHierarchy(screen), + colors = colors, onClick = { navController.navigate(screen) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } @@ -550,11 +513,21 @@ abstract class BaseMainActivity : AppCompatActivity() { icon = if (currentDestination.isTopLevelDestinationInHierarchy(Screen.RecentScreen)) Icons.Default.History else Icons.Outlined.History, label = R.string.recent ) - middleNavItem.item?.ScreenBottomItem( + + middleNavItem.ScreenBottomItem( rowScope = this, currentDestination = currentDestination, - navController = navController + navController = navController, + colors = colors, + multipleClick = { + if (showHorizontalBar) { + closeMultipleBar() + } else { + showHorizontalBar = true + } + } ) + ScreenBottomItem( screen = Screen.Settings, icon = if (currentDestination.isTopLevelDestinationInHierarchy(Screen.Settings)) Icons.Default.Settings else Icons.Outlined.Settings, @@ -562,6 +535,204 @@ abstract class BaseMainActivity : AppCompatActivity() { badge = { if (updateCheck()) Badge { Text("") } } ) } + + if (middleNavItem == MiddleNavigationAction.Multiple) { + AnimatedVisibility( + visible = showHorizontalBar, + enter = slideInVertically( + animationSpec = MaterialTheme.motionScheme.fastSpatialSpec() + ) { it / 2 } + fadeIn( + animationSpec = MaterialTheme.motionScheme.fastSpatialSpec() + ), + exit = slideOutVertically( + animationSpec = MaterialTheme.motionScheme.fastSpatialSpec() + ) { it / 2 } + fadeOut( + animationSpec = MaterialTheme.motionScheme.fastSpatialSpec() + ), + modifier = Modifier + .align(Alignment.BottomCenter) + .windowInsetsPadding(WindowInsets.navigationBars) + .offset(y = -FloatingAppBarDefaults.ScreenOffset), + ) { + LaunchedEffect(Unit) { + delay(250) + expanded = true + } + + HorizontalFloatingAppBar( + expanded = expanded, + leadingContent = { + multipleActions.startAction.item?.ScreenBottomItem( + currentDestination = currentDestination, + navController = navController, + additionalOnClick = closeMultipleBar + ) + }, + trailingContent = { + multipleActions.endAction.item?.ScreenBottomItem( + currentDestination = currentDestination, + navController = navController, + additionalOnClick = closeMultipleBar + ) + }, + ) { + FilledIconButton( + modifier = Modifier.width(64.dp), + onClick = closeMultipleBar + ) { + Icon( + if (expanded) Icons.Default.UnfoldLess else Icons.Filled.UnfoldMore, + contentDescription = "Localized description" + ) + } + } + } + } + } + } + } + + @OptIn(ExperimentalMaterial3ExpressiveApi::class) + @Composable + private fun BottomNav( + navController: NavHostController, + showNavBar: Boolean, + navType: NavigationBarType, + currentDestination: NavDestination?, + showBlur: Boolean, + isAmoledMode: Boolean, + middleNavItem: MiddleNavigationAction, + multipleActions: MiddleMultipleActions, + ) { + val scope = rememberCoroutineScope() + + var showHorizontalBar by remember { mutableStateOf(false) } + var expanded by remember { mutableStateOf(false) } + + val closeMultipleBar: () -> Unit = { + scope.launch { + expanded = false + delay(250) + showHorizontalBar = false + } + } + Box { + Column { + BottomBarAdditions() + AnimatedVisibility( + visible = showNavBar && navType == NavigationBarType.Bottom, + enter = slideInVertically { it / 2 } + expandVertically() + fadeIn(), + exit = slideOutVertically { it / 2 } + shrinkVertically() + fadeOut(), + ) { + NavigationBar( + containerColor = when { + showBlur -> Color.Transparent + isAmoledMode -> MaterialTheme.colorScheme.surface + else -> NavigationBarDefaults.containerColor + } + ) { + + @Composable + fun ScreenBottomItem( + screen: Screen, + icon: ImageVector, + label: Int, + badge: @Composable BoxScope.() -> Unit = {}, + ) { + NavigationBarItem( + icon = { BadgedBox(badge = badge) { Icon(icon, null) } }, + label = { Text(stringResource(label)) }, + selected = currentDestination.isTopLevelDestinationInHierarchy(screen), + onClick = { + navController.navigate(screen) { + popUpTo(navController.graph.findStartDestination().id) { saveState = true } + launchSingleTop = true + restoreState = true + } + } + ) + } + + ScreenBottomItem( + screen = Screen.RecentScreen, + icon = if (currentDestination.isTopLevelDestinationInHierarchy(Screen.RecentScreen)) Icons.Default.History else Icons.Outlined.History, + label = R.string.recent + ) + + middleNavItem.ScreenBottomItem( + rowScope = this, + currentDestination = currentDestination, + navController = navController, + multipleClick = { + if (showHorizontalBar) { + closeMultipleBar() + } else { + showHorizontalBar = true + } + } + ) + + ScreenBottomItem( + screen = Screen.Settings, + icon = if (currentDestination.isTopLevelDestinationInHierarchy(Screen.Settings)) Icons.Default.Settings else Icons.Outlined.Settings, + label = R.string.settings, + badge = { if (updateCheck()) Badge { Text("") } } + ) + } + } + } + + if (middleNavItem == MiddleNavigationAction.Multiple) { + AnimatedVisibility( + visible = showHorizontalBar, + enter = slideInVertically( + animationSpec = MaterialTheme.motionScheme.fastSpatialSpec() + ) { it / 2 } + fadeIn( + animationSpec = MaterialTheme.motionScheme.fastSpatialSpec() + ), + exit = slideOutVertically( + animationSpec = MaterialTheme.motionScheme.fastSpatialSpec() + ) { it / 2 } + fadeOut( + animationSpec = MaterialTheme.motionScheme.fastSpatialSpec() + ), + modifier = Modifier + .align(Alignment.BottomCenter) + .windowInsetsPadding(WindowInsets.navigationBars) + .offset(y = -FloatingAppBarDefaults.ScreenOffset), + ) { + LaunchedEffect(Unit) { + delay(250) + expanded = true + } + + HorizontalFloatingAppBar( + expanded = expanded, + leadingContent = { + multipleActions.startAction.item?.ScreenBottomItem( + currentDestination = currentDestination, + navController = navController, + additionalOnClick = closeMultipleBar + ) + }, + trailingContent = { + multipleActions.endAction.item?.ScreenBottomItem( + currentDestination = currentDestination, + navController = navController, + additionalOnClick = closeMultipleBar + ) + }, + ) { + FilledIconButton( + modifier = Modifier.width(64.dp), + onClick = closeMultipleBar + ) { + Icon( + if (expanded) Icons.Default.UnfoldLess else Icons.Filled.UnfoldMore, + contentDescription = "Localized description" + ) + } + } + } } } } diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/notifications/NotificationFragment.kt b/UIViews/src/main/java/com/programmersbox/uiviews/notifications/NotificationFragment.kt index 4475e029d..da050f5dd 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/notifications/NotificationFragment.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/notifications/NotificationFragment.kt @@ -190,7 +190,7 @@ fun NotificationsScreen( .filter { it == 0 } .collect { cancelNotificationById(42) - navController.popBackStack() + //navController.popBackStack() } } diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/settings/GeneralSettings.kt b/UIViews/src/main/java/com/programmersbox/uiviews/settings/GeneralSettings.kt index d57c70888..2fd4e09dd 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/settings/GeneralSettings.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/settings/GeneralSettings.kt @@ -4,11 +4,16 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.List import androidx.compose.material.icons.automirrored.filled.ListAlt +import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.BlurOff import androidx.compose.material.icons.filled.BlurOn import androidx.compose.material.icons.filled.ChangeHistory @@ -20,9 +25,16 @@ import androidx.compose.material.icons.filled.Navigation import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.SettingsBrightness import androidx.compose.material.icons.filled.Share +import androidx.compose.material.icons.filled.UnfoldLess +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FilledIconButton import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.HorizontalFloatingAppBar import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -30,6 +42,7 @@ import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -44,25 +57,29 @@ import com.programmersbox.uiviews.MiddleNavigationAction import com.programmersbox.uiviews.R import com.programmersbox.uiviews.SystemThemeMode import com.programmersbox.uiviews.ThemeColor +import com.programmersbox.uiviews.copy import com.programmersbox.uiviews.details.PaletteSwatchType import com.programmersbox.uiviews.utils.LightAndDarkPreviews import com.programmersbox.uiviews.utils.ListSetting import com.programmersbox.uiviews.utils.LocalSettingsHandling import com.programmersbox.uiviews.utils.LocalWindowSizeClass +import com.programmersbox.uiviews.utils.PreferenceSetting import com.programmersbox.uiviews.utils.PreviewTheme +import com.programmersbox.uiviews.utils.SettingsHandling import com.programmersbox.uiviews.utils.ShowMoreSetting import com.programmersbox.uiviews.utils.ShowWhen import com.programmersbox.uiviews.utils.SliderSetting import com.programmersbox.uiviews.utils.SwitchSetting import com.programmersbox.uiviews.utils.components.ThemeItem import com.programmersbox.uiviews.utils.components.seedColor +import com.programmersbox.uiviews.utils.customsettings.item import com.programmersbox.uiviews.utils.customsettings.visibleName import com.programmersbox.uiviews.utils.rememberFloatingNavigation import com.programmersbox.uiviews.utils.rememberHistorySave import com.programmersbox.uiviews.utils.rememberSwatchStyle import com.programmersbox.uiviews.utils.rememberSwatchType -@OptIn(ExperimentalLayoutApi::class) +@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3ExpressiveApi::class) @ExperimentalMaterial3Api @ExperimentalComposeUiApi @Composable @@ -72,261 +89,436 @@ fun GeneralSettings( SettingsScaffold(stringResource(R.string.general_menu_title)) { val handling = LocalSettingsHandling.current - var showDownload by handling.rememberShowDownload() - - var themeSetting by handling.rememberSystemThemeMode() + var isAmoledMode by handling.rememberIsAmoledMode() - val themeText by remember { - derivedStateOf { - when (themeSetting) { - SystemThemeMode.FollowSystem -> "System" - SystemThemeMode.Day -> "Light" - SystemThemeMode.Night -> "Dark" - else -> "None" - } - } - } + ThemeSetting( + handling = handling, + isAmoledMode = isAmoledMode + ) - ListSetting( - settingTitle = { Text(stringResource(R.string.theme_choice_title)) }, - dialogIcon = { Icon(Icons.Default.SettingsBrightness, null) }, - settingIcon = { Icon(Icons.Default.SettingsBrightness, null, modifier = Modifier.fillMaxSize()) }, - dialogTitle = { Text(stringResource(R.string.choose_a_theme)) }, - summaryValue = { Text(themeText) }, - confirmText = { TextButton(onClick = { it.value = false }) { Text(stringResource(R.string.cancel)) } }, - value = themeSetting, - options = listOf(SystemThemeMode.FollowSystem, SystemThemeMode.Day, SystemThemeMode.Night), - updateValue = { it, d -> - d.value = false - themeSetting = it - } + AmoledModeSetting( + isAmoledMode = isAmoledMode, + onAmoledModeChange = { isAmoledMode = it } ) - var isAmoledMode by handling.rememberIsAmoledMode() + BlurSetting(handling = handling) - var themeColor by handling.rememberThemeColor() + HorizontalDivider() - ShowMoreSetting( - settingTitle = { Text("Theme Color") }, - settingIcon = { Icon(Icons.Default.Palette, null, modifier = Modifier.fillMaxSize()) }, - summaryValue = { Text(themeColor.name) }, - ) { - FlowRow( - horizontalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterHorizontally), - verticalArrangement = Arrangement.spacedBy(2.dp), - modifier = Modifier.fillMaxWidth(), - ) { - ThemeColor.entries - //TODO: For later - .filter { it != ThemeColor.Custom && it != ThemeColor.UNRECOGNIZED } - .forEach { - ThemeItem( - themeColor = it, - onClick = { themeColor = it }, - selected = it == themeColor, - colorScheme = if (it == ThemeColor.Dynamic) - MaterialTheme.colorScheme - else - rememberDynamicColorScheme( - it.seedColor, - isDark = when (themeSetting) { - SystemThemeMode.FollowSystem -> isSystemInDarkTheme() - SystemThemeMode.Day -> false - SystemThemeMode.Night -> true - else -> isSystemInDarkTheme() - }, - isAmoled = isAmoledMode - ) - ) - } - } - } + PaletteSetting(handling = handling) - SwitchSetting( - settingTitle = { Text(stringResource(R.string.amoled_mode)) }, - settingIcon = { Icon(Icons.Default.DarkMode, null, modifier = Modifier.fillMaxSize()) }, - value = isAmoledMode, - updateValue = { isAmoledMode = it } - ) + HorizontalDivider() - var showBlur by handling.rememberShowBlur() + NavigationBarSettings(handling = handling) - SwitchSetting( - settingTitle = { Text("Show Blur") }, - summaryValue = { - Text("Use blurring to get a glassmorphic look") - }, - settingIcon = { - Icon( - imageVector = if (showBlur) Icons.Default.BlurOn else Icons.Default.BlurOff, - contentDescription = null, - modifier = Modifier.fillMaxSize() - ) - }, - value = showBlur, - updateValue = { showBlur = it } - ) + GridTypeSettings(handling = handling) - HorizontalDivider() + ShareChapterSettings(handling = handling) - var usePalette by handling.rememberUsePalette() + DetailPaneSettings(handling = handling) - SwitchSetting( - settingTitle = { Text("Use Palette") }, - summaryValue = { - Text("Use Palette to color the details screen if possible") - }, - settingIcon = { Icon(Icons.Default.Palette, null, modifier = Modifier.fillMaxSize()) }, - value = usePalette, - updateValue = { usePalette = it } - ) + ShowDownloadSettings(handling = handling) - ShowWhen(usePalette) { - var paletteSwatchType by rememberSwatchType() - ListSetting( - settingTitle = { Text("Swatch Type") }, - dialogIcon = { Icon(Icons.Default.Palette, null) }, - settingIcon = { Icon(Icons.Default.Palette, null, modifier = Modifier.fillMaxSize()) }, - dialogTitle = { Text("Choose a Swatch Type to use") }, - summaryValue = { Text(paletteSwatchType.name) }, - confirmText = { TextButton(onClick = { it.value = false }) { Text(stringResource(R.string.cancel)) } }, - value = paletteSwatchType, - options = PaletteSwatchType.entries, - updateValue = { it, d -> - d.value = false - paletteSwatchType = it - } - ) + HistorySettings(handling = handling) - var paletteStyle by rememberSwatchStyle() - ListSetting( - settingTitle = { Text("Swatch Style") }, - dialogIcon = { Icon(Icons.Default.Palette, null) }, - settingIcon = { Icon(Icons.Default.Palette, null, modifier = Modifier.fillMaxSize()) }, - dialogTitle = { Text("Choose a Swatch Style to use") }, - summaryValue = { Text(paletteStyle.name) }, - confirmText = { TextButton(onClick = { it.value = false }) { Text(stringResource(R.string.cancel)) } }, - value = paletteStyle, - options = PaletteStyle.entries, - updateValue = { it, d -> - d.value = false - paletteStyle = it - } - ) - } + customSettings() + } +} - HorizontalDivider() +@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@Composable +private fun NavigationBarSettings(handling: SettingsHandling) { + var floatingNavigation by rememberFloatingNavigation() - var floatingNavigation by rememberFloatingNavigation() + SwitchSetting( + settingTitle = { Text("Floating Navigation") }, + settingIcon = { Icon(Icons.Default.Navigation, null, modifier = Modifier.fillMaxSize()) }, + value = floatingNavigation, + updateValue = { floatingNavigation = it } + ) + + if (LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Expanded) { + var showAllScreen by handling.rememberShowAll() SwitchSetting( - settingTitle = { Text("Floating Navigation") }, - settingIcon = { Icon(Icons.Default.Navigation, null, modifier = Modifier.fillMaxSize()) }, - value = floatingNavigation, - updateValue = { floatingNavigation = it } + settingTitle = { Text(stringResource(R.string.show_all_screen)) }, + settingIcon = { Icon(Icons.Default.Menu, null, modifier = Modifier.fillMaxSize()) }, + value = showAllScreen, + updateValue = { showAllScreen = it } ) + } else { - var gridChoice by handling.rememberGridChoice() - + var middleNavigationAction by handling.rememberMiddleNavigationAction() ListSetting( - settingTitle = { Text("Grid Type") }, - settingIcon = { Icon(Icons.Default.GridView, null, modifier = Modifier.fillMaxSize()) }, - value = gridChoice, + settingTitle = { Text("Middle Navigation Destination") }, + dialogIcon = { Icon(Icons.Default.LocationOn, null) }, + settingIcon = { Icon(Icons.Default.LocationOn, null, modifier = Modifier.fillMaxSize()) }, + dialogTitle = { Text("Choose a middle navigation destination") }, + summaryValue = { Text(middleNavigationAction.visibleName) }, + confirmText = { TextButton(onClick = { it.value = false }) { Text(stringResource(R.string.cancel)) } }, + value = middleNavigationAction, + options = MiddleNavigationAction.entries.filter { it != MiddleNavigationAction.UNRECOGNIZED }, updateValue = { it, d -> d.value = false - gridChoice = it - }, - options = listOf( - GridChoice.FullAdaptive, - GridChoice.Adaptive, - GridChoice.Fixed - ), - summaryValue = { - Text( - when (gridChoice) { - GridChoice.FullAdaptive -> "Full Adaptive: This will have a dynamic number of columns." - GridChoice.Adaptive -> "Adaptive: This will be adaptive as best it can." - GridChoice.Fixed -> "Fixed: Have a fixed amount of columns. This will be 3 for compact, 5 for medium, and 6 for large." - else -> "None selected" - } - ) - }, - confirmText = { TextButton(onClick = { it.value = false }) { Text(stringResource(R.string.cancel)) } }, - dialogTitle = { Text("Grid Type") }, - dialogIcon = { Icon(Icons.Default.GridView, null) }, + middleNavigationAction = it + } ) - var shareChapter by handling.rememberShareChapter() + MultipleActionsSetting( + handling = handling, + middleNavigationAction = middleNavigationAction + ) - SwitchSetting( - settingTitle = { Text(stringResource(R.string.share_chapters)) }, - settingIcon = { Icon(Icons.Default.Share, null, modifier = Modifier.fillMaxSize()) }, - value = shareChapter, - updateValue = { shareChapter = it } + HorizontalDivider() + } +} + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +private fun MultipleActionsSetting( + handling: SettingsHandling, + middleNavigationAction: MiddleNavigationAction, +) { + var multipleActions by handling.rememberMiddleMultipleActions() + + val multipleActionOptions = MiddleNavigationAction.entries + .filter { it != MiddleNavigationAction.Multiple } + .filter { it != MiddleNavigationAction.UNRECOGNIZED } + + ShowWhen(middleNavigationAction == MiddleNavigationAction.Multiple) { + PreferenceSetting( + settingTitle = { }, + summaryValue = { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth(), + ) { + HorizontalFloatingAppBar( + expanded = true, + leadingContent = { + var showMenu by remember { mutableStateOf(false) } + DropdownMenu( + showMenu, + onDismissRequest = { showMenu = false } + ) { + multipleActionOptions.forEach { + DropdownMenuItem( + text = { Text(it.visibleName) }, + onClick = { + multipleActions = multipleActions.copy { startAction = it } + showMenu = false + } + ) + } + } + + IconButton( + onClick = { showMenu = true } + ) { + Icon( + multipleActions + .startAction + .item + ?.icon + ?.invoke(true) + ?: Icons.Default.Add, + null + ) + } + }, + trailingContent = { + var showMenu by remember { mutableStateOf(false) } + DropdownMenu( + showMenu, + onDismissRequest = { showMenu = false } + ) { + multipleActionOptions.forEach { + DropdownMenuItem( + text = { Text(it.visibleName) }, + onClick = { + multipleActions = multipleActions.copy { endAction = it } + showMenu = false + } + ) + } + } + IconButton( + onClick = { showMenu = true } + ) { + Icon( + multipleActions + .endAction + .item + ?.icon + ?.invoke(true) + ?: Icons.Default.Add, + null + ) + } + }, + ) { + FilledIconButton( + modifier = Modifier.width(64.dp), + onClick = {} + ) { + Icon( + Icons.Filled.UnfoldLess, + contentDescription = "Localized description" + ) + } + } + } + } ) + Spacer(Modifier.height(8.dp)) + } +} - if (LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Expanded) { - var showAllScreen by handling.rememberShowAll() +@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class, ExperimentalComposeUiApi::class) +@Composable +private fun ThemeSetting( + handling: SettingsHandling, + isAmoledMode: Boolean, +) { + var themeSetting by handling.rememberSystemThemeMode() + + val themeText by remember { + derivedStateOf { + when (themeSetting) { + SystemThemeMode.FollowSystem -> "System" + SystemThemeMode.Day -> "Light" + SystemThemeMode.Night -> "Dark" + else -> "None" + } + } + } - SwitchSetting( - settingTitle = { Text(stringResource(R.string.show_all_screen)) }, - settingIcon = { Icon(Icons.Default.Menu, null, modifier = Modifier.fillMaxSize()) }, - value = showAllScreen, - updateValue = { showAllScreen = it } - ) - } else { - var middleNavigationAction by handling.rememberMiddleNavigationAction() - ListSetting( - settingTitle = { Text("Middle Navigation Destination") }, - dialogIcon = { Icon(Icons.Default.LocationOn, null) }, - settingIcon = { Icon(Icons.Default.LocationOn, null, modifier = Modifier.fillMaxSize()) }, - dialogTitle = { Text("Choose a middle navigation destination") }, - summaryValue = { Text(middleNavigationAction.visibleName) }, - confirmText = { TextButton(onClick = { it.value = false }) { Text(stringResource(R.string.cancel)) } }, - value = middleNavigationAction, - options = MiddleNavigationAction.entries, - updateValue = { it, d -> - d.value = false - middleNavigationAction = it + ListSetting( + settingTitle = { Text(stringResource(R.string.theme_choice_title)) }, + dialogIcon = { Icon(Icons.Default.SettingsBrightness, null) }, + settingIcon = { Icon(Icons.Default.SettingsBrightness, null, modifier = Modifier.fillMaxSize()) }, + dialogTitle = { Text(stringResource(R.string.choose_a_theme)) }, + summaryValue = { Text(themeText) }, + confirmText = { TextButton(onClick = { it.value = false }) { Text(stringResource(R.string.cancel)) } }, + value = themeSetting, + options = listOf(SystemThemeMode.FollowSystem, SystemThemeMode.Day, SystemThemeMode.Night), + updateValue = { it, d -> + d.value = false + themeSetting = it + } + ) + + var themeColor by handling.rememberThemeColor() + + ShowMoreSetting( + settingTitle = { Text("Theme Color") }, + settingIcon = { Icon(Icons.Default.Palette, null, modifier = Modifier.fillMaxSize()) }, + summaryValue = { Text(themeColor.name) }, + ) { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterHorizontally), + verticalArrangement = Arrangement.spacedBy(2.dp), + modifier = Modifier.fillMaxWidth(), + ) { + ThemeColor.entries + //TODO: For later + .filter { it != ThemeColor.Custom && it != ThemeColor.UNRECOGNIZED } + .forEach { + ThemeItem( + themeColor = it, + onClick = { themeColor = it }, + selected = it == themeColor, + colorScheme = if (it == ThemeColor.Dynamic) + MaterialTheme.colorScheme + else + rememberDynamicColorScheme( + it.seedColor, + isDark = when (themeSetting) { + SystemThemeMode.FollowSystem -> isSystemInDarkTheme() + SystemThemeMode.Day -> false + SystemThemeMode.Night -> true + else -> isSystemInDarkTheme() + }, + isAmoled = isAmoledMode + ) + ) } - ) } + } +} - var showListDetail by handling.rememberShowListDetail() +@Composable +private fun AmoledModeSetting( + isAmoledMode: Boolean, + onAmoledModeChange: (Boolean) -> Unit, +) { + SwitchSetting( + settingTitle = { Text(stringResource(R.string.amoled_mode)) }, + settingIcon = { Icon(Icons.Default.DarkMode, null, modifier = Modifier.fillMaxSize()) }, + value = isAmoledMode, + updateValue = onAmoledModeChange + ) +} - SwitchSetting( - value = showListDetail, - settingTitle = { Text("Show List Detail Pane for Lists") }, - settingIcon = { - Icon( - if (showListDetail) Icons.AutoMirrored.Filled.List else Icons.AutoMirrored.Filled.ListAlt, - null, - modifier = Modifier.fillMaxSize() - ) - }, - updateValue = { showListDetail = it } +@Composable +private fun BlurSetting(handling: SettingsHandling) { + var showBlur by handling.rememberShowBlur() + + SwitchSetting( + settingTitle = { Text("Show Blur") }, + summaryValue = { + Text("Use blurring to get a glassmorphic look") + }, + settingIcon = { + Icon( + imageVector = if (showBlur) Icons.Default.BlurOn else Icons.Default.BlurOff, + contentDescription = null, + modifier = Modifier.fillMaxSize() + ) + }, + value = showBlur, + updateValue = { showBlur = it } + ) +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@Composable +private fun PaletteSetting(handling: SettingsHandling) { + var usePalette by handling.rememberUsePalette() + + SwitchSetting( + settingTitle = { Text("Use Palette") }, + summaryValue = { + Text("Use Palette to color the details screen if possible") + }, + settingIcon = { Icon(Icons.Default.Palette, null, modifier = Modifier.fillMaxSize()) }, + value = usePalette, + updateValue = { usePalette = it } + ) + + ShowWhen(usePalette) { + var paletteSwatchType by rememberSwatchType() + ListSetting( + settingTitle = { Text("Swatch Type") }, + dialogIcon = { Icon(Icons.Default.Palette, null) }, + settingIcon = { Icon(Icons.Default.Palette, null, modifier = Modifier.fillMaxSize()) }, + dialogTitle = { Text("Choose a Swatch Type to use") }, + summaryValue = { Text(paletteSwatchType.name) }, + confirmText = { TextButton(onClick = { it.value = false }) { Text(stringResource(R.string.cancel)) } }, + value = paletteSwatchType, + options = PaletteSwatchType.entries, + updateValue = { it, d -> + d.value = false + paletteSwatchType = it + } ) - SwitchSetting( - settingTitle = { Text("Show Download Button") }, - settingIcon = { Icon(Icons.Default.Menu, null, modifier = Modifier.fillMaxSize()) }, - value = showDownload, - updateValue = { showDownload = it } + var paletteStyle by rememberSwatchStyle() + ListSetting( + settingTitle = { Text("Swatch Style") }, + dialogIcon = { Icon(Icons.Default.Palette, null) }, + settingIcon = { Icon(Icons.Default.Palette, null, modifier = Modifier.fillMaxSize()) }, + dialogTitle = { Text("Choose a Swatch Style to use") }, + summaryValue = { Text(paletteStyle.name) }, + confirmText = { TextButton(onClick = { it.value = false }) { Text(stringResource(R.string.cancel)) } }, + value = paletteStyle, + options = PaletteStyle.entries, + updateValue = { it, d -> + d.value = false + paletteStyle = it + } ) + } +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@Composable +private fun GridTypeSettings(handling: SettingsHandling) { + var gridChoice by handling.rememberGridChoice() + + ListSetting( + settingTitle = { Text("Grid Type") }, + settingIcon = { Icon(Icons.Default.GridView, null, modifier = Modifier.fillMaxSize()) }, + value = gridChoice, + updateValue = { it, d -> + d.value = false + gridChoice = it + }, + options = listOf( + GridChoice.FullAdaptive, + GridChoice.Adaptive, + GridChoice.Fixed + ), + summaryValue = { + Text( + when (gridChoice) { + GridChoice.FullAdaptive -> "Full Adaptive: This will have a dynamic number of columns." + GridChoice.Adaptive -> "Adaptive: This will be adaptive as best it can." + GridChoice.Fixed -> "Fixed: Have a fixed amount of columns. This will be 3 for compact, 5 for medium, and 6 for large." + else -> "None selected" + } + ) + }, + confirmText = { TextButton(onClick = { it.value = false }) { Text(stringResource(R.string.cancel)) } }, + dialogTitle = { Text("Grid Type") }, + dialogIcon = { Icon(Icons.Default.GridView, null) }, + ) +} - var sliderValue by rememberHistorySave() +@Composable +private fun ShareChapterSettings(handling: SettingsHandling) { + var shareChapter by handling.rememberShareChapter() + + SwitchSetting( + settingTitle = { Text(stringResource(R.string.share_chapters)) }, + settingIcon = { Icon(Icons.Default.Share, null, modifier = Modifier.fillMaxSize()) }, + value = shareChapter, + updateValue = { shareChapter = it } + ) +} - SliderSetting( - sliderValue = sliderValue.toFloat(), - settingTitle = { Text(stringResource(R.string.history_save_title)) }, - settingSummary = { Text(stringResource(R.string.history_save_summary)) }, - settingIcon = { Icon(Icons.Default.ChangeHistory, null) }, - range = -1f..100f, - updateValue = { sliderValue = it.toInt() } - ) +@Composable +private fun DetailPaneSettings(handling: SettingsHandling) { + var showListDetail by handling.rememberShowListDetail() + + SwitchSetting( + value = showListDetail, + settingTitle = { Text(stringResource(R.string.show_list_detail_pane_for_lists)) }, + settingIcon = { + Icon( + if (showListDetail) Icons.AutoMirrored.Filled.List else Icons.AutoMirrored.Filled.ListAlt, + null, + modifier = Modifier.fillMaxSize() + ) + }, + updateValue = { showListDetail = it } + ) +} - customSettings() - } +@Composable +private fun ShowDownloadSettings(handling: SettingsHandling) { + var showDownload by handling.rememberShowDownload() + + SwitchSetting( + settingTitle = { Text(stringResource(R.string.show_download_button)) }, + settingIcon = { Icon(Icons.Default.Menu, null, modifier = Modifier.fillMaxSize()) }, + value = showDownload, + updateValue = { showDownload = it } + ) +} + +@Composable +private fun HistorySettings(handling: SettingsHandling) { + var sliderValue by rememberHistorySave() + + SliderSetting( + sliderValue = sliderValue.toFloat(), + settingTitle = { Text(stringResource(R.string.history_save_title)) }, + settingSummary = { Text(stringResource(R.string.history_save_summary)) }, + settingIcon = { Icon(Icons.Default.ChangeHistory, null) }, + range = -1f..100f, + updateValue = { sliderValue = it.toInt() } + ) } @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/utils/ProtoUtils.kt b/UIViews/src/main/java/com/programmersbox/uiviews/utils/ProtoUtils.kt index 662b0acb6..a2a26e66b 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/utils/ProtoUtils.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/utils/ProtoUtils.kt @@ -19,6 +19,7 @@ import com.programmersbox.uiviews.NotificationSortBy import com.programmersbox.uiviews.Settings import com.programmersbox.uiviews.SystemThemeMode import com.programmersbox.uiviews.ThemeColor +import com.programmersbox.uiviews.middleMultipleActions import com.programmersbox.uiviews.settings import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -73,6 +74,10 @@ object SettingsSerializer : GenericSerializer { amoledMode = false usePalette = true showBlur = true + multipleActions = middleMultipleActions { + startAction = MiddleNavigationAction.All + endAction = MiddleNavigationAction.Notifications + } } override val parseFrom: (input: InputStream) -> Settings get() = Settings::parseFrom } @@ -181,6 +186,16 @@ class SettingsHandling(context: Context) { update = { setMiddleNavigationAction(it) }, defaultValue = MiddleNavigationAction.All, ) + + @Composable + fun rememberMiddleMultipleActions() = preferences.rememberPreference( + key = { it.multipleActions }, + update = { setMultipleActions(it) }, + defaultValue = middleMultipleActions { + startAction = MiddleNavigationAction.All + endAction = MiddleNavigationAction.Notifications + }, + ) } @Composable diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/utils/customsettings/MiddleNavAction.kt b/UIViews/src/main/java/com/programmersbox/uiviews/utils/customsettings/MiddleNavAction.kt index a9f0df4f7..664feaa5b 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/utils/customsettings/MiddleNavAction.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/utils/customsettings/MiddleNavAction.kt @@ -8,11 +8,13 @@ import androidx.compose.material.icons.filled.BrowseGallery import androidx.compose.material.icons.filled.Notifications import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.filled.UnfoldMore import androidx.compose.material.icons.outlined.BrowseGallery import androidx.compose.material.icons.outlined.Notifications import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.StarOutline import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItemColors import androidx.compose.material3.NavigationBarItemDefaults @@ -69,7 +71,13 @@ val MiddleNavigationAction.item: MiddleNavigationItem? screen = Screen.GlobalSearchScreen.Home(), ) - MiddleNavigationAction.UNRECOGNIZED -> null + MiddleNavigationAction.Multiple -> MiddleNavigationItem( + icon = { Icons.Default.UnfoldMore }, + label = R.string.more, + screen = Screen.MoreSettings, + ) + + else -> null } @Composable @@ -98,6 +106,50 @@ fun MiddleNavigationItem.ScreenBottomItem( } } +@Composable +fun MiddleNavigationItem.ScreenBottomItem( + currentDestination: NavDestination?, + navController: NavHostController, + additionalOnClick: () -> Unit, + modifier: Modifier = Modifier, +) { + IconButton( + onClick = { + additionalOnClick() + navController.navigate(screen) { + popUpTo(navController.graph.findStartDestination().id) { saveState = true } + launchSingleTop = true + restoreState = true + } + }, + modifier = modifier + ) { Icon(icon(currentDestination.isTopLevelDestinationInHierarchy(screen)), null) } +} + +@Composable +fun MiddleNavigationAction.ScreenBottomItem( + rowScope: RowScope, + currentDestination: NavDestination?, + navController: NavHostController, + modifier: Modifier = Modifier, + colors: NavigationBarItemColors = NavigationBarItemDefaults.colors(), + multipleClick: () -> Unit, +) { + if (this == MiddleNavigationAction.Multiple) { + with(rowScope) { + NavigationBarItem( + icon = { Icon(Icons.Default.UnfoldMore, null) }, + label = { Text("More") }, + selected = false, + colors = colors, + onClick = multipleClick + ) + } + } else { + item?.ScreenBottomItem(rowScope, currentDestination, navController, modifier, colors) + } +} + private fun NavDestination?.isTopLevelDestinationInHierarchy(destination: Screen) = isTopLevelDestinationInHierarchy(destination.route) private fun NavDestination?.isTopLevelDestinationInHierarchy(destination: String) = this?.hierarchy?.any { diff --git a/UIViews/src/main/proto/settings.proto b/UIViews/src/main/proto/settings.proto index 15f46c6d5..bd3e5ce83 100644 --- a/UIViews/src/main/proto/settings.proto +++ b/UIViews/src/main/proto/settings.proto @@ -20,6 +20,7 @@ message Settings { GridChoice gridChoice = 15; ThemeColor themeColor = 16; MiddleNavigationAction middleNavigationAction = 17; + MiddleMultipleActions multipleActions = 18; } enum SystemThemeMode { @@ -56,4 +57,10 @@ enum MiddleNavigationAction { Lists = 2; Favorites = 3; Search = 4; + Multiple = 5; +} + +message MiddleMultipleActions { + MiddleNavigationAction startAction = 1; + MiddleNavigationAction endAction = 2; } \ No newline at end of file diff --git a/UIViews/src/main/res/values/strings.xml b/UIViews/src/main/res/values/strings.xml index 51a9e9063..60e22f774 100644 --- a/UIViews/src/main/res/values/strings.xml +++ b/UIViews/src/main/res/values/strings.xml @@ -147,6 +147,7 @@ Add a Favorite Hello blank fragment Global Search + More Search Global Search by Name Are you sure you want to delete all notifications? @@ -211,4 +212,6 @@ More Settings For Later Add to For Later list + Show Download Button + Show List Detail Pane for Lists \ No newline at end of file