diff --git a/res/layout/preference_compose.xml b/res/layout/preference_compose.xml new file mode 100644 index 00000000000..14cb2d79b6a --- /dev/null +++ b/res/layout/preference_compose.xml @@ -0,0 +1,21 @@ + + + + diff --git a/src/com/android/settings/spa/preference/ComposePreference.kt b/src/com/android/settings/spa/preference/ComposePreference.kt new file mode 100644 index 00000000000..0c9abad2d26 --- /dev/null +++ b/src/com/android/settings/spa/preference/ComposePreference.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 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.spa.preference + +import android.content.Context +import android.util.AttributeSet +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import com.android.settings.R +import com.android.settingslib.spa.framework.theme.SettingsTheme + +class ComposePreference @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0, +) : Preference(context, attrs, defStyleAttr, defStyleRes) { + var content: @Composable () -> Unit = {} + + init { + layoutResource = R.layout.preference_compose + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + + (holder.itemView as ComposeView).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + SettingsTheme { + content() + } + } + } + } +} diff --git a/src/com/android/settings/spa/preference/ComposePreferenceController.kt b/src/com/android/settings/spa/preference/ComposePreferenceController.kt new file mode 100644 index 00000000000..3ddb66bbefa --- /dev/null +++ b/src/com/android/settings/spa/preference/ComposePreferenceController.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 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.spa.preference + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.preference.PreferenceScreen +import com.android.settings.core.BasePreferenceController + +abstract class ComposePreferenceController(context: Context, preferenceKey: String) : + BasePreferenceController(context, preferenceKey) { + + private lateinit var preference: ComposePreference + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey)!! + preference.content = { Content() } + } + + @Composable + abstract fun Content() +} diff --git a/tests/spa_unit/src/com/android/settings/spa/preference/ComposePreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/spa/preference/ComposePreferenceControllerTest.kt new file mode 100644 index 00000000000..36817d1d3c7 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/preference/ComposePreferenceControllerTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 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.spa.preference + +import android.content.Context +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.preference.PreferenceManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ComposePreferenceControllerTest { + + @get:Rule + val composeTestRule = createComposeRule() + + private val context: Context = ApplicationProvider.getApplicationContext() + + private val controller = object : ComposePreferenceController( + context = context, + preferenceKey = TEST_KEY, + ) { + override fun getAvailabilityStatus() = AVAILABLE + + @Composable + override fun Content() { + Text(TEXT) + } + } + + private val preference = ComposePreference(context).apply { + key = TEST_KEY + } + + private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context) + .apply { addPreference(preference) } + + @Test + fun displayPreference() { + controller.displayPreference(preferenceScreen) + + composeTestRule.setContent { + preference.content() + } + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + private companion object { + const val TEST_KEY = "test_key" + const val TEXT = "Text" + } +} diff --git a/tests/spa_unit/src/com/android/settings/spa/preference/ComposePreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/preference/ComposePreferenceTest.kt new file mode 100644 index 00000000000..28bde3ab3a0 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/preference/ComposePreferenceTest.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 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.spa.preference + +import android.content.Context +import androidx.compose.material3.Text +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.preference.PreferenceViewHolder +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ComposePreferenceTest { + + @get:Rule + val composeTestRule = createComposeRule() + + private val context: Context = ApplicationProvider.getApplicationContext() + + private val preference = ComposePreference(context) + + private val composeView = ComposeView(context) + + @Test + fun onBindViewHolder() { + preference.content = { + Text(TEXT) + } + + preference.onBindViewHolder(PreferenceViewHolder.createInstanceForTests(composeView)) + + composeTestRule.setContent { + composeView.Content() + } + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + private companion object { + const val TEXT = "Text" + } +}