feat: Add Palette library for color extraction and enhance avatar handling in ProfileScreen

This commit is contained in:
k1ngsterr1
2026-01-31 05:16:34 +05:00
parent 5fdd30b0ae
commit 430c7d9007
4 changed files with 83 additions and 6 deletions

View File

@@ -82,7 +82,7 @@ dependencies {
// Icons extended
implementation("androidx.compose.material:material-icons-extended:1.5.4")
// Tabler Icons for Compose
implementation("br.com.devsrsouza.compose.icons:tabler-icons:1.1.0")
@@ -102,6 +102,9 @@ dependencies {
// Blurhash for image placeholders
implementation("com.vanniktech:blurhash:0.1.0")
// Palette for extracting colors from images
implementation("androidx.palette:palette-ktx:1.0.0")
// Crypto libraries for key generation
implementation("org.bitcoinj:bitcoinj-core:0.16.2")
implementation("org.bouncycastle:bcprov-jdk15to18:1.77")

View File

@@ -452,6 +452,7 @@ fun MessageBubble(
) {
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
AppleEmojiText(

View File

@@ -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)
}
}
}

6
gradle-warnings.txt Normal file
View File

@@ -0,0 +1,6 @@
> Configure project :app
The StartParameter.isConfigurationCacheRequested property has been deprecated. This is scheduled to be removed in Gradle 10.0. Please use 'configurationCache.requested' property on 'BuildFeatures' service instead. Consult the upgrading guide for further information: https://docs.gradle.org/8.14.3/userguide/upgrading_version_8.html#deprecated_startparameter_is_configuration_cache_requested
The org.gradle.api.plugins.Convention type has been deprecated. This is scheduled to be removed in Gradle 9.0. Consult the upgrading guide for further information: https://docs.gradle.org/8.14.3/userguide/upgrading_version_8.html#deprecated_access_to_conventions
[Incubating] Problems report is available at: file:///Users/ruslanmakhmatov/Desktop/Work/rosette-app/rosetta-android/build/reports/problems/problems-report.html