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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.toSafeOverlayColor
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.toUnscaledTextUnit
|
import com.x8bit.bitwarden.ui.platform.base.util.toUnscaledTextUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,7 +49,7 @@ fun BitwardenAccountActionItem(
|
||||||
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
fontWeight = FontWeight.W400,
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.lerp
|
import androidx.compose.ui.graphics.lerp
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.graphics.toColorInt
|
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.lowercaseWithCurrentLocal
|
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.base.util.toUnscaledTextUnit
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.iconRes
|
import com.x8bit.bitwarden.ui.vault.feature.vault.util.iconRes
|
||||||
|
@ -185,7 +184,7 @@ private fun AccountSummaryItem(
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_account_initials_container),
|
painter = painterResource(id = R.drawable.ic_account_initials_container),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = Color(accountSummary.avatarColorHex.toColorInt()),
|
tint = accountSummary.avatarColor,
|
||||||
modifier = Modifier.size(40.dp),
|
modifier = Modifier.size(40.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -194,7 +193,7 @@ private fun AccountSummaryItem(
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
// Do not allow scaling
|
// Do not allow scaling
|
||||||
.copy(fontSize = 16.dp.toUnscaledTextUnit()),
|
.copy(fontSize = 16.dp.toUnscaledTextUnit()),
|
||||||
color = MaterialTheme.colorScheme.surface,
|
color = accountSummary.avatarColor.toSafeOverlayColor(),
|
||||||
modifier = Modifier.clearAndSetSemantics { },
|
modifier = Modifier.clearAndSetSemantics { },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package com.x8bit.bitwarden.ui.platform.components.model
|
package com.x8bit.bitwarden.ui.platform.components.model
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.hexToColor
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +23,12 @@ data class AccountSummary(
|
||||||
val status: Status,
|
val status: Status,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [avatarColorHex] represented as a [Color].
|
||||||
|
*/
|
||||||
|
val avatarColor: Color
|
||||||
|
get() = avatarColorHex.hexToColor()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the status of the given account.
|
* 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
|
package com.x8bit.bitwarden.ui.platform.base
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.test.junit4.createComposeRule
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
|
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
import dagger.hilt.android.testing.HiltTestApplication
|
import dagger.hilt.android.testing.HiltTestApplication
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
@ -24,4 +26,21 @@ abstract class BaseComposeTest {
|
||||||
init {
|
init {
|
||||||
ShadowLog.stream = System.out
|
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