feat: Add appearance customization screen with background blur options

- Introduced `BackgroundBlurOption` data class and `BackgroundBlurPresets` object for managing background blur options.
- Created `AppearanceScreen` composable for selecting background colors and gradients, including a live preview of the selected option.
- Updated `OtherProfileScreen` and `ProfileScreen` to accept and utilize `backgroundBlurColorId` for consistent background blur across profiles.
- Enhanced `CollapsingOtherProfileHeader` and `CollapsingProfileHeader` to apply selected background blur options.
This commit is contained in:
2026-02-07 08:10:26 +05:00
parent eef254a9cf
commit 71181f49d9
9 changed files with 1864 additions and 661 deletions

View File

@@ -0,0 +1,75 @@
package com.rosetta.messenger.ui.settings
import androidx.compose.ui.graphics.Color
/**
* Модель для варианта фона blur в профиле
* @param id Уникальный идентификатор (сохраняется в PreferencesManager)
* @param colors Список цветов. Если 1 — сплошной цвет, если 2+ — градиент
* @param label Человекочитаемое название
*/
data class BackgroundBlurOption(
val id: String,
val colors: List<Color>,
val label: String
)
/**
* Предустановленные варианты цвета/градиента для background blur в профиле.
* Референс: сетка цветных кружков как в desktop-версии.
*/
object BackgroundBlurPresets {
/** Вариант "по умолчанию" — используется blur аватарки без цветного overlay */
val avatarDefault = BackgroundBlurOption(
id = "avatar",
colors = emptyList(),
label = "Avatar"
)
/** Сплошные цвета */
private val solidColors = listOf(
BackgroundBlurOption("solid_blue", listOf(Color(0xFF2979FF)), "Blue"),
BackgroundBlurOption("solid_green", listOf(Color(0xFF4CAF50)), "Green"),
BackgroundBlurOption("solid_orange", listOf(Color(0xFFFF9800)), "Orange"),
BackgroundBlurOption("solid_red", listOf(Color(0xFFE53935)), "Red"),
BackgroundBlurOption("solid_purple", listOf(Color(0xFF7C4DFF)), "Purple"),
BackgroundBlurOption("solid_teal", listOf(Color(0xFF009688)), "Teal"),
BackgroundBlurOption("solid_pink", listOf(Color(0xFFE91E63)), "Pink"),
BackgroundBlurOption("solid_grey", listOf(Color(0xFF78909C)), "Grey"),
)
/** Градиенты (по 2 цвета, как на референсе) */
private val gradients = listOf(
BackgroundBlurOption("grad_blue_cyan", listOf(Color(0xFF2979FF), Color(0xFF00BCD4)), "Blue|Cyan"),
BackgroundBlurOption("grad_green_lime", listOf(Color(0xFF4CAF50), Color(0xFFCDDC39)), "Green|Lime"),
BackgroundBlurOption("grad_orange_yellow", listOf(Color(0xFFFF9800), Color(0xFFFFEB3B)), "Orange|Yellow"),
BackgroundBlurOption("grad_red_pink", listOf(Color(0xFFE53935), Color(0xFFFF4081)), "Red|Pink"),
BackgroundBlurOption("grad_purple_blue", listOf(Color(0xFF7C4DFF), Color(0xFF536DFE)), "Purple|Blue"),
BackgroundBlurOption("grad_teal_green", listOf(Color(0xFF009688), Color(0xFF69F0AE)), "Teal|Green"),
BackgroundBlurOption("grad_pink_magenta", listOf(Color(0xFFE91E63), Color(0xFFCE93D8)), "Pink|Magenta"),
BackgroundBlurOption("grad_mono", listOf(Color(0xFF546E7A), Color(0xFFB0BEC5)), "Mono"),
)
/** Все варианты в порядке отображения: сначала сплошные, потом градиенты */
val all: List<BackgroundBlurOption> = solidColors + gradients
/** Все варианты включая "Avatar" (default) */
val allWithDefault: List<BackgroundBlurOption> = listOf(avatarDefault) + all
/**
* Найти вариант по id. Возвращает [avatarDefault] если не найден.
*/
fun findById(id: String): BackgroundBlurOption {
return allWithDefault.find { it.id == id } ?: avatarDefault
}
/**
* Получить список цветов для overlay по id.
* Возвращает null если id == "avatar" (значит используется blur аватарки без overlay).
*/
fun getOverlayColors(id: String): List<Color>? {
val option = findById(id)
return if (option.colors.isEmpty()) null else option.colors
}
}

View File

@@ -0,0 +1,573 @@
package com.rosetta.messenger.ui.settings
import android.graphics.Bitmap
import androidx.activity.compose.BackHandler
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.rosetta.messenger.repository.AvatarRepository
import com.rosetta.messenger.utils.AvatarFileManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import compose.icons.tablericons.Sun
import compose.icons.tablericons.Moon
/**
* Экран кастомизации внешнего вида.
* Позволяет выбрать цвет/градиент для background blur в профиле.
* Preview повторяет avatar block из профиля — реальный blur + аватарка.
*/
@Composable
fun AppearanceScreen(
isDarkTheme: Boolean,
currentBlurColorId: String,
onBack: () -> Unit,
onBlurColorChange: (String) -> Unit,
onToggleTheme: () -> Unit = {},
accountPublicKey: String = "",
accountName: String = "",
avatarRepository: AvatarRepository? = null
) {
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
val textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
var selectedId by remember { mutableStateOf(currentBlurColorId) }
BackHandler { onBack() }
Column(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor)
) {
// ═══════════════════════════════════════════════════════════
// CONTENT
// ═══════════════════════════════════════════════════════════
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
// ═══════════════════════════════════════════════════════
// LIVE PREVIEW — реальный avatar block из профиля
// Покрывает status bar, кнопки overlay поверх
// ═══════════════════════════════════════════════════════
Box {
ProfileBlurPreview(
selectedId = selectedId,
isDarkTheme = isDarkTheme,
publicKey = accountPublicKey,
displayName = accountName,
avatarRepository = avatarRepository
)
// Overlay: кнопка назад (слева) и смена темы (справа)
Row(
modifier = Modifier
.fillMaxWidth()
.statusBarsPadding()
.padding(horizontal = 4.dp, vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = onBack) {
Icon(
imageVector = TablerIcons.ArrowLeft,
contentDescription = "Back",
tint = Color.White
)
}
IconButton(onClick = onToggleTheme) {
Icon(
imageVector = if (isDarkTheme) TablerIcons.Sun else TablerIcons.Moon,
contentDescription = "Toggle theme",
tint = Color.White
)
}
}
}
Spacer(modifier = Modifier.height(24.dp))
// ═══════════════════════════════════════════════════════
// SECTION TITLE
// ═══════════════════════════════════════════════════════
Text(
text = "BACKGROUND COLOR",
fontSize = 13.sp,
fontWeight = FontWeight.Medium,
color = secondaryTextColor,
letterSpacing = 0.5.sp,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
// ═══════════════════════════════════════════════════════
// COLOR GRID
// ═══════════════════════════════════════════════════════
ColorSelectionGrid(
selectedId = selectedId,
isDarkTheme = isDarkTheme,
onSelect = { id ->
selectedId = id
onBlurColorChange(id)
}
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Choose a color or gradient overlay for the blurred header background in your profile.",
fontSize = 13.sp,
color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
lineHeight = 18.sp
)
Spacer(modifier = Modifier.height(32.dp))
}
}
}
// ═══════════════════════════════════════════════════════════════════════
// 🎨 LIVE PREVIEW — повторяет avatar block из ProfileScreen
// Реальный blur аватарки + overlay + круглая аватарка + имя
// ═══════════════════════════════════════════════════════════════════════
@Composable
private fun ProfileBlurPreview(
selectedId: String,
isDarkTheme: Boolean,
publicKey: String,
displayName: String,
avatarRepository: AvatarRepository?
) {
val option = BackgroundBlurPresets.findById(selectedId)
val overlayColors = BackgroundBlurPresets.getOverlayColors(selectedId)
val avatarColors = getAvatarColor(publicKey, isDarkTheme)
// Загрузка аватарки
val avatars by avatarRepository?.getAvatars(publicKey, allDecode = false)?.collectAsState()
?: remember { mutableStateOf(emptyList()) }
val avatarKey = remember(avatars) { avatars.firstOrNull()?.timestamp ?: 0L }
var avatarBitmap by remember { mutableStateOf<Bitmap?>(null) }
var blurredBitmap by remember { mutableStateOf<Bitmap?>(null) }
LaunchedEffect(avatarKey) {
val current = avatars
if (current.isNotEmpty()) {
val decoded = withContext(Dispatchers.IO) {
AvatarFileManager.base64ToBitmap(current.first().base64Data)
}
if (decoded != null) {
avatarBitmap = decoded
// Blur для фонового изображения
blurredBitmap = withContext(Dispatchers.Default) {
val scaled = Bitmap.createScaledBitmap(
decoded,
decoded.width / 4,
decoded.height / 4,
true
)
var result = scaled
repeat(3) {
result = fastBlur(result, 6)
}
result
}
}
} else {
avatarBitmap = null
blurredBitmap = null
}
}
// Анимированный label
val labelText = if (option.colors.isEmpty()) "Avatar Blur" else option.label
// Preview — повторяет profile header, покрывает status bar
val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
Box(
modifier = Modifier
.fillMaxWidth()
.height(280.dp + statusBarHeight)
) {
// ═══════════════════════════════════════════════════
// LAYER 1: Blurred avatar background (как в профиле)
// ═══════════════════════════════════════════════════
if (blurredBitmap != null) {
Image(
bitmap = blurredBitmap!!.asImageBitmap(),
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.graphicsLayer { alpha = 0.35f },
contentScale = ContentScale.Crop
)
}
// ═══════════════════════════════════════════════════
// LAYER 2: Color/gradient overlay (или fallback)
// ═══════════════════════════════════════════════════
if (overlayColors != null && overlayColors.isNotEmpty()) {
val overlayMod = if (overlayColors.size == 1) {
Modifier
.fillMaxSize()
.background(overlayColors[0].copy(alpha = if (blurredBitmap != null) 0.55f else 0.85f))
} else {
Modifier
.fillMaxSize()
.background(
Brush.linearGradient(
colors = overlayColors.map {
it.copy(alpha = if (blurredBitmap != null) 0.55f else 0.85f)
}
)
)
}
Box(modifier = overlayMod)
} else if (blurredBitmap != null) {
// Стандартный затемняющий overlay (как в профиле)
Box(
modifier = Modifier
.fillMaxSize()
.background(avatarColors.backgroundColor.copy(alpha = 0.3f))
)
} else {
// Нет аватарки и нет overlay — fallback цвет
Box(
modifier = Modifier
.fillMaxSize()
.background(
if (isDarkTheme) Color(0xFF2A2A2E) else Color(0xFFD8D8DC)
)
)
}
// ═══════════════════════════════════════════════════
// LAYER 3: Тонкий нижний градиент-затемнение
// ═══════════════════════════════════════════════════
Box(
modifier = Modifier
.fillMaxWidth()
.height(80.dp)
.align(Alignment.BottomCenter)
.background(
Brush.verticalGradient(
colors = listOf(
Color.Transparent,
Color.Black.copy(alpha = 0.35f)
)
)
)
)
// ═══════════════════════════════════════════════════
// LAYER 4: Avatar circle + Name + subtitle
// Повторяет layout из CollapsingProfileHeader
// ═══════════════════════════════════════════════════
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = statusBarHeight)
.padding(bottom = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// Круглая аватарка с shadow — 120dp как в ProfileScreen
Box(
modifier = Modifier
.size(120.dp)
.shadow(12.dp, CircleShape)
.clip(CircleShape)
.background(avatarColors.backgroundColor),
contentAlignment = Alignment.Center
) {
if (avatarBitmap != null) {
Image(
bitmap = avatarBitmap!!.asImageBitmap(),
contentDescription = "Avatar",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
} else {
// Placeholder с инициалами (40sp как в ProfileScreen)
Text(
text = getInitials(displayName.ifBlank { publicKey.take(6) }),
fontSize = 40.sp,
fontWeight = FontWeight.Bold,
color = avatarColors.textColor
)
}
}
Spacer(modifier = Modifier.height(14.dp))
// Имя пользователя
Text(
text = displayName.ifBlank { publicKey.take(10) + "..." },
fontSize = 24.sp,
fontWeight = FontWeight.SemiBold,
color = Color.White,
textAlign = TextAlign.Center,
maxLines = 1
)
Spacer(modifier = Modifier.height(2.dp))
// Label текущего пресета — с иконкой стрелки для градиентов
val labelParts = labelText.split("|")
if (labelParts.size == 2) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
text = labelParts[0],
fontSize = 14.sp,
color = Color.White.copy(alpha = 0.7f)
)
Icon(
imageVector = TablerIcons.ArrowNarrowRight,
contentDescription = null,
tint = Color.White.copy(alpha = 0.5f),
modifier = Modifier
.padding(horizontal = 4.dp)
.size(16.dp)
)
Text(
text = labelParts[1],
fontSize = 14.sp,
color = Color.White.copy(alpha = 0.7f)
)
}
} else {
Text(
text = labelText,
fontSize = 14.sp,
color = Color.White.copy(alpha = 0.7f),
textAlign = TextAlign.Center
)
}
}
}
}
// ═══════════════════════════════════════════════════════════════════
// 🎨 COLOR GRID — сетка выбора цветов (8 в ряду)
// ═══════════════════════════════════════════════════════════════════
@Composable
private fun ColorSelectionGrid(
selectedId: String,
isDarkTheme: Boolean,
onSelect: (String) -> Unit
) {
val allOptions = BackgroundBlurPresets.allWithDefault
val columns = 8
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp)
) {
allOptions.chunked(columns).forEach { rowItems ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 6.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
rowItems.forEach { option ->
ColorCircleItem(
option = option,
isSelected = option.id == selectedId,
isDarkTheme = isDarkTheme,
onClick = { onSelect(option.id) }
)
}
repeat(columns - rowItems.size) {
Spacer(modifier = Modifier.size(40.dp))
}
}
}
}
}
@Composable
private fun ColorCircleItem(
option: BackgroundBlurOption,
isSelected: Boolean,
isDarkTheme: Boolean,
onClick: () -> Unit
) {
val scale by animateFloatAsState(
targetValue = if (isSelected) 1.15f else 1.0f,
animationSpec = tween(200),
label = "scale"
)
val borderColor by animateColorAsState(
targetValue = if (isSelected) {
if (isDarkTheme) Color.White else Color(0xFF222222)
} else {
Color.Transparent
},
animationSpec = tween(200),
label = "border"
)
Box(
modifier = Modifier
.size(40.dp)
.scale(scale)
.clip(CircleShape)
.border(
width = if (isSelected) 2.5.dp else 0.5.dp,
color = if (isSelected) borderColor else Color.White.copy(alpha = 0.12f),
shape = CircleShape
)
.clickable(onClick = onClick),
contentAlignment = Alignment.Center
) {
when {
option.id == "avatar" -> {
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.linearGradient(
colors = listOf(Color(0xFF3A3A3C), Color(0xFF8E8E93))
)
),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = TablerIcons.CircleOff,
contentDescription = "Default",
tint = Color.White.copy(alpha = 0.9f),
modifier = Modifier.size(18.dp)
)
}
}
option.colors.size == 1 -> {
Box(
modifier = Modifier
.fillMaxSize()
.background(option.colors[0])
)
}
option.colors.size >= 2 -> {
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.linearGradient(colors = option.colors)
)
)
}
}
// Галочка с затемнённым фоном
if (isSelected) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.25f)),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = TablerIcons.Check,
contentDescription = "Selected",
tint = Color.White,
modifier = Modifier.size(18.dp)
)
}
}
}
}
/**
* Быстрый box blur (для preview, идентично BlurredAvatarBackground)
*/
private fun fastBlur(source: Bitmap, radius: Int): Bitmap {
if (radius < 1) return source
val w = source.width
val h = source.height
val bitmap = source.copy(source.config, true)
val pixels = IntArray(w * h)
bitmap.getPixels(pixels, 0, w, 0, 0, w, h)
for (y in 0 until h) blurRow(pixels, y, w, radius)
for (x in 0 until w) blurColumn(pixels, x, w, h, radius)
bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
return bitmap
}
private fun blurRow(pixels: IntArray, y: Int, w: Int, radius: Int) {
var sR = 0; var sG = 0; var sB = 0; var sA = 0
val dv = radius * 2 + 1; val off = y * w
for (i in -radius..radius) {
val x = i.coerceIn(0, w - 1); val p = pixels[off + x]
sA += (p shr 24) and 0xff; sR += (p shr 16) and 0xff
sG += (p shr 8) and 0xff; sB += p and 0xff
}
for (x in 0 until w) {
pixels[off + x] = ((sA / dv) shl 24) or ((sR / dv) shl 16) or ((sG / dv) shl 8) or (sB / dv)
val xL = (x - radius).coerceIn(0, w - 1); val xR = (x + radius + 1).coerceIn(0, w - 1)
val lp = pixels[off + xL]; val rp = pixels[off + xR]
sA += ((rp shr 24) and 0xff) - ((lp shr 24) and 0xff)
sR += ((rp shr 16) and 0xff) - ((lp shr 16) and 0xff)
sG += ((rp shr 8) and 0xff) - ((lp shr 8) and 0xff)
sB += (rp and 0xff) - (lp and 0xff)
}
}
private fun blurColumn(pixels: IntArray, x: Int, w: Int, h: Int, radius: Int) {
var sR = 0; var sG = 0; var sB = 0; var sA = 0
val dv = radius * 2 + 1
for (i in -radius..radius) {
val y = i.coerceIn(0, h - 1); val p = pixels[y * w + x]
sA += (p shr 24) and 0xff; sR += (p shr 16) and 0xff
sG += (p shr 8) and 0xff; sB += p and 0xff
}
for (y in 0 until h) {
val off = y * w + x
pixels[off] = ((sA / dv) shl 24) or ((sR / dv) shl 16) or ((sG / dv) shl 8) or (sB / dv)
val yT = (y - radius).coerceIn(0, h - 1); val yB = (y + radius + 1).coerceIn(0, h - 1)
val tp = pixels[yT * w + x]; val bp = pixels[yB * w + x]
sA += ((bp shr 24) and 0xff) - ((tp shr 24) and 0xff)
sR += ((bp shr 16) and 0xff) - ((tp shr 16) and 0xff)
sG += ((bp shr 8) and 0xff) - ((tp shr 8) and 0xff)
sB += (bp and 0xff) - (tp and 0xff)
}
}

View File

@@ -110,7 +110,8 @@ fun OtherProfileScreen(
user: SearchUser,
isDarkTheme: Boolean,
onBack: () -> Unit,
avatarRepository: AvatarRepository? = null
avatarRepository: AvatarRepository? = null,
backgroundBlurColorId: String = "avatar"
) {
var isBlocked by remember { mutableStateOf(false) }
var showAvatarMenu by remember { mutableStateOf(false) }
@@ -457,7 +458,8 @@ fun OtherProfileScreen(
} catch (e: Exception) {
}
}
}
},
backgroundBlurColorId = backgroundBlurColorId
)
}
}
@@ -483,7 +485,8 @@ private fun CollapsingOtherProfileHeader(
isBlocked: Boolean,
onBlockToggle: () -> Unit,
avatarRepository: AvatarRepository? = null,
onClearChat: () -> Unit
onClearChat: () -> Unit,
backgroundBlurColorId: String = "avatar"
) {
val density = LocalDensity.current
@@ -521,7 +524,8 @@ private fun CollapsingOtherProfileHeader(
avatarRepository = avatarRepository,
fallbackColor = avatarColors.backgroundColor,
blurRadius = 25f,
alpha = 0.3f
alpha = 0.3f,
overlayColors = com.rosetta.messenger.ui.settings.BackgroundBlurPresets.getOverlayColors(backgroundBlurColorId)
)
// ═══════════════════════════════════════════════════════════

View File

@@ -189,13 +189,15 @@ fun ProfileScreen(
onSaveProfile: (name: String, username: String) -> Unit,
onLogout: () -> Unit,
onNavigateToTheme: () -> Unit = {},
onNavigateToAppearance: () -> Unit = {},
onNavigateToSafety: () -> Unit = {},
onNavigateToLogs: () -> Unit = {},
onNavigateToCrashLogs: () -> Unit = {},
onNavigateToBiometric: () -> Unit = {},
viewModel: ProfileViewModel = androidx.lifecycle.viewmodel.compose.viewModel(),
avatarRepository: AvatarRepository? = null,
dialogDao: com.rosetta.messenger.database.DialogDao? = null
dialogDao: com.rosetta.messenger.database.DialogDao? = null,
backgroundBlurColorId: String = "avatar"
) {
val context = LocalContext.current
val activity = context as? FragmentActivity
@@ -633,6 +635,14 @@ fun ProfileScreen(
showDivider = true
)
TelegramSettingsItem(
icon = TablerIcons.Brush,
title = "Appearance",
onClick = onNavigateToAppearance,
isDarkTheme = isDarkTheme,
showDivider = true
)
TelegramSettingsItem(
icon = TablerIcons.Lock,
title = "Safety",
@@ -716,7 +726,8 @@ fun ProfileScreen(
}
},
hasAvatar = hasAvatar,
avatarRepository = avatarRepository
avatarRepository = avatarRepository,
backgroundBlurColorId = backgroundBlurColorId
)
}
@@ -757,7 +768,8 @@ private fun CollapsingProfileHeader(
onSetPhotoClick: () -> Unit,
onDeletePhotoClick: () -> Unit,
hasAvatar: Boolean,
avatarRepository: AvatarRepository?
avatarRepository: AvatarRepository?,
backgroundBlurColorId: String = "avatar"
) {
@Suppress("UNUSED_VARIABLE")
val density = LocalDensity.current
@@ -808,7 +820,8 @@ private fun CollapsingProfileHeader(
avatarRepository = avatarRepository,
fallbackColor = avatarColors.backgroundColor,
blurRadius = 25f,
alpha = 0.3f
alpha = 0.3f,
overlayColors = BackgroundBlurPresets.getOverlayColors(backgroundBlurColorId)
)
// ═══════════════════════════════════════════════════════════