feat: Add BlurredAvatarBackground component and integrate it into ChatsListScreen and ProfileScreen
This commit is contained in:
@@ -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
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user