Merge "Add disabled state to ActionButtons"
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user