mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-1204: Improve avatar color visiblity (#270)
This commit is contained in:
parent
57210cefc5
commit
8c0c606d72
6 changed files with 132 additions and 5 deletions
|
@ -0,0 +1,39 @@
|
|||
package com.x8bit.bitwarden.ui.platform.base.util
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
|
||||
/**
|
||||
* A fractional luminance value beyond which we will consider the associated color to be light
|
||||
* enough to require a dark overlay to be used.
|
||||
*/
|
||||
private const val DARK_OVERLAY_LUMINANCE_THRESHOLD = 0.65f
|
||||
|
||||
/**
|
||||
* Returns `true` if the given [Color] would require a light color to be used in any kind of
|
||||
* overlay when high contrast is important.
|
||||
*/
|
||||
val Color.isLightOverlayRequired: Boolean
|
||||
get() = this.luminance() < DARK_OVERLAY_LUMINANCE_THRESHOLD
|
||||
|
||||
/**
|
||||
* Returns a [Color] within the current theme that can safely be overlaid on top of the given
|
||||
* [Color].
|
||||
*/
|
||||
@Composable
|
||||
fun Color.toSafeOverlayColor(): Color {
|
||||
val surfaceColor = MaterialTheme.colorScheme.surface
|
||||
val onSurfaceColor = MaterialTheme.colorScheme.onSurface
|
||||
val lightColor: Color
|
||||
val darkColor: Color
|
||||
if (surfaceColor.luminance() > onSurfaceColor.luminance()) {
|
||||
lightColor = surfaceColor
|
||||
darkColor = onSurfaceColor
|
||||
} else {
|
||||
lightColor = onSurfaceColor
|
||||
darkColor = surfaceColor
|
||||
}
|
||||
return if (this.isLightOverlayRequired) lightColor else darkColor
|
||||
}
|
|
@ -15,6 +15,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toSafeOverlayColor
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toUnscaledTextUnit
|
||||
|
||||
/**
|
||||
|
@ -48,7 +49,7 @@ fun BitwardenAccountActionItem(
|
|||
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||
fontWeight = FontWeight.W400,
|
||||
),
|
||||
color = colorResource(id = R.color.white),
|
||||
color = color.toSafeOverlayColor(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,15 +29,14 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.lerp
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.toColorInt
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.lowercaseWithCurrentLocal
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toSafeOverlayColor
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toUnscaledTextUnit
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.iconRes
|
||||
|
@ -185,7 +184,7 @@ private fun AccountSummaryItem(
|
|||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_account_initials_container),
|
||||
contentDescription = null,
|
||||
tint = Color(accountSummary.avatarColorHex.toColorInt()),
|
||||
tint = accountSummary.avatarColor,
|
||||
modifier = Modifier.size(40.dp),
|
||||
)
|
||||
|
||||
|
@ -194,7 +193,7 @@ private fun AccountSummaryItem(
|
|||
style = MaterialTheme.typography.titleMedium
|
||||
// Do not allow scaling
|
||||
.copy(fontSize = 16.dp.toUnscaledTextUnit()),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
color = accountSummary.avatarColor.toSafeOverlayColor(),
|
||||
modifier = Modifier.clearAndSetSemantics { },
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.x8bit.bitwarden.ui.platform.components.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.hexToColor
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
|
@ -21,6 +23,12 @@ data class AccountSummary(
|
|||
val status: Status,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
* The [avatarColorHex] represented as a [Color].
|
||||
*/
|
||||
val avatarColor: Color
|
||||
get() = avatarColorHex.hexToColor()
|
||||
|
||||
/**
|
||||
* Describes the status of the given account.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package com.x8bit.bitwarden.data.platform.base.util
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.isLightOverlayRequired
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toSafeOverlayColor
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class ColorExtensionsTest : BaseComposeTest() {
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `isLightOverlayRequired for a color with luminance below the light threshold should return true`() {
|
||||
assertTrue(Color.Blue.isLightOverlayRequired)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `isLightOverlayRequired for a color with luminance above the light threshold should return false`() {
|
||||
assertFalse(Color.Yellow.isLightOverlayRequired)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSafeOverlayColor for a dark color in light mode should use the surface color`() =
|
||||
runTestWithTheme(isDarkTheme = false) {
|
||||
assertEquals(
|
||||
MaterialTheme.colorScheme.surface,
|
||||
Color.Blue.toSafeOverlayColor(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSafeOverlayColor for a dark color in dark mode should use the onSurface color`() =
|
||||
runTestWithTheme(isDarkTheme = true) {
|
||||
assertEquals(
|
||||
MaterialTheme.colorScheme.onSurface,
|
||||
Color.Blue.toSafeOverlayColor(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSafeOverlayColor for a light color in light mode should use the onSurface color`() =
|
||||
runTestWithTheme(isDarkTheme = false) {
|
||||
assertEquals(
|
||||
MaterialTheme.colorScheme.onSurface,
|
||||
Color.Yellow.toSafeOverlayColor(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSafeOverlayColor for a light color in dark mode should use the surface color`() =
|
||||
runTestWithTheme(isDarkTheme = true) {
|
||||
assertEquals(
|
||||
MaterialTheme.colorScheme.surface,
|
||||
Color.Yellow.toSafeOverlayColor(),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package com.x8bit.bitwarden.ui.platform.base
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import dagger.hilt.android.testing.HiltTestApplication
|
||||
import org.junit.Rule
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -24,4 +26,21 @@ abstract class BaseComposeTest {
|
|||
init {
|
||||
ShadowLog.stream = System.out
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for testing a basic Composable function that only requires a Composable environment
|
||||
* with the [BitwardenTheme].
|
||||
*/
|
||||
protected fun runTestWithTheme(
|
||||
isDarkTheme: Boolean,
|
||||
test: @Composable () -> Unit,
|
||||
) {
|
||||
composeTestRule.setContent {
|
||||
BitwardenTheme(
|
||||
darkTheme = isDarkTheme,
|
||||
) {
|
||||
test()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue