Merge "Add disabled state to ActionButtons"

This commit is contained in:
Chaohui Wang
2022-09-26 07:58:57 +00:00
committed by Android (Google) Code Review
5 changed files with 103 additions and 10 deletions

View File

@@ -0,0 +1,66 @@
/*
* 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)
}
}
}

View File

@@ -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)

View File

@@ -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),
) {

View File

@@ -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"

View File

@@ -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) {