summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt46
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt14
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt121
3 files changed, 160 insertions, 21 deletions
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 3cd8378b8960..9d6b311a33cb 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
@@ -41,6 +41,13 @@ import com.android.settingslib.spaprivileged.model.app.AppRecord
import kotlinx.coroutines.Dispatchers
private const val TAG = "AppList"
+private const val CONTENT_TYPE_HEADER = "header"
+
+internal data class AppListState(
+ val showSystem: State<Boolean>,
+ val option: State<Int>,
+ val searchQuery: State<String>,
+)
/**
* The template to render an App List.
@@ -49,23 +56,26 @@ private const val TAG = "AppList"
*/
@Composable
internal fun <T : AppRecord> AppList(
- appListConfig: AppListConfig,
+ config: AppListConfig,
listModel: AppListModel<T>,
- showSystem: State<Boolean>,
- option: State<Int>,
- searchQuery: State<String>,
+ state: AppListState,
+ header: @Composable () -> Unit,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
bottomPadding: Dp,
+ appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
+ loadAppListData(config, listModel, state)
+ },
) {
- LogCompositions(TAG, appListConfig.userId.toString())
- val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
- AppListWidget(appListData, listModel, appItem, bottomPadding)
+ LogCompositions(TAG, config.userId.toString())
+ val appListData = appListDataSupplier()
+ AppListWidget(appListData, listModel, header, appItem, bottomPadding)
}
@Composable
private fun <T : AppRecord> AppListWidget(
appListData: State<AppListData<T>?>,
listModel: AppListModel<T>,
+ header: @Composable () -> Unit,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
bottomPadding: Dp,
) {
@@ -81,6 +91,10 @@ private fun <T : AppRecord> AppListWidget(
state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
contentPadding = PaddingValues(bottom = bottomPadding),
) {
+ item(contentType = CONTENT_TYPE_HEADER) {
+ header()
+ }
+
items(count = list.size, key = { option to list[it].record.app.packageName }) {
val appEntry = list[it]
val summary = listModel.getSummary(option, appEntry.record) ?: "".toState()
@@ -94,19 +108,17 @@ private fun <T : AppRecord> AppListWidget(
}
@Composable
-private fun <T : AppRecord> loadAppEntries(
- appListConfig: AppListConfig,
+private fun <T : AppRecord> loadAppListData(
+ config: AppListConfig,
listModel: AppListModel<T>,
- showSystem: State<Boolean>,
- option: State<Int>,
- searchQuery: State<String>,
+ state: AppListState,
): State<AppListData<T>?> {
- val viewModel: AppListViewModel<T> = viewModel(key = appListConfig.userId.toString())
- viewModel.appListConfig.setIfAbsent(appListConfig)
+ val viewModel: AppListViewModel<T> = viewModel(key = config.userId.toString())
+ viewModel.appListConfig.setIfAbsent(config)
viewModel.listModel.setIfAbsent(listModel)
- viewModel.showSystem.Sync(showSystem)
- viewModel.option.Sync(option)
- viewModel.searchQuery.Sync(searchQuery)
+ viewModel.showSystem.Sync(state.showSystem)
+ viewModel.option.Sync(state.option)
+ viewModel.searchQuery.Sync(state.searchQuery)
return viewModel.appListDataFlow.collectAsState(null, Dispatchers.Default)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 29533679d9c1..388a7d87a1bd 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -37,6 +37,8 @@ import com.android.settingslib.spaprivileged.template.common.WorkProfilePager
/**
* The full screen template for an App List page.
+ *
+ * @param header the description header appears before all the applications.
*/
@Composable
fun <T : AppRecord> AppListPage(
@@ -44,6 +46,7 @@ fun <T : AppRecord> AppListPage(
listModel: AppListModel<T>,
showInstantApps: Boolean = false,
primaryUserOnly: Boolean = false,
+ header: @Composable () -> Unit = {},
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
) {
val showSystem = rememberSaveable { mutableStateOf(false) }
@@ -59,14 +62,17 @@ fun <T : AppRecord> AppListPage(
val selectedOption = rememberSaveable { mutableStateOf(0) }
Spinner(options, selectedOption.value) { selectedOption.value = it }
AppList(
- appListConfig = AppListConfig(
+ config = AppListConfig(
userId = userInfo.id,
showInstantApps = showInstantApps,
),
listModel = listModel,
- showSystem = showSystem,
- option = selectedOption,
- searchQuery = searchQuery,
+ state = AppListState(
+ showSystem = showSystem,
+ option = selectedOption,
+ searchQuery = searchQuery,
+ ),
+ header = header,
appItem = appItem,
bottomPadding = bottomPadding,
)
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
new file mode 100644
index 000000000000..80c4eac9b98f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.icu.text.CollationKey
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spaprivileged.R
+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
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private var context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun whenNoApps() {
+ setContent(appEntries = emptyList())
+
+ composeTestRule.onNodeWithText(context.getString(R.string.no_applications))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun couldShowAppItem() {
+ setContent(appEntries = listOf(APP_ENTRY))
+
+ composeTestRule.onNodeWithText(APP_ENTRY.label).assertIsDisplayed()
+ }
+
+ @Test
+ fun couldShowHeader() {
+ setContent(header = { Text(HEADER) }, appEntries = listOf(APP_ENTRY))
+
+ composeTestRule.onNodeWithText(HEADER).assertIsDisplayed()
+ }
+
+ private fun setContent(
+ header: @Composable () -> Unit = {},
+ appEntries: List<AppEntry<TestAppRecord>>,
+ ) {
+ composeTestRule.setContent {
+ AppList(
+ config = AppListConfig(userId = USER_ID, showInstantApps = false),
+ listModel = TestAppListModel(),
+ state = AppListState(
+ showSystem = false.toState(),
+ option = 0.toState(),
+ searchQuery = "".toState(),
+ ),
+ header = header,
+ appItem = { AppListItem(it) {} },
+ bottomPadding = 0.dp,
+ appListDataSupplier = {
+ stateOf(AppListData(appEntries, option = 0))
+ }
+ )
+ }
+ }
+
+ private companion object {
+ const val USER_ID = 0
+ const val HEADER = "Header"
+ val APP_ENTRY = AppEntry(
+ record = TestAppRecord(ApplicationInfo()),
+ label = "AAA",
+ labelCollationKey = CollationKey("", byteArrayOf()),
+ )
+ }
+}
+
+private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
+
+private class TestAppListModel : AppListModel<TestAppRecord> {
+ override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+ appListFlow.asyncMapItem { TestAppRecord(it) }
+
+ @Composable
+ override fun getSummary(option: Int, record: TestAppRecord) = null
+
+ override fun filter(
+ userIdFlow: Flow<Int>,
+ option: Int,
+ recordListFlow: Flow<List<TestAppRecord>>,
+ ) = recordListFlow
+}