fix: add caption support to image viewer with animated display
This commit is contained in:
@@ -4,6 +4,7 @@ import android.app.Activity
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
@@ -69,6 +70,38 @@ private val COLLAPSED_HEADER_HEIGHT_OTHER = 64.dp
|
||||
private val AVATAR_SIZE_EXPANDED_OTHER = 120.dp
|
||||
private val AVATAR_SIZE_COLLAPSED_OTHER = 36.dp
|
||||
|
||||
/**
|
||||
* Вычисляет средний цвет bitmap (для случаев когда Palette не может извлечь swatch)
|
||||
* Используется для белых/чёрных/однотонных изображений
|
||||
*/
|
||||
private fun calculateAverageColor(bitmap: android.graphics.Bitmap): Color {
|
||||
var redSum = 0L
|
||||
var greenSum = 0L
|
||||
var blueSum = 0L
|
||||
|
||||
// Сэмплируем каждый 4-й пиксель для производительности
|
||||
val step = 4
|
||||
var sampledCount = 0
|
||||
|
||||
for (y in 0 until bitmap.height step step) {
|
||||
for (x in 0 until bitmap.width step step) {
|
||||
val pixel = bitmap.getPixel(x, y)
|
||||
redSum += android.graphics.Color.red(pixel)
|
||||
greenSum += android.graphics.Color.green(pixel)
|
||||
blueSum += android.graphics.Color.blue(pixel)
|
||||
sampledCount++
|
||||
}
|
||||
}
|
||||
|
||||
if (sampledCount == 0) return Color.White
|
||||
|
||||
return Color(
|
||||
red = (redSum / sampledCount) / 255f,
|
||||
green = (greenSum / sampledCount) / 255f,
|
||||
blue = (blueSum / sampledCount) / 255f
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun OtherProfileScreen(
|
||||
@@ -474,12 +507,10 @@ private fun CollapsingOtherProfileHeader(
|
||||
val nameFontSize = androidx.compose.ui.unit.lerp(24.sp, 18.sp, collapseProgress)
|
||||
val onlineFontSize = androidx.compose.ui.unit.lerp(14.sp, 13.sp, collapseProgress)
|
||||
|
||||
// Определяем цвет текста на основе фона
|
||||
val textColor by remember(hasAvatar, avatarColors) {
|
||||
derivedStateOf {
|
||||
if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White
|
||||
}
|
||||
}
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 🎨 TEXT COLOR - просто по теме: белый в тёмной, чёрный в светлой
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
|
||||
Box(modifier = Modifier.fillMaxWidth().height(headerHeight)) {
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
@@ -112,6 +112,41 @@ fun isColorLight(color: Color): Boolean {
|
||||
return luminance > 0.5f
|
||||
}
|
||||
|
||||
/**
|
||||
* Вычисляет средний цвет bitmap (для случаев когда Palette не может извлечь swatch)
|
||||
* Используется для белых/чёрных/однотонных изображений
|
||||
*/
|
||||
private fun calculateAverageColor(bitmap: android.graphics.Bitmap): Color {
|
||||
var redSum = 0L
|
||||
var greenSum = 0L
|
||||
var blueSum = 0L
|
||||
val width = bitmap.width
|
||||
val height = bitmap.height
|
||||
val pixelCount = width * height
|
||||
|
||||
// Сэмплируем каждый 4-й пиксель для производительности
|
||||
val step = 4
|
||||
var sampledCount = 0
|
||||
|
||||
for (y in 0 until height step step) {
|
||||
for (x in 0 until width step step) {
|
||||
val pixel = bitmap.getPixel(x, y)
|
||||
redSum += android.graphics.Color.red(pixel)
|
||||
greenSum += android.graphics.Color.green(pixel)
|
||||
blueSum += android.graphics.Color.blue(pixel)
|
||||
sampledCount++
|
||||
}
|
||||
}
|
||||
|
||||
if (sampledCount == 0) return Color.White
|
||||
|
||||
return Color(
|
||||
red = (redSum / sampledCount) / 255f,
|
||||
green = (greenSum / sampledCount) / 255f,
|
||||
blue = (blueSum / sampledCount) / 255f
|
||||
)
|
||||
}
|
||||
|
||||
fun getAvatarColor(name: String, isDarkTheme: Boolean): AvatarColors {
|
||||
val cacheKey = "${name}_${if (isDarkTheme) "dark" else "light"}"
|
||||
return avatarColorCache.getOrPut(cacheKey) {
|
||||
@@ -800,62 +835,9 @@ private fun CollapsingProfileHeader(
|
||||
val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 🎨 DOMINANT COLOR - извлекаем из аватарки для контраста текста
|
||||
// 🎨 TEXT 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) }
|
||||
|
||||
|
||||
LaunchedEffect(avatars, publicKey) {
|
||||
if (avatars.isNotEmpty()) {
|
||||
val loadedBitmap = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
||||
AvatarFileManager.base64ToBitmap(avatars.first().base64Data)
|
||||
}
|
||||
avatarBitmap = loadedBitmap
|
||||
// Извлекаем доминантный цвет из нижней части аватарки (где будет текст)
|
||||
loadedBitmap?.let { bitmap ->
|
||||
try {
|
||||
// Берем нижнюю треть изображения для более точного определения
|
||||
val bottomThird =
|
||||
android.graphics.Bitmap.createBitmap(
|
||||
bitmap,
|
||||
0,
|
||||
(bitmap.height * 2 / 3).coerceAtLeast(1),
|
||||
bitmap.width,
|
||||
(bitmap.height / 3).coerceAtLeast(1)
|
||||
)
|
||||
val palette = AndroidPalette.from(bottomThird).generate()
|
||||
// Используем доминантный цвет или muted swatch
|
||||
val swatch = palette.dominantSwatch ?: palette.mutedSwatch
|
||||
if (swatch != null) {
|
||||
val extractedColor = Color(swatch.rgb)
|
||||
dominantColor = extractedColor
|
||||
} else {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
avatarBitmap = null
|
||||
dominantColor = null
|
||||
}
|
||||
}
|
||||
|
||||
// Определяем цвет текста на основе фона - derivedStateOf для реактивности
|
||||
val textColor by remember {
|
||||
derivedStateOf {
|
||||
if (hasAvatar && dominantColor != null) {
|
||||
val isLight = isColorLight(dominantColor!!)
|
||||
if (isLight) Color.Black else Color.White
|
||||
} else {
|
||||
if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White
|
||||
}
|
||||
}
|
||||
}
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 📐 HEADER HEIGHT - ФИКСИРОВАННАЯ! Не меняется при overscroll
|
||||
|
||||
Reference in New Issue
Block a user