[go: nahoru, domu]

blob: 6c6eb62176e9d0137ed798a49b55248295980b5d [file] [log] [blame]
/*
* Copyright 2023 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 androidx.wear.compose.foundation
import android.content.ContentResolver
import android.content.Context
import android.database.ContentObserver
import android.net.Uri
import android.os.Looper
import android.provider.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.core.os.HandlerCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import java.util.concurrent.atomic.AtomicReference
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.stateIn
/**
* CompositionLocal for global reduce-motion setting, which turns off animations and
* screen movements. To use, call LocalReduceMotion.current.enabled(), which returns a Boolean.
*/
@get:ExperimentalWearFoundationApi
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
@ExperimentalWearFoundationApi
val LocalReduceMotion: ProvidableCompositionLocal<ReduceMotion> = staticCompositionLocalOf {
ReduceMotion {
val context = LocalContext.current.applicationContext
val flow = getReduceMotionFlowFor(context)
flow.collectAsStateWithLifecycle().value
}
}
/**
* CompositionLocal containing the background scrim color of [BasicSwipeToDismissBox].
*
* Defaults to [Color.Black] if not explicitly set.
*/
val LocalSwipeToDismissBackgroundScrimColor: ProvidableCompositionLocal<Color> =
compositionLocalOf { Color.Black }
/**
* CompositionLocal containing the content scrim color of [BasicSwipeToDismissBox].
*
* Defaults to [Color.Black] if not explicitly set.
*/
val LocalSwipeToDismissContentScrimColor: ProvidableCompositionLocal<Color> =
compositionLocalOf { Color.Black }
/**
* ReduceMotion provides a means for callers to determine whether an app should turn off
* animations and screen movement.
*/
@ExperimentalWearFoundationApi
fun interface ReduceMotion {
@Composable
fun enabled(): Boolean
}
private val reduceMotionCache = AtomicReference<StateFlow<Boolean>>()
// Callers of this function should pass an application context. Passing an activity context might
// result in activity leaks.
private fun getReduceMotionFlowFor(applicationContext: Context): StateFlow<Boolean> {
val resolver = applicationContext.contentResolver
val reduceMotionUri = Settings.Global.getUriFor(REDUCE_MOTION)
return reduceMotionCache.updateAndGet {
it ?: callbackFlow {
val contentObserver =
object : ContentObserver(HandlerCompat.createAsync(Looper.getMainLooper())) {
override fun deliverSelfNotifications(): Boolean {
// Returning true to receive change notification so that
// the flow sends new value after it is initialized.
return true
}
override fun onChange(selfChange: Boolean, uri: Uri?) {
super.onChange(selfChange, uri)
trySend(getReducedMotionSettingValue(resolver))
}
}
resolver.registerContentObserver(reduceMotionUri, false, contentObserver)
// Force send value when flow is initialized
resolver.notifyChange(reduceMotionUri, contentObserver)
awaitClose {
resolver.unregisterContentObserver(contentObserver)
}
}.stateIn(
MainScope(),
SharingStarted.WhileSubscribed(5000),
getReducedMotionSettingValue(resolver)
)
}
}
private fun getReducedMotionSettingValue(resolver: ContentResolver): Boolean {
return Settings.Global.getInt(
resolver,
REDUCE_MOTION,
REDUCE_MOTION_DEFAULT
) == 1
}
// See framework's Settings.Global.Wearable#REDUCE_MOTION.
private const val REDUCE_MOTION = "reduce_motion"
private const val REDUCE_MOTION_DEFAULT = 0