Merge "Add spinner options to "All apps""
This commit is contained in:
@@ -16,10 +16,12 @@
|
|||||||
|
|
||||||
package com.android.settings.spa.app
|
package com.android.settings.spa.app
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
@@ -28,9 +30,12 @@ import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
|
|||||||
import com.android.settingslib.spa.framework.common.SettingsPage
|
import com.android.settingslib.spa.framework.common.SettingsPage
|
||||||
import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
||||||
import com.android.settingslib.spa.framework.compose.navigator
|
import com.android.settingslib.spa.framework.compose.navigator
|
||||||
|
import com.android.settingslib.spa.framework.compose.rememberContext
|
||||||
|
import com.android.settingslib.spa.framework.util.filterItem
|
||||||
import com.android.settingslib.spa.framework.util.mapItem
|
import com.android.settingslib.spa.framework.util.mapItem
|
||||||
import com.android.settingslib.spa.widget.preference.Preference
|
import com.android.settingslib.spa.widget.preference.Preference
|
||||||
import com.android.settingslib.spa.widget.preference.PreferenceModel
|
import com.android.settingslib.spa.widget.preference.PreferenceModel
|
||||||
|
import com.android.settingslib.spa.widget.ui.SpinnerOption
|
||||||
import com.android.settingslib.spaprivileged.model.app.AppListModel
|
import com.android.settingslib.spaprivileged.model.app.AppListModel
|
||||||
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
||||||
import com.android.settingslib.spaprivileged.template.app.AppList
|
import com.android.settingslib.spaprivileged.template.app.AppList
|
||||||
@@ -67,7 +72,7 @@ fun AllAppListPage(
|
|||||||
val resetAppDialogPresenter = rememberResetAppDialogPresenter()
|
val resetAppDialogPresenter = rememberResetAppDialogPresenter()
|
||||||
AppListPage(
|
AppListPage(
|
||||||
title = stringResource(R.string.all_apps),
|
title = stringResource(R.string.all_apps),
|
||||||
listModel = remember { AllAppListModel() },
|
listModel = rememberContext(::AllAppListModel),
|
||||||
showInstantApps = true,
|
showInstantApps = true,
|
||||||
moreOptions = { ResetAppPreferences(resetAppDialogPresenter::open) },
|
moreOptions = { ResetAppPreferences(resetAppDialogPresenter::open) },
|
||||||
appList = appList,
|
appList = appList,
|
||||||
@@ -79,17 +84,71 @@ data class AppRecordWithSize(
|
|||||||
) : AppRecord
|
) : AppRecord
|
||||||
|
|
||||||
class AllAppListModel(
|
class AllAppListModel(
|
||||||
private val getSummary: @Composable ApplicationInfo.() -> State<String> = { getStorageSize() },
|
private val context: Context,
|
||||||
|
private val getStorageSummary: @Composable ApplicationInfo.() -> State<String> = {
|
||||||
|
getStorageSize()
|
||||||
|
},
|
||||||
) : AppListModel<AppRecordWithSize> {
|
) : AppListModel<AppRecordWithSize> {
|
||||||
|
|
||||||
|
override fun getSpinnerOptions(recordList: List<AppRecordWithSize>): List<SpinnerOption> {
|
||||||
|
val hasDisabled = recordList.any(isDisabled)
|
||||||
|
val hasInstant = recordList.any(isInstant)
|
||||||
|
if (!hasDisabled && !hasInstant) return emptyList()
|
||||||
|
val options = mutableListOf(SpinnerItem.All, SpinnerItem.Enabled)
|
||||||
|
if (hasDisabled) options += SpinnerItem.Disabled
|
||||||
|
if (hasInstant) options += SpinnerItem.Instant
|
||||||
|
return options.map {
|
||||||
|
SpinnerOption(
|
||||||
|
id = it.ordinal,
|
||||||
|
text = context.getString(it.stringResId),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
|
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
|
||||||
appListFlow.mapItem(::AppRecordWithSize)
|
appListFlow.mapItem(::AppRecordWithSize)
|
||||||
|
|
||||||
|
override fun filter(
|
||||||
|
userIdFlow: Flow<Int>,
|
||||||
|
option: Int,
|
||||||
|
recordListFlow: Flow<List<AppRecordWithSize>>,
|
||||||
|
): Flow<List<AppRecordWithSize>> = recordListFlow.filterItem(
|
||||||
|
when (SpinnerItem.values().getOrNull(option)) {
|
||||||
|
SpinnerItem.Enabled -> ({ it.app.enabled && !it.app.isInstantApp })
|
||||||
|
SpinnerItem.Disabled -> isDisabled
|
||||||
|
SpinnerItem.Instant -> isInstant
|
||||||
|
else -> ({ true })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private val isDisabled: (AppRecordWithSize) -> Boolean =
|
||||||
|
{ !it.app.enabled && !it.app.isInstantApp }
|
||||||
|
|
||||||
|
private val isInstant: (AppRecordWithSize) -> Boolean = { it.app.isInstantApp }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun getSummary(option: Int, record: AppRecordWithSize) = record.app.getSummary()
|
override fun getSummary(option: Int, record: AppRecordWithSize): State<String> {
|
||||||
|
val storageSummary = record.app.getStorageSummary()
|
||||||
|
return remember {
|
||||||
|
derivedStateOf {
|
||||||
|
storageSummary.value +
|
||||||
|
when (isDisabled(record)) {
|
||||||
|
true -> System.lineSeparator() + context.getString(R.string.disabled)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun AppListItemModel<AppRecordWithSize>.AppItem() {
|
override fun AppListItemModel<AppRecordWithSize>.AppItem() {
|
||||||
AppListItem(onClick = AppInfoSettingsProvider.navigator(app = record.app))
|
AppListItem(onClick = AppInfoSettingsProvider.navigator(app = record.app))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum class SpinnerItem(val stringResId: Int) {
|
||||||
|
All(R.string.filter_all_apps),
|
||||||
|
Enabled(R.string.filter_enabled_apps),
|
||||||
|
Disabled(R.string.filter_apps_disabled),
|
||||||
|
Instant(R.string.filter_instant_apps);
|
||||||
|
}
|
||||||
|
@@ -26,6 +26,7 @@ import androidx.compose.runtime.State
|
|||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
import com.android.settings.spa.development.UsageStatsListModel.SpinnerItem.Companion.toSpinnerItem
|
import com.android.settings.spa.development.UsageStatsListModel.SpinnerItem.Companion.toSpinnerItem
|
||||||
import com.android.settingslib.spa.framework.compose.stateOf
|
import com.android.settingslib.spa.framework.compose.stateOf
|
||||||
|
import com.android.settingslib.spa.widget.ui.SpinnerOption
|
||||||
import com.android.settingslib.spaprivileged.model.app.AppEntry
|
import com.android.settingslib.spaprivileged.model.app.AppEntry
|
||||||
import com.android.settingslib.spaprivileged.model.app.AppListModel
|
import com.android.settingslib.spaprivileged.model.app.AppListModel
|
||||||
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
||||||
@@ -53,9 +54,13 @@ class UsageStatsListModel(private val context: Context) : AppListModel<UsageStat
|
|||||||
appList.map { app -> UsageStatsAppRecord(app, usageStatsMap[app.packageName]) }
|
appList.map { app -> UsageStatsAppRecord(app, usageStatsMap[app.packageName]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSpinnerOptions() = SpinnerItem.values().map {
|
override fun getSpinnerOptions(recordList: List<UsageStatsAppRecord>): List<SpinnerOption> =
|
||||||
context.getString(it.stringResId)
|
SpinnerItem.values().map {
|
||||||
}
|
SpinnerOption(
|
||||||
|
id = it.ordinal,
|
||||||
|
text = context.getString(it.stringResId),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun filter(
|
override fun filter(
|
||||||
userIdFlow: Flow<Int>,
|
userIdFlow: Flow<Int>,
|
||||||
@@ -75,7 +80,8 @@ class UsageStatsListModel(private val context: Context) : AppListModel<UsageStat
|
|||||||
override fun getSummary(option: Int, record: UsageStatsAppRecord): State<String>? {
|
override fun getSummary(option: Int, record: UsageStatsAppRecord): State<String>? {
|
||||||
val usageStats = record.usageStats ?: return null
|
val usageStats = record.usageStats ?: return null
|
||||||
val lastTimeUsed = DateUtils.formatSameDayTime(
|
val lastTimeUsed = DateUtils.formatSameDayTime(
|
||||||
usageStats.lastTimeUsed, now, DateFormat.MEDIUM, DateFormat.MEDIUM)
|
usageStats.lastTimeUsed, now, DateFormat.MEDIUM, DateFormat.MEDIUM
|
||||||
|
)
|
||||||
val lastTimeUsedLine = "${context.getString(R.string.last_time_used_label)}: $lastTimeUsed"
|
val lastTimeUsedLine = "${context.getString(R.string.last_time_used_label)}: $lastTimeUsed"
|
||||||
val usageTime = DateUtils.formatElapsedTime(usageStats.totalTimeInForeground / 1000)
|
val usageTime = DateUtils.formatElapsedTime(usageStats.totalTimeInForeground / 1000)
|
||||||
val usageTimeLine = "${context.getString(R.string.usage_time_label)}: $usageTime"
|
val usageTimeLine = "${context.getString(R.string.usage_time_label)}: $usageTime"
|
||||||
|
@@ -30,6 +30,7 @@ import com.android.settings.spa.notification.SpinnerItem.Companion.toSpinnerItem
|
|||||||
import com.android.settingslib.spa.framework.compose.stateOf
|
import com.android.settingslib.spa.framework.compose.stateOf
|
||||||
import com.android.settingslib.spa.framework.util.asyncFilter
|
import com.android.settingslib.spa.framework.util.asyncFilter
|
||||||
import com.android.settingslib.spa.framework.util.asyncForEach
|
import com.android.settingslib.spa.framework.util.asyncForEach
|
||||||
|
import com.android.settingslib.spa.widget.ui.SpinnerOption
|
||||||
import com.android.settingslib.spaprivileged.model.app.AppEntry
|
import com.android.settingslib.spaprivileged.model.app.AppEntry
|
||||||
import com.android.settingslib.spaprivileged.model.app.AppListModel
|
import com.android.settingslib.spaprivileged.model.app.AppListModel
|
||||||
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
||||||
@@ -78,8 +79,9 @@ class AppNotificationsListModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun onFirstLoaded(recordList: List<AppNotificationsRecord>) {
|
override suspend fun onFirstLoaded(recordList: List<AppNotificationsRecord>): Boolean {
|
||||||
recordList.asyncForEach { it.controller.getEnabled() }
|
recordList.asyncForEach { it.controller.getEnabled() }
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getComparator(option: Int) = when (option.toSpinnerItem()) {
|
override fun getComparator(option: Int) = when (option.toSpinnerItem()) {
|
||||||
@@ -97,9 +99,13 @@ class AppNotificationsListModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSpinnerOptions() = SpinnerItem.values().map {
|
override fun getSpinnerOptions(recordList: List<AppNotificationsRecord>): List<SpinnerOption> =
|
||||||
context.getString(it.stringResId)
|
SpinnerItem.values().map {
|
||||||
}
|
SpinnerOption(
|
||||||
|
id = it.ordinal,
|
||||||
|
text = context.getString(it.stringResId),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun formatLastSent(lastSent: Long) =
|
private fun formatLastSent(lastSent: Long) =
|
||||||
StringUtil.formatRelativeTime(
|
StringUtil.formatRelativeTime(
|
||||||
|
@@ -121,7 +121,7 @@ class AllAppListTest {
|
|||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun allAppListModel_transform() = runTest {
|
fun allAppListModel_transform() = runTest {
|
||||||
val listModel = AllAppListModel { stateOf(SUMMARY) }
|
val listModel = AllAppListModel(context) { stateOf(SUMMARY) }
|
||||||
|
|
||||||
val recordListFlow = listModel.transform(flowOf(USER_ID), flowOf(listOf(APP)))
|
val recordListFlow = listModel.transform(flowOf(USER_ID), flowOf(listOf(APP)))
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ class AllAppListTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun allAppListModel_getSummary() {
|
fun allAppListModel_getSummary() {
|
||||||
val listModel = AllAppListModel { stateOf(SUMMARY) }
|
val listModel = AllAppListModel(context) { stateOf(SUMMARY) }
|
||||||
|
|
||||||
lateinit var summaryState: State<String>
|
lateinit var summaryState: State<String>
|
||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
@@ -142,6 +142,23 @@ class AllAppListTest {
|
|||||||
assertThat(summaryState.value).isEqualTo(SUMMARY)
|
assertThat(summaryState.value).isEqualTo(SUMMARY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun allAppListModel_getSummaryWhenDisabled() {
|
||||||
|
val listModel = AllAppListModel(context) { stateOf(SUMMARY) }
|
||||||
|
val disabledApp = ApplicationInfo().apply {
|
||||||
|
packageName = PACKAGE_NAME
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
lateinit var summaryState: State<String>
|
||||||
|
composeTestRule.setContent {
|
||||||
|
summaryState =
|
||||||
|
listModel.getSummary(option = 0, record = AppRecordWithSize(app = disabledApp))
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(summaryState.value).isEqualTo("$SUMMARY${System.lineSeparator()}Disabled")
|
||||||
|
}
|
||||||
|
|
||||||
private fun getAppListInput(): AppListInput<AppRecordWithSize> {
|
private fun getAppListInput(): AppListInput<AppRecordWithSize> {
|
||||||
lateinit var input: AppListInput<AppRecordWithSize>
|
lateinit var input: AppListInput<AppRecordWithSize>
|
||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
@@ -157,7 +174,7 @@ class AllAppListTest {
|
|||||||
private fun setItemContent() {
|
private fun setItemContent() {
|
||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
fakeNavControllerWrapper.Wrapper {
|
fakeNavControllerWrapper.Wrapper {
|
||||||
with(AllAppListModel()) {
|
with(AllAppListModel(context)) {
|
||||||
AppListItemModel(
|
AppListItemModel(
|
||||||
record = AppRecordWithSize(app = APP),
|
record = AppRecordWithSize(app = APP),
|
||||||
label = LABEL,
|
label = LABEL,
|
||||||
|
Reference in New Issue
Block a user