From 430c7d9007d4f3037589876e784b8f56a4397ab1 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Sat, 31 Jan 2026 05:16:34 +0500 Subject: [PATCH] feat: Add Palette library for color extraction and enhance avatar handling in ProfileScreen --- app/build.gradle.kts | 5 +- .../chats/components/ChatDetailComponents.kt | 1 + .../messenger/ui/settings/ProfileScreen.kt | 77 +++++++++++++++++-- gradle-warnings.txt | 6 ++ 4 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 gradle-warnings.txt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6f0a2aa..d21b6bd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt index 81ce524..d08ec20 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt @@ -452,6 +452,7 @@ fun MessageBubble( ) { Row( verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth() ) { AppleEmojiText( diff --git a/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt index cb6213f..f812aac 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt @@ -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(null) } + var dominantColor by remember { mutableStateOf(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) } } } diff --git a/gradle-warnings.txt b/gradle-warnings.txt new file mode 100644 index 0000000..3356821 --- /dev/null +++ b/gradle-warnings.txt @@ -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