feat: add animated badge for top-level requests count in ChatsListScreen
This commit is contained in:
@@ -27,6 +27,13 @@ object BackgroundBlurPresets {
|
||||
label = "Avatar"
|
||||
)
|
||||
|
||||
/** Вариант "none" — стандартные цвета шапки без blur и без overlay */
|
||||
val noneDefault = BackgroundBlurOption(
|
||||
id = "none",
|
||||
colors = emptyList(),
|
||||
label = "None"
|
||||
)
|
||||
|
||||
/** Сплошные цвета */
|
||||
private val solidColors = listOf(
|
||||
BackgroundBlurOption("solid_blue", listOf(Color(0xFF0D8CF4)), "Blue"),
|
||||
@@ -54,19 +61,19 @@ object BackgroundBlurPresets {
|
||||
/** Все варианты в порядке отображения: сначала сплошные, потом градиенты */
|
||||
val all: List<BackgroundBlurOption> = solidColors + gradients
|
||||
|
||||
/** Все варианты включая "Avatar" (default) */
|
||||
val allWithDefault: List<BackgroundBlurOption> = listOf(avatarDefault) + all
|
||||
/** Все варианты включая "Avatar" и "None" */
|
||||
val allWithDefault: List<BackgroundBlurOption> = listOf(noneDefault, avatarDefault) + all
|
||||
|
||||
/**
|
||||
* Найти вариант по id. Возвращает [avatarDefault] если не найден.
|
||||
*/
|
||||
fun findById(id: String): BackgroundBlurOption {
|
||||
return allWithDefault.find { it.id == id } ?: avatarDefault
|
||||
return allWithDefault.find { it.id == id } ?: noneDefault
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить список цветов для overlay по id.
|
||||
* Возвращает null если id == "avatar" (значит используется blur аватарки без overlay).
|
||||
* Возвращает null если id == "avatar" или "none" (без overlay).
|
||||
*/
|
||||
fun getOverlayColors(id: String): List<Color>? {
|
||||
val option = findById(id)
|
||||
|
||||
@@ -252,7 +252,14 @@ private fun ProfileBlurPreview(
|
||||
// ═══════════════════════════════════════════════════
|
||||
// LAYER 2: Color/gradient overlay (или fallback)
|
||||
// ═══════════════════════════════════════════════════
|
||||
if (overlayColors != null && overlayColors.isNotEmpty()) {
|
||||
if (selectedId == "none") {
|
||||
// None — стандартный цвет шапки
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFF0D8CF4))
|
||||
)
|
||||
} else if (overlayColors != null && overlayColors.isNotEmpty()) {
|
||||
val overlayMod = if (overlayColors.size == 1) {
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
@@ -475,6 +482,24 @@ private fun ColorCircleItem(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
when {
|
||||
option.id == "none" -> {
|
||||
// None — стандартные цвета, перечеркнутый круг
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFF0D8CF4)
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = TablerIcons.X,
|
||||
contentDescription = "None",
|
||||
tint = Color.White.copy(alpha = 0.9f),
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
option.id == "avatar" -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -512,8 +537,8 @@ private fun ColorCircleItem(
|
||||
}
|
||||
}
|
||||
|
||||
// Галочка с затемнённым фоном (не для avatar — там уже иконка)
|
||||
if (isSelected && option.id != "avatar") {
|
||||
// Галочка с затемнённым фоном (не для avatar/none — там уже иконка)
|
||||
if (isSelected && option.id != "avatar" && option.id != "none") {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
||||
@@ -577,19 +577,21 @@ fun OtherProfileScreen(
|
||||
)
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// ✉️ WRITE MESSAGE BUTTON
|
||||
// ✉️ WRITE MESSAGE + 📞 CALL BUTTONS
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Box(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
// Write Message
|
||||
Button(
|
||||
onClick = { onWriteMessage(user) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.height(48.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
@@ -609,12 +611,43 @@ fun OtherProfileScreen(
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Write Message",
|
||||
fontSize = 16.sp,
|
||||
text = "Message",
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
|
||||
// Call
|
||||
Button(
|
||||
onClick = { /* TODO: call action */ },
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(48.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFE8E8ED),
|
||||
contentColor = if (isDarkTheme) Color.White else Color.Black
|
||||
),
|
||||
elevation = ButtonDefaults.buttonElevation(
|
||||
defaultElevation = 0.dp,
|
||||
pressedElevation = 0.dp
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = TablerIcons.Phone,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = if (isDarkTheme) Color.White else PrimaryBlue
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Call",
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = if (isDarkTheme) Color.White else Color.Black
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
@@ -662,7 +695,8 @@ fun OtherProfileScreen(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxWidth().heightIn(min = sharedPagerMinHeight),
|
||||
beyondBoundsPageCount = 0,
|
||||
verticalAlignment = Alignment.Top
|
||||
verticalAlignment = Alignment.Top,
|
||||
userScrollEnabled = false
|
||||
) { page ->
|
||||
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.TopStart) {
|
||||
OtherProfileSharedTabContent(
|
||||
|
||||
@@ -568,6 +568,14 @@ fun ProfileScreen(
|
||||
hasTriggeredSnapBackHaptic = true
|
||||
}
|
||||
}
|
||||
|
||||
// Коллапс при удалении аватара — сразу сворачиваем
|
||||
LaunchedEffect(hasAvatar) {
|
||||
if (!hasAvatar) {
|
||||
isPulledDown = false
|
||||
overscrollOffset = 0f
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// NESTED SCROLL — overscroll (pull-down аватарка) + header snap
|
||||
@@ -605,8 +613,8 @@ fun ProfileScreen(
|
||||
available: Offset,
|
||||
source: NestedScrollSource
|
||||
): Offset {
|
||||
// Overscroll при свайпе вниз от верха (когда LazyColumn в начале)
|
||||
if (available.y > 0 && !listState.canScrollBackward) {
|
||||
// Overscroll при свайпе вниз от верха (только если есть аватар)
|
||||
if (available.y > 0 && !listState.canScrollBackward && hasAvatar) {
|
||||
isDragging = true
|
||||
val resistance = if (isPulledDown) 1f else 0.5f
|
||||
val delta = available.y * resistance
|
||||
@@ -1126,15 +1134,24 @@ private fun CollapsingProfileHeader(
|
||||
// и естественно перекрывает его. Без мерцания.
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
Box(modifier = Modifier.matchParentSize()) {
|
||||
BlurredAvatarBackground(
|
||||
publicKey = publicKey,
|
||||
avatarRepository = avatarRepository,
|
||||
fallbackColor = avatarColors.backgroundColor,
|
||||
blurRadius = 20f,
|
||||
alpha = 0.9f,
|
||||
overlayColors = BackgroundBlurPresets.getOverlayColors(backgroundBlurColorId),
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
if (backgroundBlurColorId == "none") {
|
||||
// None — стандартный цвет шапки без blur
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFF0D8CF4))
|
||||
)
|
||||
} else {
|
||||
BlurredAvatarBackground(
|
||||
publicKey = publicKey,
|
||||
avatarRepository = avatarRepository,
|
||||
fallbackColor = avatarColors.backgroundColor,
|
||||
blurRadius = 20f,
|
||||
alpha = 0.9f,
|
||||
overlayColors = BackgroundBlurPresets.getOverlayColors(backgroundBlurColorId),
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user