Reimplement device details UI without ComposePreference
Replace ComposePreference with androidx.preference.Preference. ANC toggle will still use Compose until SegmentedButtonPreference is ready. BUG: 402036473 Test: local tested Flag: com.android.settings.flags.enable_bluetooth_device_details_polish Change-Id: I5114af8f2d679d695b3c5ef4d7be2874245c435e
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2025 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="?android:colorControlHighlight">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid
|
||||||
|
android:color="@color/settingslib_materialColorSecondaryContainer" />
|
||||||
|
<corners
|
||||||
|
android:radius="28dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
73
res/layout/bluetooth_device_spotlight_preference.xml
Normal file
73
res/layout/bluetooth_device_spotlight_preference.xml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2025 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.
|
||||||
|
-->
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingEnd="?android:attr/scrollbarSize"
|
||||||
|
android:background="?android:attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:background="@drawable/device_details_spotlight_preference_background" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||||
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||||
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+android:id/icon"
|
||||||
|
android:layout_width="24dip"
|
||||||
|
android:layout_height="24dip"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:importantForAccessibility="no" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<TextView android:id="@+android:id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:fadingEdge="horizontal" />
|
||||||
|
|
||||||
|
<TextView android:id="@+android:id/summary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@android:id/title"
|
||||||
|
android:layout_alignStart="@android:id/title"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:maxLines="4" />
|
||||||
|
</RelativeLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
@@ -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.bluetooth.BluetoothAdapter
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
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.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.Preference
|
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.R
|
||||||
import com.android.settings.bluetooth.BlockingPrefWithSliceController
|
import com.android.settings.bluetooth.BlockingPrefWithSliceController
|
||||||
import com.android.settings.bluetooth.BluetoothDetailsProfilesController
|
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.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.DeviceSettingPreferenceModel
|
||||||
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
||||||
import com.android.settings.bluetooth.ui.view.DeviceDetailsMoreSettingsFragment.Companion.KEY_DEVICE_ADDRESS
|
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.dashboard.DashboardFragment
|
||||||
import com.android.settings.overlay.FeatureFactory
|
import com.android.settings.overlay.FeatureFactory
|
||||||
import com.android.settings.spa.preference.ComposePreference
|
import com.android.settings.spa.preference.ComposePreference
|
||||||
|
import com.android.settingslib.PrimarySwitchPreference
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
||||||
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
|
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
|
||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingActionModel
|
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.LifecycleObserver
|
||||||
import com.android.settingslib.core.lifecycle.events.OnPause
|
import com.android.settingslib.core.lifecycle.events.OnPause
|
||||||
import com.android.settingslib.core.lifecycle.events.OnStop
|
import com.android.settingslib.core.lifecycle.events.OnStop
|
||||||
import com.android.settingslib.spa.framework.theme.SettingsDimension
|
import com.android.settingslib.widget.FooterPreference
|
||||||
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 kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.emitAll
|
import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -105,7 +88,7 @@ interface DeviceDetailsFragmentFormatter {
|
|||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class DeviceDetailsFragmentFormatterImpl(
|
class DeviceDetailsFragmentFormatterImpl(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val fragment: DashboardFragment,
|
private val dashboardFragment: DashboardFragment,
|
||||||
controllers: List<AbstractPreferenceController>,
|
controllers: List<AbstractPreferenceController>,
|
||||||
private val bluetoothAdapter: BluetoothAdapter,
|
private val bluetoothAdapter: BluetoothAdapter,
|
||||||
private val cachedDevice: CachedBluetoothDevice,
|
private val cachedDevice: CachedBluetoothDevice,
|
||||||
@@ -120,32 +103,30 @@ class DeviceDetailsFragmentFormatterImpl(
|
|||||||
|
|
||||||
private val viewModel: BluetoothDeviceDetailsViewModel =
|
private val viewModel: BluetoothDeviceDetailsViewModel =
|
||||||
ViewModelProvider(
|
ViewModelProvider(
|
||||||
fragment,
|
dashboardFragment,
|
||||||
BluetoothDeviceDetailsViewModel.Factory(
|
BluetoothDeviceDetailsViewModel.Factory(
|
||||||
fragment.requireActivity().application,
|
dashboardFragment.requireActivity().application,
|
||||||
bluetoothAdapter,
|
bluetoothAdapter,
|
||||||
cachedDevice,
|
cachedDevice,
|
||||||
backgroundCoroutineContext,
|
backgroundCoroutineContext,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.get(BluetoothDeviceDetailsViewModel::class.java)
|
.get(BluetoothDeviceDetailsViewModel::class.java)
|
||||||
|
|
||||||
/** Updates bluetooth device details fragment layout. */
|
/** Updates bluetooth device details fragment layout. */
|
||||||
override fun updateLayout(fragmentType: FragmentTypeModel) {
|
override fun updateLayout(fragmentType: FragmentTypeModel) {
|
||||||
fragment.setLoading(true, false)
|
dashboardFragment.setLoading(true, false)
|
||||||
isLoading = true
|
isLoading = true
|
||||||
fragment.lifecycleScope.launch { updateLayoutInternal(fragmentType) }
|
dashboardFragment.lifecycleScope.launch { updateLayoutInternal(fragmentType) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateLayoutInternal(fragmentType: FragmentTypeModel) {
|
private suspend fun updateLayoutInternal(fragmentType: FragmentTypeModel) {
|
||||||
val items = viewModel.getItems(fragmentType) ?: run {
|
val items =
|
||||||
fragment.setLoading(false, false)
|
viewModel.getItems(fragmentType)
|
||||||
return
|
?: run {
|
||||||
}
|
dashboardFragment.setLoading(false, false)
|
||||||
val layout = viewModel.getLayout(fragmentType) ?: run {
|
return
|
||||||
fragment.setLoading(false, false)
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val prefKeyToSettingId =
|
val prefKeyToSettingId =
|
||||||
items
|
items
|
||||||
@@ -153,14 +134,14 @@ class DeviceDetailsFragmentFormatterImpl(
|
|||||||
.associateBy({ it.preferenceKey }, { it.settingId })
|
.associateBy({ it.preferenceKey }, { it.settingId })
|
||||||
|
|
||||||
val settingIdToXmlPreferences: MutableMap<Int, Preference> = HashMap()
|
val settingIdToXmlPreferences: MutableMap<Int, Preference> = HashMap()
|
||||||
for (i in 0 until fragment.preferenceScreen.preferenceCount) {
|
for (i in 0 until dashboardFragment.preferenceScreen.preferenceCount) {
|
||||||
val pref = fragment.preferenceScreen.getPreference(i)
|
val pref = dashboardFragment.preferenceScreen.getPreference(i)
|
||||||
prefKeyToSettingId[pref.key]?.let { id -> settingIdToXmlPreferences[id] = pref }
|
prefKeyToSettingId[pref.key]?.let { id -> settingIdToXmlPreferences[id] = pref }
|
||||||
if (pref.key !in prefKeyToSettingId) {
|
if (pref.key !in prefKeyToSettingId) {
|
||||||
getController(pref.key)?.let { disableController(it) }
|
getController(pref.key)?.let { disableController(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment.preferenceScreen.removeAll()
|
dashboardFragment.preferenceScreen.removeAll()
|
||||||
for (job in prefVisibilityJobs) {
|
for (job in prefVisibilityJobs) {
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
@@ -170,53 +151,83 @@ class DeviceDetailsFragmentFormatterImpl(
|
|||||||
val settingId = settingItem.settingId
|
val settingId = settingItem.settingId
|
||||||
if (settingIdToXmlPreferences.containsKey(settingId)) {
|
if (settingIdToXmlPreferences.containsKey(settingId)) {
|
||||||
val pref = settingIdToXmlPreferences[settingId]!!.apply { order = row }
|
val pref = settingIdToXmlPreferences[settingId]!!.apply { order = row }
|
||||||
fragment.preferenceScreen.addPreference(pref)
|
dashboardFragment.preferenceScreen.addPreference(pref)
|
||||||
} else {
|
} else {
|
||||||
val prefKey = getPreferenceKey(settingId)
|
val prefKey = getPreferenceKey(settingId)
|
||||||
|
|
||||||
prefVisibilityJobs.add(
|
prefVisibilityJobs.add(
|
||||||
getDevicesSettingForRow(layout, row)
|
viewModel
|
||||||
.onEach { logItemShown(prefKey, it.isNotEmpty()) }
|
.getDeviceSetting(cachedDevice, settingId)
|
||||||
.launchIn(fragment.lifecycleScope)
|
.onEach { logItemShown(prefKey, it != null) }
|
||||||
|
.launchIn(dashboardFragment.lifecycleScope)
|
||||||
)
|
)
|
||||||
val pref =
|
if (settingId == DeviceSettingId.DEVICE_SETTING_ID_ANC) {
|
||||||
ComposePreference(context)
|
// TODO(b/399316980): replace it with SegmentedButtonPreference once it's ready.
|
||||||
.apply {
|
val pref =
|
||||||
key = prefKey
|
ComposePreference(context)
|
||||||
order = row
|
.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) } }
|
.launchIn(dashboardFragment.lifecycleScope)
|
||||||
fragment.preferenceScreen.addPreference(pref)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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) {
|
for (row in items.indices) {
|
||||||
val settingItem = items[row]
|
val settingItem = items[row]
|
||||||
val settingId = settingItem.settingId
|
val settingId = settingItem.settingId
|
||||||
if (settingIdToXmlPreferences.containsKey(settingId)) {
|
settingIdToXmlPreferences[settingId]?.let { pref ->
|
||||||
val pref = fragment.preferenceScreen.getPreference(row)
|
|
||||||
if (settingId == DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES) {
|
if (settingId == DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES) {
|
||||||
(getController(pref.key) as? BluetoothDetailsProfilesController)?.run {
|
(getController(pref.key) as? BluetoothDetailsProfilesController)?.run {
|
||||||
if (settingItem is DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem) {
|
if (
|
||||||
|
settingItem
|
||||||
|
is DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem
|
||||||
|
) {
|
||||||
setInvisibleProfiles(settingItem.invisibleProfiles)
|
setInvisibleProfiles(settingItem.invisibleProfiles)
|
||||||
setHasExtraSpace(false)
|
setHasExtraSpace(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getController(pref.key)?.displayPreference(fragment.preferenceScreen)
|
getController(pref.key)?.displayPreference(dashboardFragment.preferenceScreen)
|
||||||
logItemShown(pref.key, pref.isVisible)
|
logItemShown(pref.key, pref.isVisible)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment.lifecycleScope.launch {
|
dashboardFragment.lifecycleScope.launch {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
fragment.setLoading(false, false)
|
dashboardFragment.setLoading(false, false)
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,87 +247,170 @@ class DeviceDetailsFragmentFormatterImpl(
|
|||||||
} ?: emit(null)
|
} ?: emit(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDevicesSettingForRow(
|
private fun buildPreference(
|
||||||
layout: DeviceSettingLayout,
|
existedPref: Preference?,
|
||||||
row: Int,
|
model: DeviceSettingPreferenceModel,
|
||||||
): Flow<List<DeviceSettingPreferenceModel>> =
|
prefKey: String,
|
||||||
layout.rows[row].columns.flatMapLatest { columns ->
|
highlighted: Boolean,
|
||||||
if (columns.isEmpty()) {
|
): Preference? =
|
||||||
flowOf(emptyList())
|
when (model) {
|
||||||
} else {
|
is DeviceSettingPreferenceModel.PlainPreference -> {
|
||||||
combine(
|
val pref =
|
||||||
columns.map { column ->
|
existedPref
|
||||||
viewModel.getDeviceSetting(cachedDevice, column.settingId)
|
?: run {
|
||||||
}
|
if (highlighted) SpotlightPreference(context) else Preference(context)
|
||||||
) {
|
}
|
||||||
it.toList().filterNotNull()
|
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 getDrawable(deviceSettingIcon: DeviceSettingIcon?): Drawable? =
|
||||||
private fun buildPreference(layout: DeviceSettingLayout, row: Int, prefKey: String) {
|
when (deviceSettingIcon) {
|
||||||
val contents by
|
is DeviceSettingIcon.BitmapIcon ->
|
||||||
remember(row) { getDevicesSettingForRow(layout, row) }
|
deviceSettingIcon.bitmap.toDrawable(context.resources)
|
||||||
.collectAsStateWithLifecycle(initialValue = listOf())
|
is DeviceSettingIcon.ResourceIcon -> context.getDrawable(deviceSettingIcon.resId)
|
||||||
|
null -> null
|
||||||
val highlighted by
|
|
||||||
remember(row) {
|
|
||||||
layout.rows[row].columns.map { columns -> columns.any { it.highlighted } }
|
|
||||||
}
|
}
|
||||||
.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
|
val settings = contents
|
||||||
AnimatedVisibility(visible = settings.isNotEmpty(), enter = fadeIn(), exit = fadeOut()) {
|
AnimatedVisibility(visible = settings != null, enter = fadeIn(), exit = fadeOut()) {
|
||||||
Box {
|
(settings as? DeviceSettingPreferenceModel.MultiTogglePreference)?.let {
|
||||||
Box(
|
buildMultiTogglePreference(it, prefKey)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
@Composable
|
||||||
private fun buildMultiTogglePreference(
|
private fun buildMultiTogglePreference(
|
||||||
pref: DeviceSettingPreferenceModel.MultiTogglePreference,
|
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) {
|
private fun logItemClick(preferenceKey: String, value: Int = 0) {
|
||||||
logAction(preferenceKey, SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_CLICKED, value)
|
logAction(preferenceKey, SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_CLICKED, value)
|
||||||
}
|
}
|
||||||
@@ -452,7 +445,7 @@ class DeviceDetailsFragmentFormatterImpl(
|
|||||||
if (it) EVENT_VISIBLE else EVENT_INVISIBLE,
|
if (it) EVENT_VISIBLE else EVENT_INVISIBLE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.launchIn(fragment.lifecycleScope)
|
.launchIn(dashboardFragment.lifecycleScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.value = visible
|
.value = visible
|
||||||
@@ -485,7 +478,7 @@ class DeviceDetailsFragmentFormatterImpl(
|
|||||||
|
|
||||||
private fun disableController(controller: AbstractPreferenceController) {
|
private fun disableController(controller: AbstractPreferenceController) {
|
||||||
if (controller is LifecycleObserver) {
|
if (controller is LifecycleObserver) {
|
||||||
fragment.settingsLifecycle.removeObserver(controller as LifecycleObserver)
|
dashboardFragment.settingsLifecycle.removeObserver(controller as LifecycleObserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controller is BlockingPrefWithSliceController) {
|
if (controller is BlockingPrefWithSliceController) {
|
||||||
@@ -504,6 +497,19 @@ class DeviceDetailsFragmentFormatterImpl(
|
|||||||
|
|
||||||
private fun getPreferenceKey(settingId: Int) = "DEVICE_SETTING_${settingId}"
|
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 {
|
private companion object {
|
||||||
const val TAG = "DeviceDetailsFormatter"
|
const val TAG = "DeviceDetailsFormatter"
|
||||||
const val EVENT_SWITCH_OFF = 0
|
const val EVENT_SWITCH_OFF = 0
|
||||||
|
@@ -23,9 +23,6 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.android.settings.R
|
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.DeviceSettingPreferenceModel
|
||||||
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
||||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||||
@@ -39,11 +36,8 @@ import kotlin.coroutines.CoroutineContext
|
|||||||
import kotlinx.coroutines.CoroutineStart
|
import kotlinx.coroutines.CoroutineStart
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
|
|
||||||
class BluetoothDeviceDetailsViewModel(
|
class BluetoothDeviceDetailsViewModel(
|
||||||
private val application: Application,
|
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(
|
class Factory(
|
||||||
private val application: Application,
|
private val application: Application,
|
||||||
private val bluetoothAdapter: BluetoothAdapter,
|
private val bluetoothAdapter: BluetoothAdapter,
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package com.android.settings.bluetooth.ui.view
|
package com.android.settings.bluetooth.ui.view
|
||||||
|
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -25,6 +25,7 @@ import androidx.fragment.app.FragmentActivity
|
|||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
||||||
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
||||||
@@ -33,6 +34,7 @@ import com.android.settings.testutils.FakeFeatureFactory
|
|||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
||||||
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
|
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
|
||||||
import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
|
import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
|
||||||
|
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingActionModel
|
||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
|
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
|
||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
|
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
|
||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
|
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
|
||||||
@@ -58,14 +60,15 @@ import org.mockito.Mock
|
|||||||
import org.mockito.Mockito.any
|
import org.mockito.Mockito.any
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
|
import org.mockito.Spy
|
||||||
import org.mockito.junit.MockitoJUnit
|
import org.mockito.junit.MockitoJUnit
|
||||||
import org.mockito.junit.MockitoRule
|
import org.mockito.junit.MockitoRule
|
||||||
|
import org.mockito.kotlin.doNothing
|
||||||
import org.robolectric.Robolectric
|
import org.robolectric.Robolectric
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
import org.robolectric.shadows.ShadowLooper
|
import org.robolectric.shadows.ShadowLooper
|
||||||
import org.robolectric.shadows.ShadowLooper.shadowMainLooper
|
import org.robolectric.shadows.ShadowLooper.shadowMainLooper
|
||||||
|
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
class DeviceDetailsFragmentFormatterTest {
|
class DeviceDetailsFragmentFormatterTest {
|
||||||
@@ -78,7 +81,7 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
@Mock private lateinit var headerController: AbstractPreferenceController
|
@Mock private lateinit var headerController: AbstractPreferenceController
|
||||||
@Mock private lateinit var buttonController: AbstractPreferenceController
|
@Mock private lateinit var buttonController: AbstractPreferenceController
|
||||||
|
|
||||||
private lateinit var context: Context
|
@Spy private val context: Context = ApplicationProvider.getApplicationContext()
|
||||||
private lateinit var fragment: TestFragment
|
private lateinit var fragment: TestFragment
|
||||||
private lateinit var underTest: DeviceDetailsFragmentFormatter
|
private lateinit var underTest: DeviceDetailsFragmentFormatter
|
||||||
private lateinit var featureFactory: FakeFeatureFactory
|
private lateinit var featureFactory: FakeFeatureFactory
|
||||||
@@ -87,11 +90,15 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
context = ApplicationProvider.getApplicationContext()
|
|
||||||
featureFactory = FakeFeatureFactory.setupForTest()
|
featureFactory = FakeFeatureFactory.setupForTest()
|
||||||
|
doNothing().`when`(context).startActivity(any(Intent::class.java))
|
||||||
`when`(
|
`when`(
|
||||||
featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository(
|
featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository(
|
||||||
eq(context), eq(bluetoothAdapter), any()))
|
any(),
|
||||||
|
eq(bluetoothAdapter),
|
||||||
|
any(),
|
||||||
|
)
|
||||||
|
)
|
||||||
.thenReturn(repository)
|
.thenReturn(repository)
|
||||||
fragmentActivity = Robolectric.setupActivity(FragmentActivity::class.java)
|
fragmentActivity = Robolectric.setupActivity(FragmentActivity::class.java)
|
||||||
assertThat(fragmentActivity.applicationContext).isNotNull()
|
assertThat(fragmentActivity.applicationContext).isNotNull()
|
||||||
@@ -115,7 +122,8 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
listOf(profileController, headerController, buttonController),
|
listOf(profileController, headerController, buttonController),
|
||||||
bluetoothAdapter,
|
bluetoothAdapter,
|
||||||
cachedDevice,
|
cachedDevice,
|
||||||
testScope.testScheduler)
|
testScope.testScheduler,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -124,11 +132,16 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
`when`(repository.getDeviceSettingsConfig(cachedDevice))
|
`when`(repository.getDeviceSettingsConfig(cachedDevice))
|
||||||
.thenReturn(
|
.thenReturn(
|
||||||
DeviceSettingConfigModel(
|
DeviceSettingConfigModel(
|
||||||
listOf(), listOf(), DeviceSettingConfigItemModel.AppProvidedItem(12345, false)))
|
listOf(),
|
||||||
val intent = Intent().apply {
|
listOf(),
|
||||||
setAction(Intent.ACTION_VIEW)
|
DeviceSettingConfigItemModel.AppProvidedItem(12345, false),
|
||||||
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
)
|
||||||
}
|
)
|
||||||
|
val intent =
|
||||||
|
Intent().apply {
|
||||||
|
setAction(Intent.ACTION_VIEW)
|
||||||
|
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
`when`(repository.getDeviceSetting(cachedDevice, 12345))
|
`when`(repository.getDeviceSetting(cachedDevice, 12345))
|
||||||
.thenReturn(
|
.thenReturn(
|
||||||
flowOf(
|
flowOf(
|
||||||
@@ -136,12 +149,15 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
cachedDevice = cachedDevice,
|
cachedDevice = cachedDevice,
|
||||||
id = 12345,
|
id = 12345,
|
||||||
intent = intent,
|
intent = intent,
|
||||||
)))
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
var helpPreference: DeviceSettingPreferenceModel.HelpPreference? = null
|
var helpPreference: DeviceSettingPreferenceModel.HelpPreference? = null
|
||||||
underTest.getMenuItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment).onEach {
|
underTest
|
||||||
helpPreference = it
|
.getMenuItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment)
|
||||||
}.launchIn(testScope.backgroundScope)
|
.onEach { helpPreference = it }
|
||||||
|
.launchIn(testScope.backgroundScope)
|
||||||
delay(100)
|
delay(100)
|
||||||
runCurrent()
|
runCurrent()
|
||||||
ShadowLooper.idleMainLooper()
|
ShadowLooper.idleMainLooper()
|
||||||
@@ -171,13 +187,19 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
listOf(
|
listOf(
|
||||||
DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
|
DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
|
||||||
DeviceSettingId.DEVICE_SETTING_ID_HEADER,
|
DeviceSettingId.DEVICE_SETTING_ID_HEADER,
|
||||||
highlighted = false, preferenceKey = "bluetooth_device_header"),
|
highlighted = false,
|
||||||
|
preferenceKey = "bluetooth_device_header",
|
||||||
|
),
|
||||||
DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
|
DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
|
||||||
DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES,
|
DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES,
|
||||||
highlighted = false, preferenceKey = "bluetooth_profiles"),
|
highlighted = false,
|
||||||
|
preferenceKey = "bluetooth_profiles",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
listOf(),
|
listOf(),
|
||||||
null))
|
null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment)
|
underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment)
|
||||||
runCurrent()
|
runCurrent()
|
||||||
@@ -189,13 +211,17 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
SettingsEnums.PAGE_UNKNOWN,
|
SettingsEnums.PAGE_UNKNOWN,
|
||||||
SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN,
|
SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN,
|
||||||
0,
|
0,
|
||||||
"bluetooth_device_header", 1)
|
"bluetooth_device_header",
|
||||||
|
1,
|
||||||
|
)
|
||||||
verify(featureFactory.metricsFeatureProvider)
|
verify(featureFactory.metricsFeatureProvider)
|
||||||
.action(
|
.action(
|
||||||
SettingsEnums.PAGE_UNKNOWN,
|
SettingsEnums.PAGE_UNKNOWN,
|
||||||
SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN,
|
SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN,
|
||||||
0,
|
0,
|
||||||
"bluetooth_profiles", 1)
|
"bluetooth_profiles",
|
||||||
|
1,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,16 +235,22 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
|
DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
|
||||||
DeviceSettingId.DEVICE_SETTING_ID_HEADER,
|
DeviceSettingId.DEVICE_SETTING_ID_HEADER,
|
||||||
highlighted = false,
|
highlighted = false,
|
||||||
preferenceKey = "bluetooth_device_header"),
|
preferenceKey = "bluetooth_device_header",
|
||||||
|
),
|
||||||
DeviceSettingConfigItemModel.AppProvidedItem(
|
DeviceSettingConfigItemModel.AppProvidedItem(
|
||||||
DeviceSettingId.DEVICE_SETTING_ID_ANC, highlighted = false),
|
DeviceSettingId.DEVICE_SETTING_ID_ANC,
|
||||||
|
highlighted = false,
|
||||||
|
),
|
||||||
DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
|
DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
|
||||||
DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES,
|
DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES,
|
||||||
highlighted = false,
|
highlighted = false,
|
||||||
preferenceKey = "bluetooth_profiles"),
|
preferenceKey = "bluetooth_profiles",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
listOf(),
|
listOf(),
|
||||||
null))
|
null,
|
||||||
|
)
|
||||||
|
)
|
||||||
`when`(repository.getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC))
|
`when`(repository.getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC))
|
||||||
.thenReturn(
|
.thenReturn(
|
||||||
flowOf(
|
flowOf(
|
||||||
@@ -231,11 +263,17 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
ToggleModel(
|
ToggleModel(
|
||||||
"",
|
"",
|
||||||
DeviceSettingIcon.BitmapIcon(
|
DeviceSettingIcon.BitmapIcon(
|
||||||
Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)))),
|
Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
isActive = true,
|
isActive = true,
|
||||||
state = DeviceSettingStateModel.MultiTogglePreferenceState(0),
|
state = DeviceSettingStateModel.MultiTogglePreferenceState(0),
|
||||||
isAllowedChangingState = true,
|
isAllowedChangingState = true,
|
||||||
updateState = {})))
|
updateState = {},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment)
|
underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment)
|
||||||
runCurrent()
|
runCurrent()
|
||||||
@@ -244,13 +282,119 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
.containsExactly(
|
.containsExactly(
|
||||||
"bluetooth_device_header",
|
"bluetooth_device_header",
|
||||||
"DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}",
|
"DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}",
|
||||||
"bluetooth_profiles")
|
"bluetooth_profiles",
|
||||||
|
)
|
||||||
verify(featureFactory.metricsFeatureProvider)
|
verify(featureFactory.metricsFeatureProvider)
|
||||||
.action(
|
.action(
|
||||||
SettingsEnums.PAGE_UNKNOWN,
|
SettingsEnums.PAGE_UNKNOWN,
|
||||||
SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN,
|
SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN,
|
||||||
0,
|
0,
|
||||||
"DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}", 1
|
"DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateLayout_plainPreferenceClicked() {
|
||||||
|
testScope.runTest {
|
||||||
|
val settingId = 12345
|
||||||
|
val intent = Intent("test_intent")
|
||||||
|
`when`(repository.getDeviceSettingsConfig(cachedDevice))
|
||||||
|
.thenReturn(
|
||||||
|
DeviceSettingConfigModel(
|
||||||
|
listOf(
|
||||||
|
DeviceSettingConfigItemModel.AppProvidedItem(
|
||||||
|
settingId,
|
||||||
|
highlighted = false,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
listOf(),
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
`when`(repository.getDeviceSetting(cachedDevice, settingId))
|
||||||
|
.thenReturn(
|
||||||
|
flowOf(
|
||||||
|
DeviceSettingModel.ActionSwitchPreference(
|
||||||
|
cachedDevice = cachedDevice,
|
||||||
|
id = settingId,
|
||||||
|
title = "title",
|
||||||
|
summary = "summary",
|
||||||
|
icon = null,
|
||||||
|
action = DeviceSettingActionModel.IntentAction(intent),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment)
|
||||||
|
runCurrent()
|
||||||
|
val displayedPrefs = getDisplayedPreferences()
|
||||||
|
displayedPrefs[0].performClick()
|
||||||
|
|
||||||
|
assertThat(displayedPrefs).hasSize(1)
|
||||||
|
verify(context).startActivity(intent)
|
||||||
|
verify(featureFactory.metricsFeatureProvider)
|
||||||
|
.action(
|
||||||
|
SettingsEnums.PAGE_UNKNOWN,
|
||||||
|
SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_CLICKED,
|
||||||
|
0,
|
||||||
|
"DEVICE_SETTING_$settingId",
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateLayout_switchPreferenceClicked() {
|
||||||
|
val settingId = 12345
|
||||||
|
testScope.runTest {
|
||||||
|
`when`(repository.getDeviceSettingsConfig(cachedDevice))
|
||||||
|
.thenReturn(
|
||||||
|
DeviceSettingConfigModel(
|
||||||
|
listOf(
|
||||||
|
DeviceSettingConfigItemModel.AppProvidedItem(
|
||||||
|
settingId,
|
||||||
|
highlighted = false,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
listOf(),
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
`when`(repository.getDeviceSetting(cachedDevice, settingId))
|
||||||
|
.thenReturn(
|
||||||
|
flowOf(
|
||||||
|
DeviceSettingModel.ActionSwitchPreference(
|
||||||
|
cachedDevice = cachedDevice,
|
||||||
|
id = settingId,
|
||||||
|
title = "title",
|
||||||
|
summary = "summary",
|
||||||
|
icon = null,
|
||||||
|
action = null,
|
||||||
|
switchState = DeviceSettingStateModel.ActionSwitchPreferenceState(true),
|
||||||
|
isAllowedChangingState = true,
|
||||||
|
updateState = {},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment)
|
||||||
|
runCurrent()
|
||||||
|
val displayedPrefs = getDisplayedPreferences()
|
||||||
|
displayedPrefs[0].performClick()
|
||||||
|
|
||||||
|
assertThat(displayedPrefs).hasSize(1)
|
||||||
|
assertThat(displayedPrefs[0]).isInstanceOf(SwitchPreferenceCompat::class.java)
|
||||||
|
verify(featureFactory.metricsFeatureProvider)
|
||||||
|
.action(
|
||||||
|
SettingsEnums.PAGE_UNKNOWN,
|
||||||
|
SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_CLICKED,
|
||||||
|
0,
|
||||||
|
"DEVICE_SETTING_$settingId",
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,6 @@ import android.app.Application
|
|||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
|
|
||||||
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
||||||
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
||||||
import com.android.settings.testutils.FakeFeatureFactory
|
import com.android.settings.testutils.FakeFeatureFactory
|
||||||
@@ -164,74 +163,6 @@ class BluetoothDeviceDetailsViewModelTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getLayout_builtinDeviceSettings() {
|
|
||||||
testScope.runTest {
|
|
||||||
`when`(repository.getDeviceSettingsConfig(cachedDevice))
|
|
||||||
.thenReturn(
|
|
||||||
DeviceSettingConfigModel(
|
|
||||||
listOf(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2), listOf(), null))
|
|
||||||
|
|
||||||
val layout = underTest.getLayout(FragmentTypeModel.DeviceDetailsMainFragment)!!
|
|
||||||
|
|
||||||
assertThat(getLatestLayout(layout))
|
|
||||||
.isEqualTo(
|
|
||||||
listOf(
|
|
||||||
listOf(DeviceSettingId.DEVICE_SETTING_ID_HEADER),
|
|
||||||
listOf(DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getLayout_remoteDeviceSettings() {
|
|
||||||
val remoteSettingId1 = 10001
|
|
||||||
val remoteSettingId2 = 10002
|
|
||||||
val remoteSettingId3 = 10003
|
|
||||||
testScope.runTest {
|
|
||||||
`when`(repository.getDeviceSettingsConfig(cachedDevice))
|
|
||||||
.thenReturn(
|
|
||||||
DeviceSettingConfigModel(
|
|
||||||
listOf(
|
|
||||||
BUILTIN_SETTING_ITEM_1,
|
|
||||||
buildRemoteSettingItem(remoteSettingId1),
|
|
||||||
buildRemoteSettingItem(remoteSettingId2),
|
|
||||||
buildRemoteSettingItem(remoteSettingId3),
|
|
||||||
),
|
|
||||||
listOf(),
|
|
||||||
null))
|
|
||||||
`when`(repository.getDeviceSetting(cachedDevice, remoteSettingId1))
|
|
||||||
.thenReturn(flowOf(buildMultiTogglePreference(remoteSettingId1)))
|
|
||||||
`when`(repository.getDeviceSetting(cachedDevice, remoteSettingId2))
|
|
||||||
.thenReturn(flowOf(buildMultiTogglePreference(remoteSettingId2)))
|
|
||||||
`when`(repository.getDeviceSetting(cachedDevice, remoteSettingId3))
|
|
||||||
.thenReturn(flowOf(buildActionSwitchPreference(remoteSettingId3)))
|
|
||||||
|
|
||||||
val layout = underTest.getLayout(FragmentTypeModel.DeviceDetailsMainFragment)!!
|
|
||||||
|
|
||||||
assertThat(getLatestLayout(layout))
|
|
||||||
.isEqualTo(
|
|
||||||
listOf(
|
|
||||||
listOf(DeviceSettingId.DEVICE_SETTING_ID_HEADER),
|
|
||||||
listOf(remoteSettingId1),
|
|
||||||
listOf(remoteSettingId2),
|
|
||||||
listOf(remoteSettingId3),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLatestLayout(layout: DeviceSettingLayout): List<List<Int>> {
|
|
||||||
val latestLayout = MutableList(layout.rows.size) { emptyList<Int>() }
|
|
||||||
for (i in layout.rows.indices) {
|
|
||||||
layout.rows[i]
|
|
||||||
.columns
|
|
||||||
.onEach { latestLayout[i] = it.map { c -> c.settingId } }
|
|
||||||
.launchIn(testScope.backgroundScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
testScope.runCurrent()
|
|
||||||
return latestLayout.filter { !it.isEmpty() }.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildMultiTogglePreference(settingId: Int) =
|
private fun buildMultiTogglePreference(settingId: Int) =
|
||||||
DeviceSettingModel.MultiTogglePreference(
|
DeviceSettingModel.MultiTogglePreference(
|
||||||
cachedDevice,
|
cachedDevice,
|
||||||
|
Reference in New Issue
Block a user