feat: add animated badge for top-level requests count in ChatsListScreen

This commit is contained in:
2026-02-14 00:31:05 +05:00
parent e399fd04aa
commit a46968cfff
7 changed files with 242 additions and 83 deletions

View File

@@ -42,34 +42,63 @@ fun BoxScope.BlurredAvatarBackground(
overlayColors: List<Color>? = null,
isDarkTheme: Boolean = true
) {
// В светлой теме: если дефолтный фон (avatar) — синий как шапка chat list,
// если выбран кастомный цвет в Appearance — используем его
if (!isDarkTheme) {
val lightBgModifier = if (overlayColors != null && overlayColors.isNotEmpty()) {
if (overlayColors.size == 1) {
Modifier.matchParentSize().background(overlayColors[0])
} else {
Modifier.matchParentSize().background(
Brush.linearGradient(colors = overlayColors)
)
}
} else {
Modifier.matchParentSize().background(Color(0xFF0D8CF4))
}
Box(modifier = lightBgModifier)
// В светлой теме с дефолтным фоном (avatar, без overlay) — синий как шапка chat list
if (!isDarkTheme && (overlayColors == null || overlayColors.isEmpty())) {
Box(modifier = Modifier.matchParentSize().background(Color(0xFF0D8CF4)))
return
}
// Если выбран цвет в Appearance — просто сплошной цвет/градиент, без blur
// Если выбран цвет в Appearance — рисуем блюр аватарки + полупрозрачный overlay поверх
// (одинаково для светлой и тёмной темы, чтобы цвет совпадал с превью в Appearance)
if (overlayColors != null && overlayColors.isNotEmpty()) {
val bgModifier = if (overlayColors.size == 1) {
Modifier.matchParentSize().background(overlayColors[0])
// Загружаем блюр аватарки для подложки
val avatarsForOverlay by avatarRepository?.getAvatars(publicKey, allDecode = false)?.collectAsState()
?: remember { mutableStateOf(emptyList()) }
val avatarKeyForOverlay = remember(avatarsForOverlay) {
avatarsForOverlay.firstOrNull()?.timestamp ?: 0L
}
var blurredForOverlay by remember { mutableStateOf<Bitmap?>(null) }
LaunchedEffect(avatarKeyForOverlay) {
val currentAvatars = avatarsForOverlay
if (currentAvatars.isNotEmpty()) {
val original = withContext(Dispatchers.IO) {
AvatarFileManager.base64ToBitmap(currentAvatars.first().base64Data)
}
if (original != null) {
blurredForOverlay = withContext(Dispatchers.Default) {
val scaled = Bitmap.createScaledBitmap(original, original.width / 4, original.height / 4, true)
var result = scaled
repeat(2) { result = fastBlur(result, (blurRadius / 4).toInt().coerceAtLeast(1)) }
result
}
}
} else {
blurredForOverlay = null
}
}
// Подложка: блюр аватарки или fallback цвет
Box(modifier = Modifier.matchParentSize()) {
if (blurredForOverlay != null) {
Image(
bitmap = blurredForOverlay!!.asImageBitmap(),
contentDescription = null,
modifier = Modifier.fillMaxSize().graphicsLayer { this.alpha = 0.35f },
contentScale = ContentScale.Crop
)
}
}
// Overlay — полупрозрачный, как в Appearance preview
val overlayAlpha = if (blurredForOverlay != null) 0.55f else 0.85f
val overlayMod = if (overlayColors.size == 1) {
Modifier.matchParentSize().background(overlayColors[0].copy(alpha = overlayAlpha))
} else {
Modifier.matchParentSize().background(
Brush.linearGradient(colors = overlayColors)
Brush.linearGradient(colors = overlayColors.map { it.copy(alpha = overlayAlpha) })
)
}
Box(modifier = bgModifier)
Box(modifier = overlayMod)
return
}