feat: Add Palette library for color extraction and enhance avatar handling in ProfileScreen
This commit is contained in:
@@ -452,6 +452,7 @@ fun MessageBubble(
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
AppleEmojiText(
|
||||
|
||||
@@ -49,6 +49,7 @@ import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.palette.graphics.Palette as AndroidPalette
|
||||
import com.rosetta.messenger.biometric.BiometricAuthManager
|
||||
import com.rosetta.messenger.biometric.BiometricAvailability
|
||||
import com.rosetta.messenger.biometric.BiometricPreferences
|
||||
@@ -738,6 +739,74 @@ private fun CollapsingProfileHeader(
|
||||
// Get actual status bar height
|
||||
val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 🎨 DOMINANT COLOR - извлекаем из аватарки для контраста текста
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
val avatars by
|
||||
avatarRepository?.getAvatars(publicKey, allDecode = false)?.collectAsState()
|
||||
?: remember { mutableStateOf(emptyList()) }
|
||||
|
||||
var avatarBitmap by remember(avatars) { mutableStateOf<android.graphics.Bitmap?>(null) }
|
||||
var dominantColor by remember { mutableStateOf<Color?>(null) }
|
||||
|
||||
Log.d(TAG, "🎨 CollapsingProfileHeader: hasAvatar=$hasAvatar, avatars.size=${avatars.size}, dominantColor=$dominantColor")
|
||||
|
||||
LaunchedEffect(avatars, publicKey) {
|
||||
Log.d(TAG, "🎨 LaunchedEffect avatars: size=${avatars.size}, publicKey=${publicKey.take(8)}")
|
||||
if (avatars.isNotEmpty()) {
|
||||
val loadedBitmap = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
||||
AvatarFileManager.base64ToBitmap(avatars.first().base64Data)
|
||||
}
|
||||
avatarBitmap = loadedBitmap
|
||||
Log.d(TAG, "🎨 Bitmap loaded: ${loadedBitmap?.width}x${loadedBitmap?.height}")
|
||||
// Извлекаем доминантный цвет из нижней части аватарки (где будет текст)
|
||||
loadedBitmap?.let { bitmap ->
|
||||
try {
|
||||
Log.d(TAG, "🎨 Extracting color from bitmap ${bitmap.width}x${bitmap.height}")
|
||||
// Берем нижнюю треть изображения для более точного определения
|
||||
val bottomThird =
|
||||
android.graphics.Bitmap.createBitmap(
|
||||
bitmap,
|
||||
0,
|
||||
(bitmap.height * 2 / 3).coerceAtLeast(1),
|
||||
bitmap.width,
|
||||
(bitmap.height / 3).coerceAtLeast(1)
|
||||
)
|
||||
Log.d(TAG, "🎨 Bottom third: ${bottomThird.width}x${bottomThird.height}")
|
||||
val palette = AndroidPalette.from(bottomThird).generate()
|
||||
Log.d(TAG, "🎨 Palette generated, dominantSwatch=${palette.dominantSwatch}, mutedSwatch=${palette.mutedSwatch}")
|
||||
// Используем доминантный цвет или muted swatch
|
||||
val swatch = palette.dominantSwatch ?: palette.mutedSwatch
|
||||
if (swatch != null) {
|
||||
val extractedColor = Color(swatch.rgb)
|
||||
Log.d(TAG, "🎨 Extracted dominant color: R=${extractedColor.red}, G=${extractedColor.green}, B=${extractedColor.blue}, isLight=${isColorLight(extractedColor)}")
|
||||
dominantColor = extractedColor
|
||||
} else {
|
||||
Log.w(TAG, "🎨 No swatch found in palette!")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to extract dominant color: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
avatarBitmap = null
|
||||
dominantColor = null
|
||||
}
|
||||
}
|
||||
|
||||
// Определяем цвет текста на основе фона - derivedStateOf для реактивности
|
||||
val textColor by remember {
|
||||
derivedStateOf {
|
||||
if (hasAvatar && dominantColor != null) {
|
||||
val isLight = isColorLight(dominantColor!!)
|
||||
Log.d(TAG, "🎨 Text color: hasAvatar=$hasAvatar, dominantColor=$dominantColor, isLight=$isLight")
|
||||
if (isLight) Color.Black else Color.White
|
||||
} else {
|
||||
if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 📐 HEADER HEIGHT - ФИКСИРОВАННАЯ! Не меняется при overscroll
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@@ -831,7 +900,7 @@ private fun CollapsingProfileHeader(
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 📝 TEXT - внизу header зоны, внутри блока
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
val textDefaultY = expandedHeight - 60.dp // Внизу header блока (чуть ниже)
|
||||
val textDefaultY = expandedHeight - 48.dp // Внизу header блока (ближе к низу)
|
||||
val textCollapsedY = statusBarHeight + COLLAPSED_HEADER_HEIGHT / 2
|
||||
|
||||
// Текст меняет позицию только при collapse, НЕ при overscroll
|
||||
@@ -989,9 +1058,7 @@ private fun CollapsingProfileHeader(
|
||||
text = name,
|
||||
fontSize = nameFontSize,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color =
|
||||
if (isColorLight(avatarColors.backgroundColor)) Color.Black
|
||||
else Color.White,
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.widthIn(max = 220.dp),
|
||||
@@ -1000,7 +1067,7 @@ private fun CollapsingProfileHeader(
|
||||
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
|
||||
Text(text = "online", fontSize = onlineFontSize, color = Color.White)
|
||||
Text(text = "online", fontSize = onlineFontSize, color = textColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user