feat: Add BlurredAvatarBackground component and integrate it into ChatsListScreen and ProfileScreen

This commit is contained in:
k1ngsterr1
2026-01-24 20:18:27 +05:00
parent 23e1d72ac0
commit dc548a3c7a
8 changed files with 420 additions and 29 deletions

View File

@@ -1,5 +1,6 @@
package com.rosetta.messenger.ui.settings
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
@@ -38,6 +39,7 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import com.rosetta.messenger.utils.ImageCropHelper
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -53,6 +55,7 @@ import com.rosetta.messenger.biometric.BiometricPreferences
import com.rosetta.messenger.repository.AvatarRepository
import com.rosetta.messenger.utils.AvatarFileManager
import com.rosetta.messenger.ui.components.AvatarImage
import com.rosetta.messenger.ui.components.BlurredAvatarBackground
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
@@ -166,50 +169,50 @@ fun ProfileScreen(
// Состояние меню аватара для установки фото профиля
var showAvatarMenu by remember { mutableStateOf(false) }
// Image picker launcher для выбора аватара
val imagePickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent()
) { uri: Uri? ->
Log.d(TAG, "🖼️ Image picker result: uri=$uri")
uri?.let {
// URI выбранного изображения (до crop)
var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
// Launcher для обрезки изображения (uCrop)
val cropLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
Log.d(TAG, "✂️ Crop result: resultCode=${result.resultCode}")
val croppedUri = ImageCropHelper.getCroppedImageUri(result)
val error = ImageCropHelper.getCropError(result)
if (croppedUri != null) {
Log.d(TAG, "✅ Cropped image URI: $croppedUri")
scope.launch {
try {
Log.d(TAG, "📁 Reading image from URI: $uri")
// Читаем файл изображения
val inputStream = context.contentResolver.openInputStream(uri)
// Читаем обрезанное изображение
val inputStream = context.contentResolver.openInputStream(croppedUri)
val imageBytes = inputStream?.readBytes()
inputStream?.close()
Log.d(TAG, "📊 Image bytes read: ${imageBytes?.size ?: 0} bytes")
Log.d(TAG, "📊 Cropped image bytes: ${imageBytes?.size ?: 0} bytes")
if (imageBytes != null) {
Log.d(TAG, "🔄 Converting to PNG Base64...")
// Конвертируем в PNG Base64 (кросс-платформенная совместимость)
Log.d(TAG, "🔄 Converting cropped image to PNG Base64...")
val base64Png = withContext(Dispatchers.IO) {
AvatarFileManager.imagePrepareForNetworkTransfer(context, imageBytes)
}
Log.d(TAG, "✅ Converted to Base64: ${base64Png.length} chars")
Log.d(TAG, "🔐 Avatar repository available: ${avatarRepository != null}")
Log.d(TAG, "👤 Current public key: ${accountPublicKey.take(16)}...")
// Сохраняем аватар через репозиторий
Log.d(TAG, "💾 Calling avatarRepository.changeMyAvatar()...")
avatarRepository?.changeMyAvatar(base64Png)
Log.d(TAG, "🎉 Avatar update completed")
// Показываем успешное сообщение
android.widget.Toast.makeText(
context,
"Avatar updated successfully",
android.widget.Toast.LENGTH_SHORT
).show()
} else {
Log.e(TAG, "❌ Image bytes are null")
}
} catch (e: Exception) {
Log.e(TAG, "❌ Failed to upload avatar", e)
Log.e(TAG, "❌ Failed to process cropped avatar", e)
android.widget.Toast.makeText(
context,
"Failed to update avatar: ${e.message}",
@@ -217,6 +220,27 @@ fun ProfileScreen(
).show()
}
}
} else if (error != null) {
Log.e(TAG, "❌ Crop error", error)
android.widget.Toast.makeText(
context,
"Failed to crop image: ${error.message}",
android.widget.Toast.LENGTH_LONG
).show()
} else {
Log.w(TAG, "⚠️ Crop cancelled")
}
}
// Image picker launcher - после выбора открываем crop
val imagePickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent()
) { uri: Uri? ->
Log.d(TAG, "🖼️ Image picker result: uri=$uri")
uri?.let {
// Запускаем uCrop для обрезки
val cropIntent = ImageCropHelper.createCropIntent(context, it, isDarkTheme)
cropLauncher.launch(cropIntent)
} ?: Log.w(TAG, "⚠️ URI is null, image picker cancelled")
}
@@ -535,10 +559,18 @@ private fun CollapsingProfileHeader(
modifier = Modifier
.fillMaxWidth()
.height(headerHeight)
.drawBehind {
drawRect(avatarColors.backgroundColor)
}
) {
// ═══════════════════════════════════════════════════════════
// 🎨 BLURRED AVATAR BACKGROUND (вместо цвета)
// ═══════════════════════════════════════════════════════════
BlurredAvatarBackground(
publicKey = publicKey,
avatarRepository = avatarRepository,
fallbackColor = avatarColors.backgroundColor,
blurRadius = 25f,
alpha = 0.3f
)
// ═══════════════════════════════════════════════════════════
// 🔙 BACK BUTTON
// ═══════════════════════════════════════════════════════════