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

Commit e8908f9d authored by Chaohui Wang's avatar Chaohui Wang Committed by Android (Google) Code Review
Browse files

Merge "Add disabled state to ActionButtons"

parents 43b487a8 d493898c
Loading
Loading
Loading
Loading
+66 −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.framework.compose

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver

/**
 * A [BroadcastReceiver] which registered when on start and unregistered when on stop.
 */
@Composable
fun DisposableBroadcastReceiverAsUser(
    userId: Int,
    intentFilter: IntentFilter,
    onReceive: (Intent) -> Unit,
) {
    val broadcastReceiver = remember {
        object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                onReceive(intent)
            }
        }
    }
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                context.registerReceiverAsUser(
                    broadcastReceiver, UserHandle.of(userId), intentFilter, null, null)
            } else if (event == Lifecycle.Event.ON_STOP) {
                context.unregisterReceiver(broadcastReceiver)
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}
+16 −8
Original line number Diff line number Diff line
@@ -19,25 +19,33 @@ package com.android.settingslib.spa.framework.util
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.snapshotFlow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map

/**
 * Returns a [Flow] whose values are a list which containing the results of asynchronously applying
 * the given [transform] function to each element in the original flow's list.
 */
inline fun <T, R> Flow<List<T>>.asyncMapItem(crossinline transform: (T) -> R): Flow<List<R>> =
    map { list -> list.asyncMap(transform) }

@OptIn(ExperimentalCoroutinesApi::class)
inline fun <T, R> Flow<T>.mapState(crossinline block: (T) -> State<R>): Flow<R> =
    flatMapLatest { snapshotFlow { block(it).value } }
/**
 * Delays the flow a little bit, wait the other flow's first value.
 */
fun <T1, T2> Flow<T1>.waitFirst(otherFlow: Flow<T2>): Flow<T1> =
    combine(otherFlow.distinctUntilChangedBy {}) { value, _ -> value }

fun <T1, T2> Flow<T1>.waitFirst(flow: Flow<T2>): Flow<T1> =
    combine(flow.distinctUntilChangedBy {}) { value, _ -> value }
/**
 * Returns a [Flow] whose values are generated list by combining the most recently emitted non null
 * values by each flow.
 */
inline fun <reified T : Any> combineToList(vararg flows: Flow<T?>): Flow<List<T>> = combine(
    flows.asList(),
) { array: Array<T?> -> array.filterNotNull() }

class StateFlowBridge<T> {
    private val stateFlow = MutableStateFlow<T?>(null)
+9 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.widget.button

import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
@@ -39,6 +40,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -54,6 +56,7 @@ import com.android.settingslib.spa.framework.theme.divider
data class ActionButton(
    val text: String,
    val imageVector: ImageVector,
    val enabled: Boolean = true,
    val onClick: () -> Unit,
)

@@ -79,10 +82,15 @@ private fun RowScope.ActionButton(actionButton: ActionButton) {
        modifier = Modifier
            .weight(1f)
            .fillMaxHeight(),
        enabled = actionButton.enabled,
        // Because buttons could appear, disappear or change positions, reset the interaction source
        // to prevent highlight the wrong button.
        interactionSource = remember(actionButton) { MutableInteractionSource() },
        shape = RectangleShape,
        colors = ButtonDefaults.filledTonalButtonColors(
            containerColor = MaterialTheme.colorScheme.surface,
            containerColor = SettingsTheme.colorScheme.surface,
            contentColor = SettingsTheme.colorScheme.categoryTitle,
            disabledContainerColor = SettingsTheme.colorScheme.surface,
        ),
        contentPadding = PaddingValues(horizontal = 4.dp, vertical = 20.dp),
    ) {
+11 −0
Original line number Diff line number Diff line
@@ -17,12 +17,23 @@
package com.android.settingslib.spaprivileged.model.app

import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.UserHandle

/** The user id for a given application. */
val ApplicationInfo.userId: Int
    get() = UserHandle.getUserId(uid)

/** The [UserHandle] for a given application. */
val ApplicationInfo.userHandle: UserHandle
    get() = UserHandle.getUserHandleForUid(uid)

/** Checks whether a flag is associated with the application. */
fun ApplicationInfo.hasFlag(flag: Int): Boolean = (flags and flag) > 0

/** Checks whether the application is disabled until used. */
fun ApplicationInfo.isDisabledUntilUsed(): Boolean =
    enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED

/** Converts to the route string which used in navigation. */
fun ApplicationInfo.toRoute() = "$packageName/$userId"
+1 −1
Original line number Diff line number Diff line
@@ -55,7 +55,7 @@ object PackageManagers {
            iPackageManager.isPackageAvailable(it, userId)
        }.toSet()

    private fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
    fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
        try {
            PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
        } catch (e: PackageManager.NameNotFoundException) {