Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f660b558 authored by Joshua Mokut's avatar Joshua Mokut Committed by Android (Google) Code Review
Browse files

Merge "Added sticky header, when custom app shortcuts limit is used up" into main

parents 6dbefde9 c6f0070e
Loading
Loading
Loading
Loading
+61 −16
Original line number Diff line number Diff line
@@ -473,11 +473,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
    @Test
    fun allowExtendedAppShortcutsCustomization_true_WhenExtraAppsShortcutsCustomizedIsBelowLimit() {
        testScope.runTest {
            setupShortcutHelperWithExtendedAppsShortcutCustomizations(
                numberOfDefaultAppsShortcuts = 3,
                numberOfCustomShortcutsForDefaultApps = 3,
                numberOfCustomShortcutsForExtendedApps = 3,
            )
            openShortcutHelper()

            underTest.toggleCustomizationMode(true)
            val uiState by collectLastValue(underTest.shortcutsUiState)
@@ -491,10 +487,8 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
    @Test
    fun allowExtendedAppShortcutsCustomization_false_WhenExtraAppsShortcutsCustomizedIsAtLimit() {
        testScope.runTest {
            setupShortcutHelperWithExtendedAppsShortcutCustomizations(
                numberOfDefaultAppsShortcuts = 3,
                numberOfCustomShortcutsForDefaultApps = 3,
                numberOfCustomShortcutsForExtendedApps = EXTENDED_APPS_SHORTCUT_CUSTOMIZATION_LIMIT,
            openShortcutHelper(
                customShortcutsCountForExtendedApps = EXTENDED_APPS_SHORTCUT_CUSTOMIZATION_LIMIT
            )

            underTest.toggleCustomizationMode(true)
@@ -507,13 +501,10 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
    }

    @Test
    fun allowExtendedAppShortcutsCustomization_false_WhenExtraAppsShortcutsCustomizedIsAboveLimit() {
    fun showsCustomAppsShortcutLimitHeader_whenAtLimit_customizationModeEnabled() {
        testScope.runTest {
            setupShortcutHelperWithExtendedAppsShortcutCustomizations(
                numberOfDefaultAppsShortcuts = 3,
                numberOfCustomShortcutsForDefaultApps = 3,
                numberOfCustomShortcutsForExtendedApps =
                    EXTENDED_APPS_SHORTCUT_CUSTOMIZATION_LIMIT + 3,
            openShortcutHelper(
                customShortcutsCountForExtendedApps = EXTENDED_APPS_SHORTCUT_CUSTOMIZATION_LIMIT
            )

            underTest.toggleCustomizationMode(true)
@@ -521,10 +512,64 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {

            val activeUiState = uiState as ShortcutsUiState.Active

            assertThat(activeUiState.allowExtendedAppShortcutsCustomization).isFalse()
            assertThat(activeUiState.shouldShowCustomAppsShortcutLimitHeader).isTrue()
        }
    }

    @Test
    fun doesNotShowCustomAppsShortcutLimitHeader_whenBelowLimit_customizationModeEnabled() {
        testScope.runTest {
            openShortcutHelper()

            underTest.toggleCustomizationMode(true)
            val uiState by collectLastValue(underTest.shortcutsUiState)

            val activeUiState = uiState as ShortcutsUiState.Active

            assertThat(activeUiState.shouldShowCustomAppsShortcutLimitHeader).isFalse()
        }
    }

    @Test
    fun doesNotShowCustomAppsShortcutLimitHeader_whenAtLimit_customizationModeDisabled() {
        testScope.runTest {
            openShortcutHelper(
                customShortcutsCountForExtendedApps = EXTENDED_APPS_SHORTCUT_CUSTOMIZATION_LIMIT
            )
            underTest.toggleCustomizationMode(false)
            val uiState by collectLastValue(underTest.shortcutsUiState)

            val activeUiState = uiState as ShortcutsUiState.Active

            assertThat(activeUiState.shouldShowCustomAppsShortcutLimitHeader).isFalse()
        }
    }

    @Test
    fun doesNotShowCustomAppsShortcutLimitHeader_whenBelowLimit_customizationModeDisabled() {
        testScope.runTest {
            openShortcutHelper()
            underTest.toggleCustomizationMode(false)
            val uiState by collectLastValue(underTest.shortcutsUiState)

            val activeUiState = uiState as ShortcutsUiState.Active

            assertThat(activeUiState.shouldShowCustomAppsShortcutLimitHeader).isFalse()
        }
    }

    private fun openShortcutHelper(
        customShortcutsCountForExtendedApps: Int = 0,
        customShortcutsCountForDefaultApps: Int = 3,
        defaultShortcutsCount: Int = 3,
    ) {
        setupShortcutHelperWithExtendedAppsShortcutCustomizations(
            numberOfDefaultAppsShortcuts = defaultShortcutsCount,
            numberOfCustomShortcutsForDefaultApps = customShortcutsCountForDefaultApps,
            numberOfCustomShortcutsForExtendedApps = customShortcutsCountForExtendedApps,
        )
    }

    private fun setupShortcutHelperWithExtendedAppsShortcutCustomizations(
        numberOfDefaultAppsShortcuts: Int,
        numberOfCustomShortcutsForDefaultApps: Int,
+10 −0
Original line number Diff line number Diff line
@@ -4194,6 +4194,16 @@
         The helper is a component that shows the user which keyboard shortcuts they can use.
         [CHAR LIMIT=NONE] -->
    <string name="shortcut_helper_delete_shortcut_button_label">Delete shortcut</string>
    <!-- Message displayed at the top of shortcut helper when user has already added 10 custom
         keyboard shortcuts for extra applications in shortcut helper. The helper is a component
         that shows the user which keyboard shortcuts they can use and allows users to customize
         their keyboard shortcuts-->
    <string name="shortcut_helper_app_custom_shortcut_limit_exceeded">10 app custom shortcut limit has been used up</string>
    <!-- Instruction displayed at the top of shortcut helper when user has already added 10 custom
         keyboard shortcuts for extra applications in shortcut helper. The helper is a component
         that shows the user which keyboard shortcuts they can use and allows users to customize
         their keyboard shortcuts-->
    <string name="shortcut_helper_app_custom_shortcut_limit_exceeded_instruction">Delete a shortcut to be able to add a new one</string>

    <!-- Keyboard touchpad tutorial scheduler-->
    <!-- Notification title for launching keyboard tutorial [CHAR_LIMIT=100] -->
+111 −55
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.DeleteOutline
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Tune
@@ -121,6 +122,7 @@ import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
@@ -132,6 +134,7 @@ import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import com.android.systemui.res.R
import kotlinx.coroutines.delay

// TODO break down this file into smaller files for readability. b/424757065
@Composable
fun ShortcutHelper(
    onSearchQueryChanged: (String) -> Unit,
@@ -187,18 +190,12 @@ private fun ActiveShortcutHelper(
        )
    } else {
        ShortcutHelperTwoPane(
            shortcutsUiState.searchQuery,
            onSearchQueryChanged,
            shortcutsUiState.shortcutCategories,
            selectedCategoryType,
            onCategorySelected = { selectedCategoryType = it },
            onKeyboardSettingsClicked,
            shortcutsUiState.isShortcutCustomizerFlagEnabled,
            shortcutsUiState.shouldShowResetButton,
            shortcutsUiState.isCustomizationModeEnabled,
            onCustomizationModeToggled,
            shortcutsUiState.isExtendedAppCategoryFlagEnabled,
            shortcutsUiState.allowExtendedAppShortcutsCustomization,
            shortcutsUiState,
            modifier,
            onShortcutCustomizationRequested,
        )
@@ -382,22 +379,17 @@ private fun ShortcutSubCategorySinglePane(searchQuery: String, subCategory: Shor

@Composable
private fun ShortcutHelperTwoPane(
    searchQuery: String,
    onSearchQueryChanged: (String) -> Unit,
    categories: List<ShortcutCategoryUi>,
    selectedCategoryType: ShortcutCategoryType?,
    onCategorySelected: (ShortcutCategoryType?) -> Unit,
    onKeyboardSettingsClicked: () -> Unit,
    isShortcutCustomizerFlagEnabled: Boolean,
    shouldShowResetButton: Boolean,
    isCustomizationModeEnabled: Boolean,
    onCustomizationModeToggled: (isCustomizing: Boolean) -> Unit,
    isExtendedAppCategoryFlagEnabled: Boolean,
    allowExtendedAppShortcutsCustomization: Boolean,
    uiState: ShortcutsUiState.Active,
    modifier: Modifier = Modifier,
    onShortcutCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
) {
    val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
    val selectedCategory =
        uiState.shortcutCategories.fastFirstOrNull { it.type == selectedCategoryType }

    Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) {
        Row(
@@ -408,19 +400,19 @@ private fun ShortcutHelperTwoPane(
            // Keep title centered whether customize button is visible or not.
            Spacer(modifier = Modifier.weight(1f))
            Box(modifier = Modifier.width(412.dp), contentAlignment = Alignment.Center) {
                TitleBar(isCustomizationModeEnabled)
                TitleBar(uiState.isCustomizationModeEnabled)
            }
            if (isShortcutCustomizerFlagEnabled) {
            if (uiState.isShortcutCustomizerFlagEnabled) {
                CustomizationButtonsContainer(
                    modifier = Modifier.weight(1f),
                    isCustomizing = isCustomizationModeEnabled,
                    isCustomizing = uiState.isCustomizationModeEnabled,
                    onToggleCustomizationMode = {
                        onCustomizationModeToggled(!isCustomizationModeEnabled)
                        onCustomizationModeToggled(!uiState.isCustomizationModeEnabled)
                    },
                    onReset = {
                        onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
                    },
                    shouldShowResetButton = shouldShowResetButton,
                    shouldShowResetButton = uiState.shouldShowResetButton,
                )
            } else {
                Spacer(modifier = Modifier.weight(1f))
@@ -431,20 +423,16 @@ private fun ShortcutHelperTwoPane(
            StartSidePanel(
                onSearchQueryChanged = onSearchQueryChanged,
                modifier = Modifier.width(240.dp).semantics { isTraversalGroup = true },
                categories = categories,
                categories = uiState.shortcutCategories,
                onKeyboardSettingsClicked = onKeyboardSettingsClicked,
                selectedCategory = selectedCategoryType,
                onCategoryClicked = { onCategorySelected(it.type) },
            )
            Spacer(modifier = Modifier.width(24.dp))
            EndSidePanel(
                searchQuery,
                isCustomizationModeEnabled,
                uiState,
                onCustomizationModeToggled,
                selectedCategory,
                isCustomizing = isCustomizationModeEnabled,
                isExtendedAppCategoryFlagEnabled,
                allowExtendedAppShortcutsCustomization,
                Modifier.fillMaxSize().padding(top = 8.dp).semantics { isTraversalGroup = true },
                onShortcutCustomizationRequested,
            )
@@ -511,17 +499,14 @@ private fun DoneButton(onClick: () -> Unit) {

@Composable
private fun EndSidePanel(
    searchQuery: String,
    isCustomizationModeEnabled: Boolean,
    uiState: ShortcutsUiState.Active,
    onCustomizationModeToggled: (isCustomizing: Boolean) -> Unit,
    category: ShortcutCategoryUi?,
    isCustomizing: Boolean,
    isExtendedAppCategoryFlagEnabled: Boolean,
    allowExtendedAppShortcutsCustomization: Boolean,
    modifier: Modifier = Modifier,
    onShortcutCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
) {
    val listState = rememberLazyListState()

    LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
    if (category == null) {
        NoSearchResultsText(horizontalPadding = 24.dp, fillHeight = false)
@@ -532,36 +517,39 @@ private fun EndSidePanel(
        state = listState,
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        stickyHeader {
            Column {
                AnimatedVisibility(
                    category.type == AppCategories &&
                        uiState.shouldShowCustomAppsShortcutLimitHeader
                ) {
                    AppCustomShortcutLimitContainer(Modifier.padding(8.dp))
                }
            }
        }
        items(category.subCategories) { subcategory ->
            SubCategoryContainerDualPane(
                searchQuery = searchQuery,
                subCategory = subcategory,
                isCustomizing = isCustomizing and category.type.includeInCustomization,
                uiState.searchQuery,
                subcategory,
                isCustomizing =
                    uiState.isCustomizationModeEnabled && category.type.includeInCustomization,
                onShortcutCustomizationRequested = { requestInfo ->
                    when (requestInfo) {
                        is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add ->
                            onShortcutCustomizationRequested(
                                requestInfo.copy(categoryType = category.type)
                            )

                        is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete ->
                            onShortcutCustomizationRequested(
                                requestInfo.copy(categoryType = category.type)
                    onShortcutCustomizationRequestedInSubCategory(
                        requestInfo,
                        onShortcutCustomizationRequested,
                        category.type,
                    )

                        ShortcutCustomizationRequestInfo.Reset ->
                            onShortcutCustomizationRequested(requestInfo)
                    }
                },
                allowExtendedAppShortcutsCustomization = allowExtendedAppShortcutsCustomization,
                uiState.allowExtendedAppShortcutsCustomization,
            )
            Spacer(modifier = Modifier.height(8.dp))
        }

        if (
            category.type == ShortcutCategoryType.AppCategories &&
                !isCustomizationModeEnabled &&
                isExtendedAppCategoryFlagEnabled &&
                allowExtendedAppShortcutsCustomization
            category.type == AppCategories &&
                !uiState.isCustomizationModeEnabled &&
                uiState.isExtendedAppCategoryFlagEnabled &&
                uiState.allowExtendedAppShortcutsCustomization
        ) {
            item {
                ShortcutHelperButton(
@@ -577,6 +565,73 @@ private fun EndSidePanel(
    }
}

private fun onShortcutCustomizationRequestedInSubCategory(
    requestInfo: ShortcutCustomizationRequestInfo,
    onShortcutCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit,
    categoryType: ShortcutCategoryType,
) {
    when (requestInfo) {
        is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add ->
            onShortcutCustomizationRequested(requestInfo.copy(categoryType = categoryType))

        is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete ->
            onShortcutCustomizationRequested(requestInfo.copy(categoryType = categoryType))

        ShortcutCustomizationRequestInfo.Reset -> onShortcutCustomizationRequested(requestInfo)
    }
}

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun AppCustomShortcutLimitContainer(modifier: Modifier = Modifier) {
    Row(
        modifier =
            modifier
                .fillMaxWidth()
                .background(
                    color = MaterialTheme.colorScheme.secondaryContainer,
                    shape = RoundedCornerShape(40.dp),
                )
                .padding(16.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(12.dp),
    ) {
        Surface(
            shape = CircleShape,
            modifier = Modifier.size(40.dp),
            color = MaterialTheme.colorScheme.secondary,
        ) {
            Icon(
                imageVector = Icons.Default.Info,
                tint = MaterialTheme.colorScheme.onSecondary,
                modifier = Modifier.size(24.dp).padding(8.dp),
                contentDescription = null,
            )
        }

        Column(
            horizontalAlignment = Alignment.Start,
            verticalArrangement = Arrangement.spacedBy(2.dp),
        ) {
            Text(
                text = stringResource(R.string.shortcut_helper_app_custom_shortcut_limit_exceeded),
                color = MaterialTheme.colorScheme.onSecondaryContainer,
                style = MaterialTheme.typography.titleMediumEmphasized,
                textAlign = TextAlign.Center,
            )
            Text(
                text =
                    stringResource(
                        R.string.shortcut_helper_app_custom_shortcut_limit_exceeded_instruction
                    ),
                style = MaterialTheme.typography.labelMedium,
                color = MaterialTheme.colorScheme.onSecondaryContainer,
                textAlign = TextAlign.Center,
            )
        }
    }
}

@Composable
private fun NoSearchResultsText(horizontalPadding: Dp, fillHeight: Boolean) {
    var modifier = Modifier.fillMaxWidth()
@@ -609,7 +664,7 @@ private fun SubCategoryContainerDualPane(
        color = MaterialTheme.colorScheme.surfaceBright,
    ) {
        Column(Modifier.padding(16.dp)) {
            SubCategoryTitle(subCategory.label)
            SubCategoryTitle(subCategory.label, Modifier.padding(8.dp))
            Spacer(Modifier.height(8.dp))
            subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
                if (index > 0) {
@@ -647,11 +702,12 @@ private fun SubCategoryContainerDualPane(
}

@Composable
private fun SubCategoryTitle(title: String) {
private fun SubCategoryTitle(title: String, modifier: Modifier = Modifier) {
    Text(
        title,
        style = MaterialTheme.typography.titleSmall,
        color = MaterialTheme.colorScheme.primary,
        modifier = modifier,
    )
}

+1 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ sealed interface ShortcutsUiState {
        val shouldShowResetButton: Boolean = false,
        val isCustomizationModeEnabled: Boolean = false,
        val allowExtendedAppShortcutsCustomization: Boolean = true,
        val shouldShowCustomAppsShortcutLimitHeader: Boolean = false,
    ) : ShortcutsUiState

    data object Inactive : ShortcutsUiState
+11 −4
Original line number Diff line number Diff line
@@ -98,6 +98,12 @@ constructor(
                    val filteredCategories =
                        filterCategoriesBySearchQuery(query, categoriesWithLauncherExcluded)
                    val shortcutCategoriesUi = convertCategoriesModelToUiModel(filteredCategories)

                    val allowExtendedAppShortcutsCustomization =
                        !isExtendedAppsShortcutCustomizationLimitReached(
                            shortcutCategories = categoriesWithLauncherExcluded
                        )

                    ShortcutsUiState.Active(
                        searchQuery = query,
                        shortcutCategories = shortcutCategoriesUi,
@@ -107,10 +113,11 @@ constructor(
                        isExtendedAppCategoryFlagEnabled = extendedAppsShortcutCategory(),
                        shouldShowResetButton = shouldShowResetButton(shortcutCategoriesUi),
                        isCustomizationModeEnabled = isCustomizationModeEnabled,
                        allowExtendedAppShortcutsCustomization =
                            !isExtendedAppsShortcutCustomizationLimitReached(
                                categoriesWithLauncherExcluded
                            ),
                        allowExtendedAppShortcutsCustomization,
                        shouldShowCustomAppsShortcutLimitHeader =
                            isCustomizationModeEnabled &&
                                extendedAppsShortcutCategory() &&
                                !allowExtendedAppShortcutsCustomization,
                    )
                }
            }