Merge pull request #1224 from vector-im/feature/bma/displayNameColor
Iterate on display name and avatar color
This commit is contained in:
1
changelog.d/1224.feature
Normal file
1
changelog.d/1224.feature
Normal file
@@ -0,0 +1 @@
|
||||
Set color on display name and default avatar in the timeline.
|
||||
@@ -75,6 +75,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
import io.element.android.libraries.designsystem.colors.avatarColors
|
||||
import io.element.android.libraries.designsystem.components.EqualWidthColumn
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
@@ -327,6 +328,7 @@ private fun MessageSenderInformation(
|
||||
) {
|
||||
val avatarStrokeColor = MaterialTheme.colorScheme.background
|
||||
val avatarSize = senderAvatar.size.dp
|
||||
val avatarColors = avatarColors(senderAvatar.id)
|
||||
Box(
|
||||
modifier = modifier
|
||||
) {
|
||||
@@ -344,13 +346,13 @@ private fun MessageSenderInformation(
|
||||
}
|
||||
// Content
|
||||
Row {
|
||||
Avatar(senderAvatar)
|
||||
Avatar(senderAvatar, initialAvatarColors = avatarColors)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = sender,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
color = avatarColors.foreground,
|
||||
style = ElementTheme.typography.fontBodyMdMedium,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* 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 io.element.android.libraries.designsystem.colors
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.theme.colors.avatarColorsDark
|
||||
import io.element.android.libraries.theme.colors.avatarColorsLight
|
||||
|
||||
data class AvatarColors(
|
||||
val background: Color,
|
||||
val foreground: Color,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun avatarColors(userId: String): AvatarColors {
|
||||
val hash = userId.toHash()
|
||||
val colors = if (ElementTheme.isLightTheme) {
|
||||
avatarColorsLight[hash]
|
||||
} else {
|
||||
avatarColorsDark[hash]
|
||||
}
|
||||
return AvatarColors(
|
||||
background = colors.first,
|
||||
foreground = colors.second,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun String.toHash(): Int {
|
||||
return toList().sumOf { it.code } % avatarColorsLight.size
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.AsyncImage
|
||||
import io.element.android.libraries.designsystem.colors.AvatarColors
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.designsystem.preview.debugPlaceholderAvatar
|
||||
@@ -45,6 +46,7 @@ import timber.log.Timber
|
||||
fun Avatar(
|
||||
avatarData: AvatarData,
|
||||
modifier: Modifier = Modifier,
|
||||
initialAvatarColors: AvatarColors? = null,
|
||||
contentDescription: String? = null,
|
||||
) {
|
||||
val commonModifier = modifier
|
||||
@@ -53,6 +55,7 @@ fun Avatar(
|
||||
if (avatarData.url.isNullOrBlank()) {
|
||||
InitialsAvatar(
|
||||
avatarData = avatarData,
|
||||
avatarColors = initialAvatarColors,
|
||||
modifier = commonModifier,
|
||||
)
|
||||
} else {
|
||||
@@ -85,22 +88,23 @@ private fun ImageAvatar(
|
||||
@Composable
|
||||
private fun InitialsAvatar(
|
||||
avatarData: AvatarData,
|
||||
avatarColors: AvatarColors?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// Use temporary color for default avatar background
|
||||
// Use temporary color for default avatar background, if avatarColors is not provided
|
||||
val avatarColor = ElementTheme.colors.bgActionPrimaryDisabled
|
||||
Box(
|
||||
modifier.background(color = avatarColor),
|
||||
modifier.background(color = avatarColors?.background ?: avatarColor)
|
||||
) {
|
||||
val fontSize = avatarData.size.dp.toSp() / 2
|
||||
val originalFont = ElementTheme.typography.fontBodyMdRegular
|
||||
val originalFont = ElementTheme.typography.fontHeadingMdBold
|
||||
val ratio = fontSize.value / originalFont.fontSize.value
|
||||
val lineHeight = originalFont.lineHeight * ratio
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = avatarData.initial,
|
||||
style = originalFont.copy(fontSize = fontSize, lineHeight = lineHeight, letterSpacing = 0.sp),
|
||||
color = Color.White,
|
||||
color = avatarColors?.foreground ?: Color.White,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* 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 io.element.android.libraries.designsystem.components.avatar
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.colors.avatarColors
|
||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.theme.colors.avatarColorsLight
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun UserAvatarPreview() = ElementPreview {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
repeat(avatarColorsLight.size) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
// Note: it's OK, since the hash of "0" is 0, the hash of "1" is 1, etc.
|
||||
Avatar(anAvatarData(), initialAvatarColors = avatarColors("$it"))
|
||||
Text(text = "Color index $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* 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 io.element.android.libraries.designsystem.colors
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.theme.colors.avatarColorsDark
|
||||
import io.element.android.libraries.theme.colors.avatarColorsLight
|
||||
import org.junit.Test
|
||||
|
||||
class AvatarColorsTest {
|
||||
|
||||
@Test
|
||||
fun `ensure the size of the avatar color are equal for light and dark theme`() {
|
||||
assertThat(avatarColorsDark.size).isEqualTo(avatarColorsLight.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `compute string hash`() {
|
||||
assertThat("@alice:domain.org".toHash()).isEqualTo(6)
|
||||
assertThat("@bob:domain.org".toHash()).isEqualTo(3)
|
||||
assertThat("@charlie:domain.org".toHash()).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `compute string hash reverse`() {
|
||||
assertThat("0".toHash()).isEqualTo(0)
|
||||
assertThat("1".toHash()).isEqualTo(1)
|
||||
assertThat("2".toHash()).isEqualTo(2)
|
||||
assertThat("3".toHash()).isEqualTo(3)
|
||||
assertThat("4".toHash()).isEqualTo(4)
|
||||
assertThat("5".toHash()).isEqualTo(5)
|
||||
assertThat("6".toHash()).isEqualTo(6)
|
||||
assertThat("7".toHash()).isEqualTo(7)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* 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 io.element.android.libraries.theme.colors
|
||||
|
||||
import io.element.android.libraries.theme.compound.generated.internal.DarkDesignTokens
|
||||
|
||||
/**
|
||||
* Avatar colors are not yet part of SemanticColors, so create list here.
|
||||
* DarkDesignTokens is internal to the module.
|
||||
*/
|
||||
|
||||
val avatarColorsDark = listOf(
|
||||
DarkDesignTokens.colorBlue300 to DarkDesignTokens.colorBlue1200,
|
||||
DarkDesignTokens.colorFuchsia300 to DarkDesignTokens.colorFuchsia1200,
|
||||
DarkDesignTokens.colorGreen300 to DarkDesignTokens.colorGreen1200,
|
||||
DarkDesignTokens.colorPink300 to DarkDesignTokens.colorPink1200,
|
||||
DarkDesignTokens.colorOrange300 to DarkDesignTokens.colorOrange1200,
|
||||
DarkDesignTokens.colorCyan300 to DarkDesignTokens.colorCyan1200,
|
||||
DarkDesignTokens.colorPurple300 to DarkDesignTokens.colorPurple1200,
|
||||
DarkDesignTokens.colorLime300 to DarkDesignTokens.colorLime1200,
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* 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 io.element.android.libraries.theme.colors
|
||||
|
||||
import io.element.android.libraries.theme.compound.generated.internal.LightDesignTokens
|
||||
|
||||
/**
|
||||
* Avatar colors are not yet part of SemanticColors, so create list here.
|
||||
* LightDesignTokens is internal to the module.
|
||||
*/
|
||||
|
||||
val avatarColorsLight = listOf(
|
||||
LightDesignTokens.colorBlue300 to LightDesignTokens.colorBlue1200,
|
||||
LightDesignTokens.colorFuchsia300 to LightDesignTokens.colorFuchsia1200,
|
||||
LightDesignTokens.colorGreen300 to LightDesignTokens.colorGreen1200,
|
||||
LightDesignTokens.colorPink300 to LightDesignTokens.colorPink1200,
|
||||
LightDesignTokens.colorOrange300 to LightDesignTokens.colorOrange1200,
|
||||
LightDesignTokens.colorCyan300 to LightDesignTokens.colorCyan1200,
|
||||
LightDesignTokens.colorPurple300 to LightDesignTokens.colorPurple1200,
|
||||
LightDesignTokens.colorLime300 to LightDesignTokens.colorLime1200,
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user