Merge "Reimplement device details UI without ComposePreference" into main
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.settings.bluetooth.ui.layout
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Represent the layout of device settings. */
|
||||
data class DeviceSettingLayout(val rows: List<DeviceSettingLayoutRow>)
|
||||
|
||||
/** Represent a row in the layout. */
|
||||
data class DeviceSettingLayoutRow(val columns: Flow<List<DeviceSettingLayoutColumn>>)
|
||||
|
||||
/** Represent a column in a row. */
|
||||
data class DeviceSettingLayoutColumn(val settingId: Int, val highlighted: Boolean)
|
||||
@@ -21,35 +21,26 @@ import android.app.settings.SettingsEnums
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.preference.TwoStatePreference
|
||||
import com.android.settings.R
|
||||
import com.android.settings.bluetooth.BlockingPrefWithSliceController
|
||||
import com.android.settings.bluetooth.BluetoothDetailsProfilesController
|
||||
import com.android.settings.bluetooth.ui.composable.Icon
|
||||
import com.android.settings.bluetooth.ui.composable.MultiTogglePreference
|
||||
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
|
||||
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
||||
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
||||
import com.android.settings.bluetooth.ui.view.DeviceDetailsMoreSettingsFragment.Companion.KEY_DEVICE_ADDRESS
|
||||
@@ -58,6 +49,7 @@ import com.android.settings.core.SubSettingLauncher
|
||||
import com.android.settings.dashboard.DashboardFragment
|
||||
import com.android.settings.overlay.FeatureFactory
|
||||
import com.android.settings.spa.preference.ComposePreference
|
||||
import com.android.settingslib.PrimarySwitchPreference
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
||||
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
|
||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingActionModel
|
||||
@@ -67,24 +59,15 @@ import com.android.settingslib.core.AbstractPreferenceController
|
||||
import com.android.settingslib.core.lifecycle.LifecycleObserver
|
||||
import com.android.settingslib.core.lifecycle.events.OnPause
|
||||
import com.android.settingslib.core.lifecycle.events.OnStop
|
||||
import com.android.settingslib.spa.framework.theme.SettingsDimension
|
||||
import com.android.settingslib.spa.widget.preference.Preference as SpaPreference
|
||||
import com.android.settingslib.spa.widget.preference.PreferenceModel
|
||||
import com.android.settingslib.spa.widget.preference.SwitchPreference
|
||||
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
|
||||
import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
|
||||
import com.android.settingslib.spa.widget.ui.Footer
|
||||
import com.android.settingslib.widget.FooterPreference
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@@ -105,7 +88,7 @@ interface DeviceDetailsFragmentFormatter {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class DeviceDetailsFragmentFormatterImpl(
|
||||
private val context: Context,
|
||||
private val fragment: DashboardFragment,
|
||||
private val dashboardFragment: DashboardFragment,
|
||||
controllers: List<AbstractPreferenceController>,
|
||||
private val bluetoothAdapter: BluetoothAdapter,
|
||||
private val cachedDevice: CachedBluetoothDevice,
|
||||
@@ -120,32 +103,30 @@ class DeviceDetailsFragmentFormatterImpl(
|
||||
|
||||
private val viewModel: BluetoothDeviceDetailsViewModel =
|
||||
ViewModelProvider(
|
||||
fragment,
|
||||
BluetoothDeviceDetailsViewModel.Factory(
|
||||
fragment.requireActivity().application,
|
||||
bluetoothAdapter,
|
||||
cachedDevice,
|
||||
backgroundCoroutineContext,
|
||||
),
|
||||
)
|
||||
dashboardFragment,
|
||||
BluetoothDeviceDetailsViewModel.Factory(
|
||||
dashboardFragment.requireActivity().application,
|
||||
bluetoothAdapter,
|
||||
cachedDevice,
|
||||
backgroundCoroutineContext,
|
||||
),
|
||||
)
|
||||
.get(BluetoothDeviceDetailsViewModel::class.java)
|
||||
|
||||
/** Updates bluetooth device details fragment layout. */
|
||||
override fun updateLayout(fragmentType: FragmentTypeModel) {
|
||||
fragment.setLoading(true, false)
|
||||
dashboardFragment.setLoading(true, false)
|
||||
isLoading = true
|
||||
fragment.lifecycleScope.launch { updateLayoutInternal(fragmentType) }
|
||||
dashboardFragment.lifecycleScope.launch { updateLayoutInternal(fragmentType) }
|
||||
}
|
||||
|
||||
private suspend fun updateLayoutInternal(fragmentType: FragmentTypeModel) {
|
||||
val items = viewModel.getItems(fragmentType) ?: run {
|
||||
fragment.setLoading(false, false)
|
||||
return
|
||||
}
|
||||
val layout = viewModel.getLayout(fragmentType) ?: run {
|
||||
fragment.setLoading(false, false)
|
||||
return
|
||||
}
|
||||
val items =
|
||||
viewModel.getItems(fragmentType)
|
||||
?: run {
|
||||
dashboardFragment.setLoading(false, false)
|
||||
return
|
||||
}
|
||||
|
||||
val prefKeyToSettingId =
|
||||
items
|
||||
@@ -153,14 +134,14 @@ class DeviceDetailsFragmentFormatterImpl(
|
||||
.associateBy({ it.preferenceKey }, { it.settingId })
|
||||
|
||||
val settingIdToXmlPreferences: MutableMap<Int, Preference> = HashMap()
|
||||
for (i in 0 until fragment.preferenceScreen.preferenceCount) {
|
||||
val pref = fragment.preferenceScreen.getPreference(i)
|
||||
for (i in 0 until dashboardFragment.preferenceScreen.preferenceCount) {
|
||||
val pref = dashboardFragment.preferenceScreen.getPreference(i)
|
||||
prefKeyToSettingId[pref.key]?.let { id -> settingIdToXmlPreferences[id] = pref }
|
||||
if (pref.key !in prefKeyToSettingId) {
|
||||
getController(pref.key)?.let { disableController(it) }
|
||||
}
|
||||
}
|
||||
fragment.preferenceScreen.removeAll()
|
||||
dashboardFragment.preferenceScreen.removeAll()
|
||||
for (job in prefVisibilityJobs) {
|
||||
job.cancel()
|
||||
}
|
||||
@@ -170,53 +151,83 @@ class DeviceDetailsFragmentFormatterImpl(
|
||||
val settingId = settingItem.settingId
|
||||
if (settingIdToXmlPreferences.containsKey(settingId)) {
|
||||
val pref = settingIdToXmlPreferences[settingId]!!.apply { order = row }
|
||||
fragment.preferenceScreen.addPreference(pref)
|
||||
dashboardFragment.preferenceScreen.addPreference(pref)
|
||||
} else {
|
||||
val prefKey = getPreferenceKey(settingId)
|
||||
|
||||
prefVisibilityJobs.add(
|
||||
getDevicesSettingForRow(layout, row)
|
||||
.onEach { logItemShown(prefKey, it.isNotEmpty()) }
|
||||
.launchIn(fragment.lifecycleScope)
|
||||
viewModel
|
||||
.getDeviceSetting(cachedDevice, settingId)
|
||||
.onEach { logItemShown(prefKey, it != null) }
|
||||
.launchIn(dashboardFragment.lifecycleScope)
|
||||
)
|
||||
val pref =
|
||||
ComposePreference(context)
|
||||
.apply {
|
||||
key = prefKey
|
||||
order = row
|
||||
if (settingId == DeviceSettingId.DEVICE_SETTING_ID_ANC) {
|
||||
// TODO(b/399316980): replace it with SegmentedButtonPreference once it's ready.
|
||||
val pref =
|
||||
ComposePreference(context)
|
||||
.apply {
|
||||
key = prefKey
|
||||
order = row
|
||||
}
|
||||
.also { pref ->
|
||||
pref.setContent {
|
||||
buildComposePreference(cachedDevice, settingId, prefKey)
|
||||
}
|
||||
}
|
||||
dashboardFragment.preferenceScreen.addPreference(pref)
|
||||
} else {
|
||||
viewModel
|
||||
.getDeviceSetting(cachedDevice, settingId)
|
||||
.onEach {
|
||||
val existedPref =
|
||||
dashboardFragment.preferenceScreen.findPreference<Preference>(
|
||||
prefKey
|
||||
)
|
||||
val item =
|
||||
it
|
||||
?: run {
|
||||
existedPref?.let {
|
||||
dashboardFragment.preferenceScreen.removePreference(
|
||||
existedPref
|
||||
)
|
||||
}
|
||||
return@onEach
|
||||
}
|
||||
buildPreference(existedPref, item, prefKey, settingItem.highlighted)
|
||||
?.apply {
|
||||
key = prefKey
|
||||
order = row
|
||||
}
|
||||
?.also { dashboardFragment.preferenceScreen.addPreference(it) }
|
||||
}
|
||||
.also { pref -> pref.setContent { buildPreference(layout, row, prefKey) } }
|
||||
fragment.preferenceScreen.addPreference(pref)
|
||||
.launchIn(dashboardFragment.lifecycleScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(b/343317785): figure out how to remove the foot preference.
|
||||
fragment.preferenceScreen.addPreference(ComposePreference(context).apply {
|
||||
order = 10000
|
||||
isEnabled = false
|
||||
isSelectable = false
|
||||
setContent { Spacer(modifier = Modifier.height(1.dp)) }
|
||||
})
|
||||
|
||||
for (row in items.indices) {
|
||||
val settingItem = items[row]
|
||||
val settingId = settingItem.settingId
|
||||
if (settingIdToXmlPreferences.containsKey(settingId)) {
|
||||
val pref = fragment.preferenceScreen.getPreference(row)
|
||||
settingIdToXmlPreferences[settingId]?.let { pref ->
|
||||
if (settingId == DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES) {
|
||||
(getController(pref.key) as? BluetoothDetailsProfilesController)?.run {
|
||||
if (settingItem is DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem) {
|
||||
if (
|
||||
settingItem
|
||||
is DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem
|
||||
) {
|
||||
setInvisibleProfiles(settingItem.invisibleProfiles)
|
||||
setHasExtraSpace(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
getController(pref.key)?.displayPreference(fragment.preferenceScreen)
|
||||
getController(pref.key)?.displayPreference(dashboardFragment.preferenceScreen)
|
||||
logItemShown(pref.key, pref.isVisible)
|
||||
}
|
||||
}
|
||||
|
||||
fragment.lifecycleScope.launch {
|
||||
dashboardFragment.lifecycleScope.launch {
|
||||
if (isLoading) {
|
||||
fragment.setLoading(false, false)
|
||||
dashboardFragment.setLoading(false, false)
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
@@ -236,87 +247,170 @@ class DeviceDetailsFragmentFormatterImpl(
|
||||
} ?: emit(null)
|
||||
}
|
||||
|
||||
private fun getDevicesSettingForRow(
|
||||
layout: DeviceSettingLayout,
|
||||
row: Int,
|
||||
): Flow<List<DeviceSettingPreferenceModel>> =
|
||||
layout.rows[row].columns.flatMapLatest { columns ->
|
||||
if (columns.isEmpty()) {
|
||||
flowOf(emptyList())
|
||||
} else {
|
||||
combine(
|
||||
columns.map { column ->
|
||||
viewModel.getDeviceSetting(cachedDevice, column.settingId)
|
||||
}
|
||||
) {
|
||||
it.toList().filterNotNull()
|
||||
private fun buildPreference(
|
||||
existedPref: Preference?,
|
||||
model: DeviceSettingPreferenceModel,
|
||||
prefKey: String,
|
||||
highlighted: Boolean,
|
||||
): Preference? =
|
||||
when (model) {
|
||||
is DeviceSettingPreferenceModel.PlainPreference -> {
|
||||
val pref =
|
||||
existedPref
|
||||
?: run {
|
||||
if (highlighted) SpotlightPreference(context) else Preference(context)
|
||||
}
|
||||
pref.apply {
|
||||
title = model.title
|
||||
summary = model.summary
|
||||
icon = getDrawable(model.icon)
|
||||
onPreferenceClickListener =
|
||||
object : Preference.OnPreferenceClickListener {
|
||||
override fun onPreferenceClick(p: Preference): Boolean {
|
||||
logItemClick(prefKey, EVENT_CLICK_PRIMARY)
|
||||
model.action?.let { triggerAction(it) }
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is DeviceSettingPreferenceModel.SwitchPreference ->
|
||||
if (model.action == null) {
|
||||
val pref =
|
||||
existedPref as? SwitchPreferenceCompat ?: SwitchPreferenceCompat(context)
|
||||
pref.apply {
|
||||
title = model.title
|
||||
summary = model.summary
|
||||
icon = getDrawable(model.icon)
|
||||
isChecked = model.checked
|
||||
isEnabled = !model.disabled
|
||||
onPreferenceChangeListener =
|
||||
object : Preference.OnPreferenceChangeListener {
|
||||
override fun onPreferenceChange(
|
||||
p: Preference,
|
||||
value: Any?,
|
||||
): Boolean {
|
||||
(p as? TwoStatePreference)?.let { newState ->
|
||||
val newState = value as? Boolean ?: return false
|
||||
logItemClick(
|
||||
prefKey,
|
||||
if (newState) EVENT_SWITCH_ON else EVENT_SWITCH_OFF,
|
||||
)
|
||||
isEnabled = false
|
||||
model.onCheckedChange.invoke(newState)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val pref =
|
||||
existedPref as? PrimarySwitchPreference ?: PrimarySwitchPreference(context)
|
||||
pref.apply {
|
||||
title = model.title
|
||||
summary = model.summary
|
||||
icon = getDrawable(model.icon)
|
||||
isChecked = model.checked
|
||||
isEnabled = !model.disabled
|
||||
isSwitchEnabled = !model.disabled
|
||||
onPreferenceClickListener =
|
||||
object : Preference.OnPreferenceClickListener {
|
||||
override fun onPreferenceClick(p: Preference): Boolean {
|
||||
logItemClick(prefKey, EVENT_CLICK_PRIMARY)
|
||||
triggerAction(model.action)
|
||||
return true
|
||||
}
|
||||
}
|
||||
onPreferenceChangeListener =
|
||||
object : Preference.OnPreferenceChangeListener {
|
||||
override fun onPreferenceChange(
|
||||
p: Preference,
|
||||
value: Any?,
|
||||
): Boolean {
|
||||
val newState = value as? Boolean ?: return false
|
||||
logItemClick(
|
||||
prefKey,
|
||||
if (newState) EVENT_SWITCH_ON else EVENT_SWITCH_OFF,
|
||||
)
|
||||
isSwitchEnabled = false
|
||||
model.onCheckedChange.invoke(newState)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is DeviceSettingPreferenceModel.MultiTogglePreference -> {
|
||||
// TODO(b/399316980): implemented it by SegmentedButtonPreference
|
||||
null
|
||||
}
|
||||
is DeviceSettingPreferenceModel.FooterPreference -> {
|
||||
val pref = existedPref as? FooterPreference ?: FooterPreference(context)
|
||||
pref.apply { title = model.footerText }
|
||||
}
|
||||
is DeviceSettingPreferenceModel.MoreSettingsPreference -> {
|
||||
val pref = existedPref ?: Preference(context)
|
||||
pref.apply {
|
||||
title =
|
||||
context.getString(R.string.bluetooth_device_more_settings_preference_title)
|
||||
summary =
|
||||
context.getString(
|
||||
R.string.bluetooth_device_more_settings_preference_summary
|
||||
)
|
||||
icon = context.getDrawable(R.drawable.ic_chevron_right_24dp)
|
||||
onPreferenceClickListener =
|
||||
object : Preference.OnPreferenceClickListener {
|
||||
override fun onPreferenceClick(p: Preference): Boolean {
|
||||
logItemClick(prefKey, EVENT_CLICK_PRIMARY)
|
||||
SubSettingLauncher(context)
|
||||
.setDestination(
|
||||
DeviceDetailsMoreSettingsFragment::class.java.name
|
||||
)
|
||||
.setSourceMetricsCategory(
|
||||
dashboardFragment.getMetricsCategory()
|
||||
)
|
||||
.setArguments(
|
||||
Bundle().apply {
|
||||
putString(KEY_DEVICE_ADDRESS, cachedDevice.address)
|
||||
}
|
||||
)
|
||||
.launch()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is DeviceSettingPreferenceModel.HelpPreference -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildPreference(layout: DeviceSettingLayout, row: Int, prefKey: String) {
|
||||
val contents by
|
||||
remember(row) { getDevicesSettingForRow(layout, row) }
|
||||
.collectAsStateWithLifecycle(initialValue = listOf())
|
||||
|
||||
val highlighted by
|
||||
remember(row) {
|
||||
layout.rows[row].columns.map { columns -> columns.any { it.highlighted } }
|
||||
private fun getDrawable(deviceSettingIcon: DeviceSettingIcon?): Drawable? =
|
||||
when (deviceSettingIcon) {
|
||||
is DeviceSettingIcon.BitmapIcon ->
|
||||
deviceSettingIcon.bitmap.toDrawable(context.resources)
|
||||
is DeviceSettingIcon.ResourceIcon -> context.getDrawable(deviceSettingIcon.resId)
|
||||
null -> null
|
||||
}
|
||||
.collectAsStateWithLifecycle(initialValue = false)
|
||||
|
||||
@Composable
|
||||
private fun buildComposePreference(
|
||||
cachedDevice: CachedBluetoothDevice,
|
||||
settingId: Int,
|
||||
prefKey: String,
|
||||
) {
|
||||
val contents by
|
||||
remember(settingId) { viewModel.getDeviceSetting(cachedDevice, settingId) }
|
||||
.collectAsStateWithLifecycle(initialValue = null)
|
||||
|
||||
val settings = contents
|
||||
AnimatedVisibility(visible = settings.isNotEmpty(), enter = fadeIn(), exit = fadeOut()) {
|
||||
Box {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.matchParentSize()
|
||||
.padding(16.dp, 0.dp, 8.dp, 0.dp)
|
||||
.background(
|
||||
color =
|
||||
if (highlighted) {
|
||||
MaterialTheme.colorScheme.primaryContainer
|
||||
} else {
|
||||
Color.Transparent
|
||||
},
|
||||
shape = RoundedCornerShape(28.dp),
|
||||
)
|
||||
) {}
|
||||
buildPreferences(settings, prefKey)
|
||||
AnimatedVisibility(visible = settings != null, enter = fadeIn(), exit = fadeOut()) {
|
||||
(settings as? DeviceSettingPreferenceModel.MultiTogglePreference)?.let {
|
||||
buildMultiTogglePreference(it, prefKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun buildPreferences(settings: List<DeviceSettingPreferenceModel?>, prefKey: String) {
|
||||
when (settings.size) {
|
||||
0 -> {}
|
||||
1 -> {
|
||||
when (val setting = settings[0]) {
|
||||
is DeviceSettingPreferenceModel.PlainPreference -> {
|
||||
buildPlainPreference(setting, prefKey)
|
||||
}
|
||||
is DeviceSettingPreferenceModel.SwitchPreference -> {
|
||||
buildSwitchPreference(setting, prefKey)
|
||||
}
|
||||
is DeviceSettingPreferenceModel.MultiTogglePreference -> {
|
||||
buildMultiTogglePreference(setting, prefKey)
|
||||
}
|
||||
is DeviceSettingPreferenceModel.FooterPreference -> {
|
||||
buildFooterPreference(setting)
|
||||
}
|
||||
is DeviceSettingPreferenceModel.MoreSettingsPreference -> {
|
||||
buildMoreSettingsPreference(prefKey)
|
||||
}
|
||||
is DeviceSettingPreferenceModel.HelpPreference -> {}
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildMultiTogglePreference(
|
||||
pref: DeviceSettingPreferenceModel.MultiTogglePreference,
|
||||
@@ -332,107 +426,6 @@ class DeviceDetailsFragmentFormatterImpl(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildSwitchPreference(
|
||||
model: DeviceSettingPreferenceModel.SwitchPreference,
|
||||
prefKey: String,
|
||||
) {
|
||||
val switchPrefModel =
|
||||
object : SwitchPreferenceModel {
|
||||
override val title = model.title
|
||||
override val summary = { model.summary ?: "" }
|
||||
override val checked = { model.checked }
|
||||
override val onCheckedChange = { newState: Boolean ->
|
||||
logItemClick(prefKey, if (newState) EVENT_SWITCH_ON else EVENT_SWITCH_OFF)
|
||||
model.onCheckedChange(newState)
|
||||
}
|
||||
override val changeable = { !model.disabled }
|
||||
override val icon: (@Composable () -> Unit)?
|
||||
get() {
|
||||
if (model.icon == null) {
|
||||
return null
|
||||
}
|
||||
return { deviceSettingIcon(model.icon) }
|
||||
}
|
||||
}
|
||||
if (model.action != null) {
|
||||
TwoTargetSwitchPreference(
|
||||
switchPrefModel,
|
||||
primaryOnClick = {
|
||||
logItemClick(prefKey, EVENT_CLICK_PRIMARY)
|
||||
triggerAction(model.action)
|
||||
},
|
||||
primaryEnabled = { !model.disabled },
|
||||
)
|
||||
} else {
|
||||
SwitchPreference(switchPrefModel)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildPlainPreference(
|
||||
model: DeviceSettingPreferenceModel.PlainPreference,
|
||||
prefKey: String,
|
||||
) {
|
||||
SpaPreference(
|
||||
object : PreferenceModel {
|
||||
override val title = model.title
|
||||
override val summary = { model.summary ?: "" }
|
||||
override val onClick = {
|
||||
logItemClick(prefKey, EVENT_CLICK_PRIMARY)
|
||||
model.action?.let { triggerAction(it) }
|
||||
Unit
|
||||
}
|
||||
override val icon: (@Composable () -> Unit)?
|
||||
get() {
|
||||
if (model.icon == null) {
|
||||
return null
|
||||
}
|
||||
return { deviceSettingIcon(model.icon) }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun buildMoreSettingsPreference(prefKey: String) {
|
||||
SpaPreference(
|
||||
object : PreferenceModel {
|
||||
override val title =
|
||||
stringResource(R.string.bluetooth_device_more_settings_preference_title)
|
||||
override val summary = {
|
||||
context.getString(R.string.bluetooth_device_more_settings_preference_summary)
|
||||
}
|
||||
override val onClick = {
|
||||
logItemClick(prefKey, EVENT_CLICK_PRIMARY)
|
||||
SubSettingLauncher(context)
|
||||
.setDestination(DeviceDetailsMoreSettingsFragment::class.java.name)
|
||||
.setSourceMetricsCategory(fragment.getMetricsCategory())
|
||||
.setArguments(
|
||||
Bundle().apply { putString(KEY_DEVICE_ADDRESS, cachedDevice.address) }
|
||||
)
|
||||
.launch()
|
||||
}
|
||||
override val icon =
|
||||
@Composable {
|
||||
deviceSettingIcon(
|
||||
DeviceSettingIcon.ResourceIcon(R.drawable.ic_chevron_right_24dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun buildFooterPreference(model: DeviceSettingPreferenceModel.FooterPreference) {
|
||||
Footer(footerText = model.footerText)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun deviceSettingIcon(icon: DeviceSettingIcon?) {
|
||||
icon?.let { Icon(it, modifier = Modifier.size(SettingsDimension.itemIconSize)) }
|
||||
}
|
||||
|
||||
private fun logItemClick(preferenceKey: String, value: Int = 0) {
|
||||
logAction(preferenceKey, SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_CLICKED, value)
|
||||
}
|
||||
@@ -452,7 +445,7 @@ class DeviceDetailsFragmentFormatterImpl(
|
||||
if (it) EVENT_VISIBLE else EVENT_INVISIBLE,
|
||||
)
|
||||
}
|
||||
.launchIn(fragment.lifecycleScope)
|
||||
.launchIn(dashboardFragment.lifecycleScope)
|
||||
}
|
||||
}
|
||||
.value = visible
|
||||
@@ -485,7 +478,7 @@ class DeviceDetailsFragmentFormatterImpl(
|
||||
|
||||
private fun disableController(controller: AbstractPreferenceController) {
|
||||
if (controller is LifecycleObserver) {
|
||||
fragment.settingsLifecycle.removeObserver(controller as LifecycleObserver)
|
||||
dashboardFragment.settingsLifecycle.removeObserver(controller as LifecycleObserver)
|
||||
}
|
||||
|
||||
if (controller is BlockingPrefWithSliceController) {
|
||||
@@ -504,6 +497,19 @@ class DeviceDetailsFragmentFormatterImpl(
|
||||
|
||||
private fun getPreferenceKey(settingId: Int) = "DEVICE_SETTING_${settingId}"
|
||||
|
||||
private class SpotlightPreference(context: Context) : Preference(context) {
|
||||
|
||||
init {
|
||||
layoutResource = R.layout.bluetooth_device_spotlight_preference
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||
super.onBindViewHolder(holder)
|
||||
holder.isDividerAllowedBelow = false
|
||||
holder.isDividerAllowedAbove = false
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TAG = "DeviceDetailsFormatter"
|
||||
const val EVENT_SWITCH_OFF = 0
|
||||
|
||||
@@ -23,9 +23,6 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.settings.R
|
||||
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
|
||||
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayoutColumn
|
||||
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayoutRow
|
||||
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
||||
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
@@ -39,11 +36,8 @@ import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class BluetoothDeviceDetailsViewModel(
|
||||
private val application: Application,
|
||||
@@ -141,43 +135,6 @@ class BluetoothDeviceDetailsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getLayout(fragment: FragmentTypeModel): DeviceSettingLayout? {
|
||||
val configItems = getItems(fragment) ?: return null
|
||||
val idToDeviceSetting =
|
||||
configItems
|
||||
.filterIsInstance<DeviceSettingConfigItemModel.AppProvidedItem>()
|
||||
.associateBy({ it.settingId }, { getDeviceSetting(cachedDevice, it.settingId) })
|
||||
|
||||
val configDeviceSetting =
|
||||
configItems.map { idToDeviceSetting[it.settingId] ?: flowOf(null) }
|
||||
val positionToSettingIds =
|
||||
combine(configDeviceSetting) { settings ->
|
||||
val positionMapping = mutableMapOf<Int, List<DeviceSettingLayoutColumn>>()
|
||||
for (i in settings.indices) {
|
||||
val configItem = configItems[i]
|
||||
val setting = settings[i]
|
||||
val isXmlPreference = configItem is DeviceSettingConfigItemModel.BuiltinItem
|
||||
if (!isXmlPreference && setting == null) {
|
||||
continue
|
||||
}
|
||||
positionMapping[i] =
|
||||
listOf(
|
||||
DeviceSettingLayoutColumn(
|
||||
configItem.settingId,
|
||||
configItem.highlighted,
|
||||
)
|
||||
)
|
||||
}
|
||||
positionMapping
|
||||
}
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
|
||||
return DeviceSettingLayout(
|
||||
configItems.indices.map { idx ->
|
||||
DeviceSettingLayoutRow(positionToSettingIds.map { it[idx] ?: emptyList() })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val application: Application,
|
||||
private val bluetoothAdapter: BluetoothAdapter,
|
||||
|
||||
Reference in New Issue
Block a user