Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ed2035f7 authored by Kelly Zhang's avatar Kelly Zhang Committed by Android (Google) Code Review
Browse files

Merge "Add ProgressBar and LoadingBar widgets to SPA and create gallery samples for them."

parents 85a70efe 8391a3f0
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
@@ -66,6 +67,7 @@ object GallerySpaEnvironment : SpaEnvironment() {
                IllustrationPageProvider,
                CategoryPageProvider,
                ActionButtonPageProvider,
                ProgressBarPageProvider,
            ),
            rootPages = listOf(
                HomePageProvider.createSettingsPage(),
+2 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import com.android.settingslib.spa.gallery.page.ArgumentPageModel
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
@@ -54,6 +55,7 @@ object HomePageProvider : SettingsPageProvider {
            IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
            ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
        )
    }

+134 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.page

import android.os.Bundle
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.SystemUpdate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.ProgressBarPreference
import com.android.settingslib.spa.widget.preference.ProgressBarPreferenceModel
import com.android.settingslib.spa.widget.preference.ProgressBarWithDataPreference
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.CircularLoadingBar
import com.android.settingslib.spa.widget.ui.CircularProgressBar
import com.android.settingslib.spa.widget.ui.LinearLoadingBar
import kotlinx.coroutines.delay

private const val TITLE = "Sample ProgressBar"

object ProgressBarPageProvider : SettingsPageProvider {
    override val name = "ProgressBar"

    fun buildInjectEntry(): SettingsEntryBuilder {
        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
            .setIsAllowSearch(true)
            .setUiLayoutFn {
                Preference(object : PreferenceModel {
                    override val title = TITLE
                    override val onClick = navigator(name)
                })
            }
    }

    @Composable
    override fun Page(arguments: Bundle?) {
        // Mocks a loading time of 2 seconds.
        var loading by remember { mutableStateOf(true) }
        LaunchedEffect(Unit) {
            delay(2000)
            loading = false
        }

        RegularScaffold(title = TITLE) {
            // Auto update the progress and finally jump tp 0.4f.
            var progress by remember { mutableStateOf(0f) }
            LaunchedEffect(Unit) {
                delay(2000)
                while (progress < 1f) {
                    delay(100)
                    progress += 0.01f
                }
                delay(500)
                progress = 0.4f
            }

            // Show as a placeholder for progress bar
            LargeProgressBar(progress)
            // The remaining information only shows after loading complete.
            if (!loading) {
                SimpleProgressBar()
                ProgressBarWithData()
                CircularProgressBar(progress = progress, radius = 160f)
            }
        }

        // Add loading bar examples, running for 2 seconds.
        LinearLoadingBar(isLoading = loading, yOffset = 64.dp)
        CircularLoadingBar(isLoading = loading)
    }
}

@Composable
private fun LargeProgressBar(progress: Float) {
    ProgressBarPreference(object : ProgressBarPreferenceModel {
        override val title = "Large Progress Bar"
        override val progress = progress
        override val height = 20f
    })
}

@Composable
private fun SimpleProgressBar() {
    ProgressBarPreference(object : ProgressBarPreferenceModel {
        override val title = "Simple Progress Bar"
        override val progress = 0.2f
        override val icon = Icons.Outlined.SystemUpdate
    })
}

@Composable
private fun ProgressBarWithData() {
    ProgressBarWithDataPreference(model = object : ProgressBarPreferenceModel {
        override val title = "Progress Bar with Data"
        override val progress = 0.2f
        override val icon = Icons.Outlined.Delete
    }, data = "25G")
}

@Preview(showBackground = true)
@Composable
private fun ProgressBarPagePreview() {
    SettingsTheme {
        ProgressBarPageProvider.Page(null)
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ internal fun BaseLayout(
}

@Composable
private fun BaseIcon(
internal fun BaseIcon(
    icon: @Composable (() -> Unit)?,
    modifier: Modifier,
    paddingStart: Dp,
+171 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.graphics.vector.ImageVector
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.LinearProgressBar
import com.android.settingslib.spa.widget.ui.SettingsTitle

/**
 * The widget model for [ProgressBarPreference] widget.
 */
interface ProgressBarPreferenceModel {
    /**
     * The title of this [ProgressBarPreference].
     */
    val title: String

    /**
     * The progress fraction of the ProgressBar. Should be float in range [0f, 1f]
     */
    val progress: Float

    /**
     * The icon image for [ProgressBarPreference]. If not specified, hides the icon by default.
     */
    val icon: ImageVector?
        get() = null

    /**
     * The height of the ProgressBar.
     */
    val height: Float
        get() = 4f

    /**
     * Indicates whether to use rounded corner for the progress bars.
     */
    val roundedCorner: Boolean
        get() = true
}

/**
 * Progress bar preference widget.
 *
 * Data is provided through [ProgressBarPreferenceModel].
 */
@Composable
fun ProgressBarPreference(model: ProgressBarPreferenceModel) {
    ProgressBarPreference(
        title = model.title,
        progress = model.progress,
        icon = model.icon,
        height = model.height,
        roundedCorner = model.roundedCorner,
    )
}

/**
 * Progress bar with data preference widget.
 */
@Composable
fun ProgressBarWithDataPreference(model: ProgressBarPreferenceModel, data: String) {
    val icon = model.icon
    ProgressBarWithDataPreference(
        title = model.title,
        data = data,
        progress = model.progress,
        icon = if (icon != null) ({
            Icon(imageVector = icon, contentDescription = null)
        }) else null,
        height = model.height,
        roundedCorner = model.roundedCorner,
    )
}

@Composable
internal fun ProgressBarPreference(
    title: String,
    progress: Float,
    icon: ImageVector? = null,
    height: Float = 4f,
    roundedCorner: Boolean = true,
) {
    BaseLayout(
        title = title,
        subTitle = {
            LinearProgressBar(progress, height, roundedCorner)
        },
        icon = if (icon != null) ({
            Icon(imageVector = icon, contentDescription = null)
        }) else null,
    )
}


@Composable
internal fun ProgressBarWithDataPreference(
    title: String,
    data: String,
    progress: Float,
    icon: (@Composable () -> Unit)? = null,
    height: Float = 4f,
    roundedCorner: Boolean = true,
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(end = SettingsDimension.itemPaddingEnd),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        BaseIcon(icon, Modifier, SettingsDimension.itemPaddingStart)
        TitleWithData(
            title = title,
            data = data,
            subTitle = {
                LinearProgressBar(progress, height, roundedCorner)
            },
            modifier = Modifier
                .weight(1f)
                .padding(vertical = SettingsDimension.itemPaddingVertical),
        )
    }
}

@Composable
private fun TitleWithData(
    title: String,
    data: String,
    subTitle: @Composable () -> Unit,
    modifier: Modifier
) {
    Column(modifier) {
        Row {
            Box(modifier = Modifier.weight(1f)) {
                SettingsTitle(title)
            }
            Text(
                text = data,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
                style = MaterialTheme.typography.titleMedium,
            )
        }
        subTitle()
    }
}
Loading