Merge "Updated AppList to allow custom message and display summary for empty list, added a button appItem as part of requirements for background install control feature."

This commit is contained in:
George Chan
2022-12-21 04:33:23 +00:00
committed by Android (Google) Code Review
5 changed files with 169 additions and 3 deletions

View File

@@ -0,0 +1,47 @@
/*
* 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.spa.widget.preference
import com.android.settingslib.spa.framework.util.EntryHighlight
import androidx.compose.material3.IconButton
import androidx.compose.runtime.State
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.material3.Icon
@Composable
fun TwoTargetButtonPreference(
title: String,
summary: State<String>,
icon: @Composable (() -> Unit)? = null,
onClick: () -> Unit,
buttonIcon: ImageVector,
buttonIconDescription: String,
onButtonClick: () -> Unit
) {
EntryHighlight {
TwoTargetPreference(
title = title,
summary = summary,
onClick = onClick,
icon = icon) {
IconButton(onClick = onButtonClick) {
Icon(imageVector = buttonIcon, contentDescription = buttonIconDescription)
}
}
}
}

View File

@@ -0,0 +1,73 @@
package com.android.settingslib.spa.widget.preference
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.compose.toState
import com.google.common.truth.Truth
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
private const val TEST_MODEL_TITLE = "TwoTargetButtonPreference"
private const val TEST_MODEL_SUMMARY = "TestSummary"
private const val TEST_BUTTON_ICON_DESCRIPTION = "TestButtonIconDescription"
private val TEST_BUTTON_ICON = Icons.Outlined.Delete
@RunWith(AndroidJUnit4::class)
class TwoTargetButtonPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
testTwoTargetButtonPreference()
}
composeTestRule.onNodeWithText(TEST_MODEL_TITLE).assertIsDisplayed()
}
@Test
fun clickable_label_canBeClicked() {
var clicked = false
composeTestRule.setContent {
testTwoTargetButtonPreference(onClick = { clicked = true })
}
composeTestRule.onNodeWithText(TEST_MODEL_TITLE).performClick()
Truth.assertThat(clicked).isTrue()
}
@Test
fun clickable_button_label_canBeClicked() {
var clicked = false
composeTestRule.setContent {
testTwoTargetButtonPreference(onButtonClick = { clicked = true })
}
composeTestRule.onNodeWithContentDescription(TEST_BUTTON_ICON_DESCRIPTION).performClick()
Truth.assertThat(clicked).isTrue()
}
}
@Composable
private fun testTwoTargetButtonPreference(
onClick: () -> Unit = {},
onButtonClick: () -> Unit = {},
) {
TwoTargetButtonPreference(
title = TEST_MODEL_TITLE,
summary = TEST_MODEL_SUMMARY.toState(),
onClick = onClick,
buttonIcon = TEST_BUTTON_ICON,
buttonIconDescription = TEST_BUTTON_ICON_DESCRIPTION,
onButtonClick = onButtonClick
)
}

View File

@@ -60,6 +60,7 @@ data class AppListInput<T : AppRecord>(
val listModel: AppListModel<T>,
val state: AppListState,
val header: @Composable () -> Unit,
val noItemMessage: String? = null,
val bottomPadding: Dp,
)
@@ -79,7 +80,7 @@ internal fun <T : AppRecord> AppListInput<T>.AppListImpl(
) {
LogCompositions(TAG, config.userId.toString())
val appListData = appListDataSupplier()
listModel.AppListWidget(appListData, header, bottomPadding)
listModel.AppListWidget(appListData, header, bottomPadding, noItemMessage)
}
@Composable
@@ -87,12 +88,14 @@ private fun <T : AppRecord> AppListModel<T>.AppListWidget(
appListData: State<AppListData<T>?>,
header: @Composable () -> Unit,
bottomPadding: Dp,
noItemMessage: String?
) {
val timeMeasurer = rememberTimeMeasurer(TAG)
appListData.value?.let { (list, option) ->
timeMeasurer.logFirst("app list first loaded")
if (list.isEmpty()) {
PlaceholderTitle(stringResource(R.string.no_applications))
header()
PlaceholderTitle(noItemMessage ?: stringResource(R.string.no_applications))
return
}
LazyColumn(
@@ -151,4 +154,4 @@ private fun <T : AppRecord> loadAppListData(
) { viewModel.reloadApps() }
return viewModel.appListDataFlow.collectAsState(null, Dispatchers.IO)
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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 androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spa.widget.preference.TwoTargetButtonPreference
@Composable
fun <T : AppRecord> AppListItemModel<T>.AppListButtonItem (
onClick: () -> Unit,
onButtonClick: () -> Unit,
buttonIcon: ImageVector,
buttonIconDescription: String,
) {
TwoTargetButtonPreference(
title = label,
summary = this@AppListButtonItem.summary,
icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
onClick = onClick,
buttonIcon = buttonIcon,
buttonIconDescription = buttonIconDescription,
onButtonClick = onButtonClick
)
}

View File

@@ -45,6 +45,7 @@ fun <T : AppRecord> AppListPage(
listModel: AppListModel<T>,
showInstantApps: Boolean = false,
primaryUserOnly: Boolean = false,
noItemMessage: String? = null,
moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
header: @Composable () -> Unit = {},
appList: @Composable AppListInput<T>.() -> Unit = { AppList() },
@@ -77,6 +78,7 @@ fun <T : AppRecord> AppListPage(
),
header = header,
bottomPadding = bottomPadding,
noItemMessage = noItemMessage,
)
appList(appListInput)
}