From a907c9d949f406fe5e69e5c3f6b491dad003b476 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Tue, 15 Nov 2022 16:49:02 +0800 Subject: [PATCH] Support grouping in the New App List By adding getGroupTitle() to AppListModel. Default no grouping. Note: App items should be sorted by group in the getComparator() first, the new getGroupTitle() will not change the list order. Bug: 259206313 Test: Unit test Change-Id: I96fe8615c1f71066288e01d95805a85d5caaff13 --- .../spaprivileged/model/app/AppListModel.kt | 8 +++ .../spaprivileged/template/app/AppList.kt | 19 +++++- .../spaprivileged/template/app/AppListTest.kt | 60 +++++++++++++++---- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt index 373b57f73ef02..a7122d0eb03ad 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt @@ -53,6 +53,14 @@ interface AppListModel { { it.record.app.uid }, ) + /** + * Gets the group title of this item. + * + * Note: Items should be sorted by group in [getComparator] first, this [getGroupTitle] will not + * change the list order. + */ + fun getGroupTitle(option: Int, record: T): String? = null + /** * Gets the summary for the given app record. * diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt index 7f5fe9f75d51f..681eb1c3508ed 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt @@ -34,9 +34,11 @@ import com.android.settingslib.spa.framework.compose.LogCompositions import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll import com.android.settingslib.spa.framework.compose.toState +import com.android.settingslib.spa.widget.ui.CategoryTitle import com.android.settingslib.spa.widget.ui.PlaceholderTitle import com.android.settingslib.spaprivileged.R import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser +import com.android.settingslib.spaprivileged.model.app.AppEntry import com.android.settingslib.spaprivileged.model.app.AppListConfig import com.android.settingslib.spaprivileged.model.app.AppListData import com.android.settingslib.spaprivileged.model.app.AppListModel @@ -100,17 +102,28 @@ private fun AppListWidget( } items(count = list.size, key = { option to list[it].record.app.packageName }) { + remember(list) { listModel.getGroupTitleIfFirst(option, list, it) } + ?.let { group -> CategoryTitle(title = group) } + val appEntry = list[it] val summary = listModel.getSummary(option, appEntry.record) ?: "".toState() - val itemModel = remember(appEntry) { + appItem(remember(appEntry) { AppListItemModel(appEntry.record, appEntry.label, summary) - } - appItem(itemModel) + }) } } } } +/** Returns group title if this is the first item of the group. */ +private fun AppListModel.getGroupTitleIfFirst( + option: Int, + list: List>, + index: Int, +): String? = getGroupTitle(option, list[index].record)?.takeIf { + index == 0 || it != getGroupTitle(option, list[index - 1].record) +} + @Composable private fun loadAppListData( config: AppListConfig, diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt index 80c4eac9b98fd..9f20c78485a87 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt @@ -58,26 +58,43 @@ class AppListTest { @Test fun couldShowAppItem() { - setContent(appEntries = listOf(APP_ENTRY)) + setContent(appEntries = listOf(APP_ENTRY_A)) - composeTestRule.onNodeWithText(APP_ENTRY.label).assertIsDisplayed() + composeTestRule.onNodeWithText(APP_ENTRY_A.label).assertIsDisplayed() } @Test fun couldShowHeader() { - setContent(header = { Text(HEADER) }, appEntries = listOf(APP_ENTRY)) + setContent(appEntries = listOf(APP_ENTRY_A), header = { Text(HEADER) }) composeTestRule.onNodeWithText(HEADER).assertIsDisplayed() } + @Test + fun whenNotGrouped_groupTitleDoesNotExist() { + setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = false) + + composeTestRule.onNodeWithText(GROUP_A).assertDoesNotExist() + composeTestRule.onNodeWithText(GROUP_B).assertDoesNotExist() + } + + @Test + fun whenGrouped_groupTitleDisplayed() { + setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = true) + + composeTestRule.onNodeWithText(GROUP_A).assertIsDisplayed() + composeTestRule.onNodeWithText(GROUP_B).assertIsDisplayed() + } + private fun setContent( - header: @Composable () -> Unit = {}, appEntries: List>, + header: @Composable () -> Unit = {}, + enableGrouping: Boolean = false, ) { composeTestRule.setContent { AppList( config = AppListConfig(userId = USER_ID, showInstantApps = false), - listModel = TestAppListModel(), + listModel = TestAppListModel(enableGrouping), state = AppListState( showSystem = false.toState(), option = 0.toState(), @@ -96,17 +113,37 @@ class AppListTest { private companion object { const val USER_ID = 0 const val HEADER = "Header" - val APP_ENTRY = AppEntry( - record = TestAppRecord(ApplicationInfo()), - label = "AAA", + const val GROUP_A = "Group A" + const val GROUP_B = "Group B" + val APP_ENTRY_A = AppEntry( + record = TestAppRecord( + app = ApplicationInfo().apply { + packageName = "package.name.a" + }, + group = GROUP_A, + ), + label = "Label A", + labelCollationKey = CollationKey("", byteArrayOf()), + ) + val APP_ENTRY_B = AppEntry( + record = TestAppRecord( + app = ApplicationInfo().apply { + packageName = "package.name.b" + }, + group = GROUP_B, + ), + label = "Label B", labelCollationKey = CollationKey("", byteArrayOf()), ) } } -private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord +private data class TestAppRecord( + override val app: ApplicationInfo, + val group: String? = null, +) : AppRecord -private class TestAppListModel : AppListModel { +private class TestAppListModel(val enableGrouping: Boolean) : AppListModel { override fun transform(userIdFlow: Flow, appListFlow: Flow>) = appListFlow.asyncMapItem { TestAppRecord(it) } @@ -118,4 +155,7 @@ private class TestAppListModel : AppListModel { option: Int, recordListFlow: Flow>, ) = recordListFlow + + override fun getGroupTitle(option: Int, record: TestAppRecord) = + if (enableGrouping) record.group else null }