diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/theme/FocusIndication.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/theme/FocusIndication.kt new file mode 100644 index 000000000..8160190a7 --- /dev/null +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/theme/FocusIndication.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2017 - 2026 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +@file:Suppress("PackageName") + +package ee.ria.DigiDoc.ui.theme + +import androidx.compose.foundation.IndicationNodeFactory +import androidx.compose.foundation.interaction.FocusInteraction +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.node.DelegatableNode +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.invalidateDraw +import ee.ria.DigiDoc.ui.theme.Dimensions.MSCornerRadius +import kotlinx.coroutines.launch + +data class FocusIndication( + private val color: Color, + private val alpha: Float = 1.0f, +) : IndicationNodeFactory { + override fun create(interactionSource: InteractionSource): DelegatableNode = + FocusIndicationNode(interactionSource, color.copy(alpha = alpha)) +} + +private class FocusIndicationNode( + private val interactionSource: InteractionSource, + private val color: Color, +) : Modifier.Node(), + DrawModifierNode { + private var isFocused = false + + override fun onAttach() { + coroutineScope.launch { + interactionSource.interactions.collect { interaction -> + when (interaction) { + is FocusInteraction.Focus -> isFocused = true + is FocusInteraction.Unfocus -> isFocused = false + } + invalidateDraw() + } + } + } + + override fun ContentDrawScope.draw() { + if (isFocused) { + drawRoundRect( + color = color, + size = size, + cornerRadius = CornerRadius(MSCornerRadius.toPx()), + ) + } + drawContent() + } +} diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/theme/Theme.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/theme/Theme.kt index da34de78d..1132f30f3 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/theme/Theme.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/theme/Theme.kt @@ -22,16 +22,24 @@ package ee.ria.DigiDoc.ui.theme import android.app.Activity +import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ripple.RippleAlpha +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalRippleConfiguration import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RippleConfiguration import androidx.compose.material3.darkColorScheme import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView @@ -120,15 +128,30 @@ fun RIADigiDocTheme( dynamicColor: Boolean = false, content: @Composable () -> Unit, ) { + val accessibilityFocusColor = + if (isSystemInDarkTheme()) { + LightColorScheme.primary + } else { + DarkColorScheme.primary + } + + val accessibilityFocusAlpha = + RippleAlpha( + pressedAlpha = 0.1f, + focusedAlpha = 0.4f, + draggedAlpha = 0.16f, + hoveredAlpha = 0.08f, + ) + val useDarkTheme = darkTheme ?: isSystemInDarkTheme() val colorScheme = when { dynamicColor -> { val context = LocalContext.current - if (useDarkTheme == true) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + if (useDarkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } - useDarkTheme == true -> DarkColorScheme + useDarkTheme -> DarkColorScheme else -> LightColorScheme } val view = LocalView.current @@ -147,6 +170,36 @@ fun RIADigiDocTheme( MaterialTheme( colorScheme = colorScheme, typography = getTypography(), + content = { + AccessibilityFocusProvider( + focusColor = accessibilityFocusColor, + alpha = accessibilityFocusAlpha, + content = content, + ) + }, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun AccessibilityFocusProvider( + focusColor: Color, + alpha: RippleAlpha, + content: @Composable () -> Unit, +) { + CompositionLocalProvider( + LocalIndication provides + remember(focusColor) { + FocusIndication( + color = focusColor, + alpha = alpha.focusedAlpha, + ) + }, + LocalRippleConfiguration provides + RippleConfiguration( + color = focusColor, + rippleAlpha = alpha, + ), content = content, ) }