Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 32 additions & 69 deletions app/src/main/java/friendly/android/ColorFromString.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,45 @@ package friendly.android

import androidx.compose.ui.graphics.Color

private const val DJB_SHIFT_OFFSET = 5
private const val BITS_PER_CHANNEL = 8
private const val BYTE_MASK = 0x16

private val darkPastelColors = listOf(
Color(19, 37, 54, 255),
Color(68, 83, 36),
Color(43, 73, 63),
Color(90, 13, 5, 255),
Color(58, 88, 84),
Color(58, 42, 8),
Color(100, 66, 108),
Color(23, 17, 80),
Color(127, 36, 126),
Color(120, 30, 32),
Color(59, 56, 100),
Color(149, 52, 53),
Color(50, 47, 94, 255),
Color(14, 11, 27),
Color(121, 32, 59),
Color(93, 42, 18),
Color(124, 59, 70),
Color(55, 62, 22, 255),
Color(54, 116, 78),
Color(100, 36, 71),
Color(44, 61, 19),
Color(34, 32, 171),
Color(63, 7, 78, 255),
private val lightPastels = listOf(
Color(255, 179, 179),
Color(255, 217, 153),
Color(255, 242, 179),
Color(204, 255, 179),
Color(179, 255, 217),
Color(179, 230, 255),
Color(204, 191, 255),
Color(242, 191, 255),
Color(255, 191, 230),
Color(230, 217, 191),
)

private val lightPastelColors = listOf(
Color(71, 104, 188),
Color(197, 214, 160),
Color(168, 203, 192),
Color(190, 119, 81, 255),
Color(189, 212, 209),
Color(232, 187, 89),
Color(220, 204, 224),
Color(220, 107, 107, 255),
Color(235, 184, 234),
Color(104, 234, 173, 255),
Color(193, 191, 220),
Color(241, 215, 215),
Color(226, 162, 224),
Color(121, 99, 193),
Color(234, 174, 192),
Color(232, 165, 135),
Color(232, 206, 211),
Color(106, 125, 177),
Color(197, 228, 209),
Color(223, 168, 198),
Color(173, 193, 10, 255),
Color(212, 211, 247),
Color(89, 165, 180),
private val darkPastels = listOf(
Color(153, 64, 64),
Color(153, 102, 51),
Color(140, 128, 51),
Color(89, 128, 64),
Color(64, 128, 102),
Color(64, 102, 153),
Color(89, 77, 140),
Color(128, 77, 140),
Color(153, 77, 115),
Color(128, 102, 77),
)

fun Color.Companion.pastelFromString(string: String, useDark: Boolean): Color {
val utf16String = string.toByteArray(Charsets.UTF_16)
val hash = utf16String.fold(0) { hash, char ->
val shifted = hash shl DJB_SHIFT_OFFSET
char + shifted - hash
val hash = string.toByteArray(Charsets.UTF_8).fold(0) { acc, byte ->
(acc * 31 + byte.toInt()) and 0x7FFFFFFF
}

val num = (hash shr (0 * BITS_PER_CHANNEL)) and BYTE_MASK

return if (useDark) darkPastelColors[num] else lightPastelColors[num]
val pastels = if (useDark) darkPastels else lightPastels
return pastels[hash % pastels.size]
}

fun Color.Companion.pastelFromLong(long: Long, useDark: Boolean): Color {
val utf16String = long.toString().toByteArray(Charsets.UTF_16)
val hash = utf16String.fold(0) { hash, char ->
val shifted = hash shl DJB_SHIFT_OFFSET
char + shifted - hash
}

val num = (hash shr (0 * BITS_PER_CHANNEL)) and BYTE_MASK
private val knuthHashConstant = 2654435761L

return if (useDark) darkPastelColors[num] else lightPastelColors[num]
fun Color.Companion.pastelFromLong(long: Long, useDark: Boolean): Color {
val pastels = if (useDark) darkPastels else lightPastels
val hash = (long * knuthHashConstant) and 0x7FFFFFF
val index = hash.toInt() % pastels.size
return pastels[index]
}
41 changes: 22 additions & 19 deletions app/src/main/java/friendly/android/FeedCard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ElevatedSuggestionChip
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.SuggestionChipDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -279,28 +281,29 @@ private fun AvatarWithInterests(
LazyRow(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(vertical = 8.dp)
.fillMaxWidth(),
) {
item { Spacer(Modifier.width(8.dp)) }

items(interests) { interest ->
Text(
text = interest.string,
modifier = Modifier
.padding(horizontal = 3.dp)
.clip(RoundedCornerShape(6.dp))
.background(
color = Color.pastelFromString(
string = interest.string,
useDark = isSystemInDarkTheme(),
),
)
.padding(4.dp),
itemsIndexed(interests) { i, interest ->
val useDark = isSystemInDarkTheme()
val color = remember(interest.string, useDark) {
Color.pastelFromString(
string = interest.string,
useDark = useDark,
)
}
Spacer(Modifier.width(8.dp))
ElevatedSuggestionChip(
onClick = {},
label = { Text(interest.string) },
colors = SuggestionChipDefaults.suggestionChipColors(
containerColor = color,
labelColor = MaterialTheme.colorScheme.onSurface,
),
)
if (i == interests.lastIndex) {
Spacer(Modifier.width(8.dp))
}
}

item { Spacer(Modifier.width(8.dp)) }
}
}
}
Expand Down
51 changes: 32 additions & 19 deletions app/src/main/java/friendly/android/ProfileScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.expandIn
import androidx.compose.animation.fadeIn
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
Expand All @@ -22,8 +21,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenu
Expand All @@ -36,6 +35,8 @@ import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.SuggestionChipDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
Expand All @@ -44,13 +45,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.Clipboard
Expand Down Expand Up @@ -417,26 +418,38 @@ fun LoadedProfileState(
Spacer(Modifier.height(16.dp))

FlowRow(
horizontalArrangement = Arrangement.spacedBy(
space = 8.dp,
alignment = Alignment.CenterHorizontally,
),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth(),
) {
state.profile.interests.raw.forEach { interest ->
Text(
text = interest.string,
modifier = Modifier
.clip(RoundedCornerShape(6.dp))
.background(
color = Color.pastelFromString(
string = interest.string,
useDark = isSystemInDarkTheme(),
),
state.profile.interests.raw.forEachIndexed { i, interest ->
val useDark = isSystemInDarkTheme()
val color = remember(interest.string, useDark) {
Color.pastelFromString(
string = interest.string,
useDark = useDark,
)
}
val label = MaterialTheme.colorScheme.onSurface
key(interest.string) {
Row {
Spacer(Modifier.width(8.dp))
SuggestionChip(
onClick = {},
label = { Text(interest.string) },
colors = SuggestionChipDefaults
.suggestionChipColors(
containerColor = color,
labelColor = label,
),
border = null,
modifier = Modifier.height(32.dp),
)
.padding(4.dp),
)
if (i == interests.size) {
Spacer(Modifier.width(8.dp))
}
}
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion app/src/main/java/friendly/android/UserAvatar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class UserAvatarStyle(
}
}

private val knuthHashConstant = 2654435761L

@Composable
fun UserAvatar(
nickname: Nickname,
Expand All @@ -66,7 +68,8 @@ fun UserAvatar(
style: UserAvatarStyle,
modifier: Modifier = Modifier,
) {
val shapeIndex = (userId.long % avatarShapes.size).toInt()
val hash = (userId.long * knuthHashConstant) and 0x7FFFFFFF
val shapeIndex = (hash % avatarShapes.size).toInt()
val shape = avatarShapes[shapeIndex].toShape()
SubcomposeAsyncImage(
model = uri,
Expand Down