[expressive design] Create IntroPreference layout.

Test: visual
Bug: 360916599
Flag: EXEMPT bug fix
Change-Id: I8f912c4aab416666acaf4688c9a7325bd5ebc3cc
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 83d657e..cff0059 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -42,6 +42,7 @@
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
 import com.android.settingslib.spa.gallery.scaffold.NonScrollablePagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
+import com.android.settingslib.spa.gallery.preference.IntroPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
@@ -109,6 +110,7 @@
                 SuwScaffoldPageProvider,
                 BannerPageProvider,
                 CopyablePageProvider,
+                IntroPreferencePageProvider,
             ),
             rootPages = listOf(
                 HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
new file mode 100644
index 0000000..603fcee
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.IntroPreference
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+private const val TITLE = "Sample IntroPreference"
+
+object IntroPreferencePageProvider : SettingsPageProvider {
+    override val name = "IntroPreference"
+    private val owner = createSettingsPage()
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val entryList = mutableListOf<SettingsEntry>()
+        entryList.add(
+            SettingsEntryBuilder.create("IntroPreference", owner)
+                .setUiLayoutFn { SampleIntroPreference() }
+                .build()
+        )
+
+        return entryList
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+            Preference(
+                object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                }
+            )
+        }
+    }
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+}
+
+@Composable
+private fun SampleIntroPreference() {
+    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+        IntroPreference(
+            title = "Preferred network type",
+            descriptions = listOf("Description"),
+            imageVector = Icons.Outlined.AirplanemodeActive,
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index ce9678b..6d13967 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -39,6 +39,7 @@
             ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
                 .setLink(fromPage = owner).build(),
+            IntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
         )
     }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 1f3e2425..1b4f856 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -21,6 +21,7 @@
 
 object SettingsDimension {
     val paddingTiny = 2.dp
+    val paddingExtraSmall = 4.dp
     val paddingSmall = 4.dp
     val paddingLarge = 16.dp
     val paddingExtraLarge = 24.dp
@@ -56,6 +57,7 @@
     val itemDividerHeight = 32.dp
 
     val iconLarge = 48.dp
+    val introIconSize = 40.dp
 
     /** The size when app icon is displayed in list. */
     val appIconItemSize = 32.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
new file mode 100644
index 0000000..22a5755
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun IntroPreference(
+    title: String,
+    descriptions: List<String>? = null,
+    imageVector: ImageVector? = null,
+) {
+    IntroPreference(title = title, descriptions = descriptions, icon = { IntroIcon(imageVector) })
+}
+
+@Composable
+fun IntroAppPreference(
+    title: String,
+    descriptions: List<String>? = null,
+    appIcon: @Composable (() -> Unit),
+) {
+    IntroPreference(title = title, descriptions = descriptions, icon = { IntroAppIcon(appIcon) })
+}
+
+@Composable
+internal fun IntroPreference(
+    title: String,
+    descriptions: List<String>?,
+    icon: @Composable (() -> Unit),
+) {
+    Column(
+        modifier =
+            Modifier.fillMaxWidth()
+                .padding(
+                    horizontal = SettingsDimension.paddingExtraLarge,
+                    vertical = SettingsDimension.paddingLarge,
+                ),
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        icon()
+        IntroTitle(title)
+        IntroDescription(descriptions)
+    }
+}
+
+@Composable
+private fun IntroIcon(imageVector: ImageVector?) {
+    if (imageVector != null) {
+        Box(
+            modifier =
+                Modifier.size(SettingsDimension.itemIconContainerSize)
+                    .clip(CircleShape)
+                    .background(MaterialTheme.colorScheme.secondaryContainer),
+            contentAlignment = Alignment.Center,
+        ) {
+            Icon(
+                imageVector = imageVector,
+                contentDescription = null,
+                modifier = Modifier.size(SettingsDimension.introIconSize),
+                tint = MaterialTheme.colorScheme.onSecondary,
+            )
+        }
+    }
+}
+
+@Composable
+private fun IntroAppIcon(appIcon: @Composable () -> Unit) {
+    Box(
+        modifier = Modifier.size(SettingsDimension.itemIconContainerSize).clip(CircleShape),
+        contentAlignment = Alignment.Center,
+    ) {
+        appIcon()
+    }
+}
+
+@Composable
+private fun IntroTitle(title: String) {
+    Box(modifier = Modifier.padding(top = SettingsDimension.paddingLarge)) {
+        Text(
+            text = title,
+            textAlign = TextAlign.Center,
+            style = MaterialTheme.typography.titleLarge,
+            color = MaterialTheme.colorScheme.onSurface,
+        )
+    }
+}
+
+@Composable
+private fun IntroDescription(descriptions: List<String>?) {
+    if (descriptions != null) {
+        for (description in descriptions) {
+            if (description.isEmpty()) continue
+            Text(
+                text = description,
+                textAlign = TextAlign.Center,
+                style = MaterialTheme.typography.titleMedium,
+                color = MaterialTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.padding(top = SettingsDimension.paddingExtraSmall),
+            )
+        }
+    }
+}
+
+@Preview
+@Composable
+private fun IntroPreferencePreview() {
+    IntroPreference(
+        title = "Preferred network type",
+        descriptions = listOf("Description", "Version"),
+        imageVector = Icons.Outlined.AirplanemodeActive,
+    )
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
new file mode 100644
index 0000000..5d80145
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.settingslib.spa.widget.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class IntroPreferenceTest {
+    @get:Rule val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent { IntroPreference(title = TITLE) }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun description_displayed() {
+        composeTestRule.setContent { IntroPreference(title = TITLE, descriptions = DESCRIPTION) }
+
+        composeTestRule.onNodeWithText(DESCRIPTION.component1()).assertIsDisplayed()
+        composeTestRule.onNodeWithText(DESCRIPTION.component2()).assertIsNotDisplayed()
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        val DESCRIPTION = listOf("Description", "")
+    }
+}