Skip to content

Commit

Permalink
Add Backlight Settings
Browse files Browse the repository at this point in the history
  • Loading branch information
ReneeVandervelde committed Jul 21, 2024
1 parent 527e4e4 commit b6d3807
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import kimchi.Kimchi
import kimchi.logger.LogLevel
import kimchi.logger.defaultWriter
import kimchi.logger.withThreshold
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.datetime.Clock
import kotlinx.serialization.json.Json
import regolith.data.settings.AndroidSettingsModule
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
package com.inkapplications.glassconsole

import android.os.Bundle
import android.provider.Settings
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.inkapplications.glassconsole.structures.BacklightConfig
import ink.ui.render.compose.ComposeRenderer
import ink.ui.render.compose.theme.ColorVariant
import ink.ui.render.compose.theme.darken
import ink.ui.render.compose.theme.defaultTheme
import inkapplications.spondee.structure.toFloat
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds

/**
* Main screen of the application, shows the display as configured after
Expand All @@ -34,18 +50,63 @@ class MainActivity : ComponentActivity() {

setContent {
val state = viewModel.state.collectAsState()
val screenConfig = (state.value as? ScreenState.Configured)?.config?.backlight
val overrideBacklight = remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()

window.attributes = window.attributes.apply {
screenBrightness = if (overrideBacklight.value && isDimmed(screenConfig)) BRIGHTNESS_OVERRIDE_NONE
else when (screenConfig) {
null, BacklightConfig.Auto -> BRIGHTNESS_OVERRIDE_NONE
is BacklightConfig.Fixed -> screenConfig.brightness.toDecimal().toFloat()
is BacklightConfig.Off -> BRIGHTNESS_OVERRIDE_OFF
}
}

DisplayApplication.module.run {
ComposeRenderer(
theme = defaultTheme().copy(
colors = ColorVariant.Defaults.dark.copy(
background = Color.Black,
surface = ColorVariant.Defaults.dark.surface.darken(.08f),
surfaceInteraction = ColorVariant.Defaults.dark.surfaceInteraction.darken(.05f),
)
),
renderers = renderers,
).render(layoutFactory.forState(state.value))
Box(
modifier = Modifier.fillMaxSize().pointerInput(screenConfig) {
detectTapGestures {
val tapToWake = when (screenConfig) {
null, BacklightConfig.Auto -> false
is BacklightConfig.Fixed -> screenConfig.tapToWake
is BacklightConfig.Off -> screenConfig.tapToWake
}
if (tapToWake && isDimmed(screenConfig)) {
overrideBacklight.value = true
scope.launch {
delay(10.seconds)
overrideBacklight.value = false
}
}
}
}
) {
ComposeRenderer(
theme = defaultTheme().copy(
colors = ColorVariant.Defaults.dark.copy(
background = Color.Black,
surface = ColorVariant.Defaults.dark.surface.darken(.08f),
surfaceInteraction = ColorVariant.Defaults.dark.surfaceInteraction.darken(.05f),
)
),
renderers = renderers,
).render(layoutFactory.forState(state.value, overrideBacklight.value))
}
}
}
}

private fun isDimmed(screenConfig: BacklightConfig?): Boolean {
val setting = try {
Settings.System.getInt(application.contentResolver, Settings.System.SCREEN_BRIGHTNESS).toFloat() / 255f
} catch (e: Settings.SettingNotFoundException) {
1f
}
return when (screenConfig) {
null, BacklightConfig.Auto -> false
is BacklightConfig.Off -> true
is BacklightConfig.Fixed -> screenConfig.brightness.toDecimal().toFloat() < setting
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class UiLayoutFactory(
private val json: Json,
private val actionScope: CoroutineScope = CoroutineScope(Dispatchers.IO),
) {
fun forState(state: ScreenState): UiLayout {
fun forState(state: ScreenState, overrideBacklight: Boolean): UiLayout {
return when (state) {
ScreenState.Initial -> CenteredElementLayout(
body = ThrobberElement(
Expand All @@ -47,27 +47,31 @@ class UiLayoutFactory(
positioning = Positioning.Center,
),
)
is ScreenState.Configured -> when (val layout = state.config.layout) {
is LayoutType.VerticalGrid -> {
val headings = FixedGridLayout.GridItem(
span = layout.columns,
horizontalPositioning = Positioning.Center,
body = StatusIndicatorElement(
text = "Not Connected",
sentiment = Sentiment.Caution,
)
).takeIf { !state.connected }.let(::listOf).filterNotNull()
val gridItems = state.config.items.map { item ->
FixedGridLayout.GridItem(
span = item.span,
body = item.toUiElement(),
horizontalPositioning = item.position,
is ScreenState.Configured -> {
if (!overrideBacklight && state.config.backlight is BacklightConfig.Off) {
CenteredElementLayout(EmptyElement)
} else when (val layout = state.config.layout) {
is LayoutType.VerticalGrid -> {
val headings = FixedGridLayout.GridItem(
span = layout.columns,
horizontalPositioning = Positioning.Center,
body = StatusIndicatorElement(
text = "Not Connected",
sentiment = Sentiment.Caution,
)
).takeIf { !state.connected }.let(::listOf).filterNotNull()
val gridItems = state.config.items.map { item ->
FixedGridLayout.GridItem(
span = item.span,
body = item.toUiElement(),
horizontalPositioning = item.position,
)
}
FixedGridLayout(
columns = layout.columns,
items = headings + gridItems,
)
}
FixedGridLayout(
columns = layout.columns,
items = headings + gridItems,
)
}
}
is ScreenState.ShowPsk -> CenteredElementLayout(
Expand Down
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ module = "io.ktor:ktor-client-content-negotiation"
version.ref = "ink-ui"
module = "com.inkapplications.ui:structures"

[libraries.ink-spondee]
version = "1.5.0"
module = "com.inkapplications.spondee:units"

[libraries.ink-ui-render-compose]
version.ref = "ink-ui"
module = "com.inkapplications.ui:render-compose-android"
Expand Down
1 change: 1 addition & 0 deletions structures/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ kotlin {
implementation(libs.kotlinx.serialization.json)
api(libs.kotlinx.datetime)
api(libs.ink.ui.structures)
api(libs.ink.spondee)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.inkapplications.glassconsole.structures

import inkapplications.spondee.scalar.Percentage
import inkapplications.spondee.scalar.decimalPercentage
import inkapplications.spondee.structure.toFloat
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

/**
* Settings for the behavior of the backlight and UI visibility.
*/
@Serializable(with = Serializer::class)
sealed interface BacklightConfig {
/**
* Use the device's default behavior for the backlight.
*/
data object Auto: BacklightConfig

/**
* Turn off the backlight.
*
* @param tapToWake Whether to allow the user to tap the device to wake it.
*/
data class Off(
val tapToWake: Boolean = DEFAULT_TAP_RESET,
): BacklightConfig {
companion object {
const val DEFAULT_TAP_RESET = true
}
}

/**
* Set the backlight to a specified brightness.
*
* @param brightness The percentage brightness to set the backlight to.
* @param tapToWake Whether to allow the user to tap the device to wake it.
*/
data class Fixed(
val brightness: Percentage,
val tapToWake: Boolean = DEFAULT_TAP_RESET,
): BacklightConfig {
companion object {
const val DEFAULT_TAP_RESET = false
}
}
}

@Serializable
private class JsonSchema(
val behavior: String,
@Serializable(with = PercentageAsDecimalFloatSerializer::class)
val brightness: Percentage? = null,
val tapToWake: Boolean? = null,
)

private class Serializer: KSerializer<BacklightConfig> {
private val backingSerializer = JsonSchema.serializer()
override val descriptor: SerialDescriptor = backingSerializer.descriptor

override fun serialize(encoder: Encoder, value: BacklightConfig) {
val schema = when(value) {
is BacklightConfig.Auto -> JsonSchema("auto")
is BacklightConfig.Off -> JsonSchema("off", tapToWake = value.tapToWake)
is BacklightConfig.Fixed -> JsonSchema("fixed", value.brightness, value.tapToWake)
}
backingSerializer.serialize(encoder, schema)
}

override fun deserialize(decoder: Decoder): BacklightConfig {
val schema = backingSerializer.deserialize(decoder)
return when(schema.behavior) {
"auto" -> BacklightConfig.Auto
"off" -> BacklightConfig.Off(
tapToWake = schema.tapToWake ?: BacklightConfig.Off.DEFAULT_TAP_RESET
)
"fixed" -> BacklightConfig.Fixed(
schema.brightness!!,
schema.tapToWake ?: BacklightConfig.Fixed.DEFAULT_TAP_RESET
)
else -> throw IllegalArgumentException("Unknown backlight behavior: ${schema.behavior}")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.inkapplications.glassconsole.structures

import com.inkapplications.glassconsole.structures.BacklightConfig.Auto
import kotlinx.serialization.Serializable
import kotlin.time.Duration

Expand All @@ -26,4 +27,9 @@ data class DisplayConfig(
*/
@Serializable(with = SecondsDurationSerializer::class)
val expiration: Duration? = null,

/**
* Backlight configuration for the display.
*/
val backlight: BacklightConfig = Auto,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.inkapplications.glassconsole.structures

import inkapplications.spondee.scalar.Percentage
import inkapplications.spondee.scalar.decimalPercentage
import inkapplications.spondee.structure.toFloat
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

/**
* Serializes a percentage as a fractional decimal float.
*
* ie. 0.5 for 50%
*/
class PercentageAsDecimalFloatSerializer: KSerializer<Percentage> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Percentage", PrimitiveKind.FLOAT)

override fun deserialize(decoder: Decoder): Percentage {
return decoder.decodeFloat().decimalPercentage
}

override fun serialize(encoder: Encoder, value: Percentage) {
encoder.encodeFloat(value.toDecimal().toFloat())
}
}

0 comments on commit b6d3807

Please sign in to comment.