feat: Update avatar behavior in CollapsingProfileHeader to handle presence of avatar for animations and haptic feedback
This commit is contained in:
@@ -751,17 +751,19 @@ private fun CollapsingProfileHeader(
|
|||||||
// ═══════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════
|
||||||
// 👤 AVATAR - По умолчанию КРУГЛАЯ, при overscroll расширяется до прямоугольника
|
// 👤 AVATAR - По умолчанию КРУГЛАЯ, при overscroll расширяется до прямоугольника
|
||||||
// При collapse - уменьшается и уходит вверх
|
// При collapse - уменьшается и уходит вверх
|
||||||
|
// ТОЛЬКО ЕСЛИ ЕСТЬ АВАТАРКА! Без аватарки всегда круг
|
||||||
// ═══════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════
|
||||||
val circleSize = AVATAR_SIZE_EXPANDED
|
val circleSize = AVATAR_SIZE_EXPANDED
|
||||||
// Зона аватарки = ВСЯ высота header включая статус бар
|
// Зона аватарки = ВСЯ высота header включая статус бар
|
||||||
val avatarZoneHeight = EXPANDED_HEADER_HEIGHT + statusBarHeight
|
val avatarZoneHeight = EXPANDED_HEADER_HEIGHT + statusBarHeight
|
||||||
|
|
||||||
// При overscroll расширяем до прямоугольника на всю зону (только если не collapsed)
|
// При overscroll расширяем до прямоугольника на всю зону (только если не collapsed И есть
|
||||||
|
// аватарка)
|
||||||
val avatarWidth: Dp
|
val avatarWidth: Dp
|
||||||
val avatarHeight: Dp
|
val avatarHeight: Dp
|
||||||
|
|
||||||
if (collapseProgress < 0.1f && expansionProgress > 0f) {
|
if (hasAvatar && collapseProgress < 0.1f && expansionProgress > 0f) {
|
||||||
// Overscroll: круг -> прямоугольник на всю зону ВКЛЮЧАЯ статус бар
|
// Overscroll: круг -> прямоугольник на всю зону ВКЛЮЧАЯ статус бар (ТОЛЬКО С АВАТАРКОЙ)
|
||||||
avatarWidth = androidx.compose.ui.unit.lerp(circleSize, screenWidthDp, expansionProgress)
|
avatarWidth = androidx.compose.ui.unit.lerp(circleSize, screenWidthDp, expansionProgress)
|
||||||
avatarHeight =
|
avatarHeight =
|
||||||
androidx.compose.ui.unit.lerp(circleSize, avatarZoneHeight, expansionProgress)
|
androidx.compose.ui.unit.lerp(circleSize, avatarZoneHeight, expansionProgress)
|
||||||
@@ -783,8 +785,8 @@ private fun CollapsingProfileHeader(
|
|||||||
val topAvatarY = 0.dp // От самого верха экрана при полном expansion
|
val topAvatarY = 0.dp // От самого верха экрана при полном expansion
|
||||||
|
|
||||||
val avatarY =
|
val avatarY =
|
||||||
if (collapseProgress < 0.1f && expansionProgress > 0f) {
|
if (hasAvatar && collapseProgress < 0.1f && expansionProgress > 0f) {
|
||||||
// При overscroll прижимаемся к самому верху
|
// При overscroll прижимаемся к самому верху (ТОЛЬКО С АВАТАРКОЙ)
|
||||||
androidx.compose.ui.unit.lerp(defaultCenterY, topAvatarY, expansionProgress)
|
androidx.compose.ui.unit.lerp(defaultCenterY, topAvatarY, expansionProgress)
|
||||||
} else {
|
} else {
|
||||||
// Collapse: сразу начинаем уходить вверх
|
// Collapse: сразу начинаем уходить вверх
|
||||||
@@ -795,9 +797,10 @@ private fun CollapsingProfileHeader(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Закругление: круг по умолчанию, при overscroll становится квадратом БЕЗ скругления
|
// Закругление: круг по умолчанию, при overscroll становится квадратом БЕЗ скругления (ТОЛЬКО С
|
||||||
|
// АВАТАРКОЙ)
|
||||||
val cornerRadius =
|
val cornerRadius =
|
||||||
if (collapseProgress < 0.1f && expansionProgress > 0f) {
|
if (hasAvatar && collapseProgress < 0.1f && expansionProgress > 0f) {
|
||||||
// Overscroll: круг -> квадрат без скругления
|
// Overscroll: круг -> квадрат без скругления
|
||||||
androidx.compose.ui.unit.lerp(avatarSize / 2, 0.dp, expansionProgress)
|
androidx.compose.ui.unit.lerp(avatarSize / 2, 0.dp, expansionProgress)
|
||||||
} else {
|
} else {
|
||||||
@@ -805,12 +808,12 @@ private fun CollapsingProfileHeader(
|
|||||||
avatarSize / 2
|
avatarSize / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
// Haptic feedback при достижении полного квадрата
|
// Haptic feedback при достижении полного квадрата (ТОЛЬКО С АВАТАРКОЙ)
|
||||||
val hapticFeedback = LocalHapticFeedback.current
|
val hapticFeedback = LocalHapticFeedback.current
|
||||||
var hasTriggeredHaptic by remember { mutableStateOf(false) }
|
var hasTriggeredHaptic by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(expansionProgress) {
|
LaunchedEffect(expansionProgress, hasAvatar) {
|
||||||
if (expansionProgress >= 0.95f && !hasTriggeredHaptic) {
|
if (hasAvatar && expansionProgress >= 0.95f && !hasTriggeredHaptic) {
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
hasTriggeredHaptic = true
|
hasTriggeredHaptic = true
|
||||||
} else if (expansionProgress < 0.5f) {
|
} else if (expansionProgress < 0.5f) {
|
||||||
@@ -846,36 +849,44 @@ private fun CollapsingProfileHeader(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════
|
||||||
// 👤 AVATAR - Круг по умолчанию, квадрат при overscroll
|
// 👤 AVATAR - Круг по умолчанию, квадрат при overscroll (ТОЛЬКО С АВАТАРКОЙ)
|
||||||
|
// Без аватарки - всегда круглый placeholder как в sidebar
|
||||||
// ═══════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════
|
||||||
if (avatarSize > 1.dp) {
|
if (avatarSize > 1.dp) {
|
||||||
Box(
|
if (hasAvatar) {
|
||||||
modifier =
|
// С аватаркой - расширяется до квадрата
|
||||||
Modifier.offset(x = avatarX, y = avatarY)
|
Box(
|
||||||
.size(width = avatarWidth, height = avatarHeight)
|
modifier =
|
||||||
.clip(RoundedCornerShape(cornerRadius)),
|
Modifier.offset(x = avatarX, y = avatarY)
|
||||||
contentAlignment = Alignment.Center
|
.size(width = avatarWidth, height = avatarHeight)
|
||||||
) {
|
.clip(RoundedCornerShape(cornerRadius)),
|
||||||
if (avatarRepository != null) {
|
contentAlignment = Alignment.Center
|
||||||
FullSizeAvatar(
|
) {
|
||||||
publicKey = publicKey,
|
if (avatarRepository != null) {
|
||||||
avatarRepository = avatarRepository,
|
FullSizeAvatar(
|
||||||
isDarkTheme = isDarkTheme
|
publicKey = publicKey,
|
||||||
)
|
avatarRepository = avatarRepository,
|
||||||
} else {
|
isDarkTheme = isDarkTheme
|
||||||
Box(
|
)
|
||||||
modifier =
|
}
|
||||||
Modifier.fillMaxSize().background(avatarColors.backgroundColor),
|
}
|
||||||
contentAlignment = Alignment.Center
|
} else {
|
||||||
) {
|
// Без аватарки - ВСЕГДА круглый placeholder как в sidebar
|
||||||
if (avatarFontSize > 1.sp) {
|
Box(
|
||||||
Text(
|
modifier =
|
||||||
text = getInitials(name),
|
Modifier.offset(x = avatarX, y = avatarY)
|
||||||
fontSize = avatarFontSize,
|
.size(avatarSize)
|
||||||
fontWeight = FontWeight.Bold,
|
.clip(CircleShape)
|
||||||
color = avatarColors.textColor
|
.background(avatarColors.backgroundColor),
|
||||||
)
|
contentAlignment = Alignment.Center
|
||||||
}
|
) {
|
||||||
|
if (avatarFontSize > 1.sp) {
|
||||||
|
Text(
|
||||||
|
text = getInitials(name),
|
||||||
|
fontSize = avatarFontSize,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = avatarColors.textColor
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -984,7 +995,7 @@ private fun CollapsingProfileHeader(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
|
||||||
Text(text = "online", fontSize = onlineFontSize, color = Color(0xFF4CAF50))
|
Text(text = "online", fontSize = onlineFontSize, color = Color.White)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user