feat: Add BlurredAvatarBackground component and integrate it into ChatsListScreen and ProfileScreen
This commit is contained in:
@@ -90,6 +90,9 @@ dependencies {
|
||||
implementation("io.coil-kt:coil-compose:2.5.0")
|
||||
implementation("io.coil-kt:coil-gif:2.5.0") // For animated WebP/GIF support
|
||||
|
||||
// uCrop for image cropping
|
||||
implementation("com.github.yalantis:ucrop:2.2.8")
|
||||
|
||||
// Blurhash for image placeholders
|
||||
implementation("com.vanniktech:blurhash:0.1.0")
|
||||
|
||||
|
||||
@@ -51,6 +51,12 @@
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_color"
|
||||
android:resource="@color/primary_blue" />
|
||||
|
||||
<!-- UCrop Activity for image cropping -->
|
||||
<activity
|
||||
android:name="com.yalantis.ucrop.UCropActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.RosettaAndroid.UCrop" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.rosetta.messenger.network.ProtocolManager
|
||||
import com.rosetta.messenger.network.ProtocolState
|
||||
import com.rosetta.messenger.ui.components.AppleEmojiText
|
||||
import com.rosetta.messenger.ui.components.AvatarImage
|
||||
import com.rosetta.messenger.ui.components.BlurredAvatarBackground
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
@@ -385,12 +386,25 @@ fun ChatsListScreen(
|
||||
)
|
||||
val headerColor = avatarColors.backgroundColor
|
||||
|
||||
// Header с размытым фоном аватарки
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.background(
|
||||
color = headerColor
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 🎨 BLURRED AVATAR BACKGROUND (на всю область header)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
BlurredAvatarBackground(
|
||||
publicKey = accountPublicKey,
|
||||
avatarRepository = avatarRepository,
|
||||
fallbackColor = headerColor,
|
||||
blurRadius = 40f,
|
||||
alpha = 0.6f
|
||||
)
|
||||
|
||||
// Content поверх фона
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.statusBarsPadding()
|
||||
.padding(
|
||||
top = 16.dp,
|
||||
@@ -399,7 +413,6 @@ fun ChatsListScreen(
|
||||
bottom = 20.dp
|
||||
)
|
||||
) {
|
||||
Column {
|
||||
// Avatar - используем AvatarImage
|
||||
Box(
|
||||
modifier =
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
package com.rosetta.messenger.ui.components
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.rosetta.messenger.repository.AvatarRepository
|
||||
import com.rosetta.messenger.utils.AvatarFileManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Компонент для отображения размытого фона аватарки
|
||||
* Используется в профиле и сайдбаре
|
||||
* ВАЖНО: Должен вызываться внутри BoxScope чтобы matchParentSize() работал
|
||||
*
|
||||
* @param publicKey Публичный ключ пользователя
|
||||
* @param avatarRepository Репозиторий аватаров
|
||||
* @param fallbackColor Цвет фона если нет аватарки
|
||||
* @param blurRadius Радиус размытия (в пикселях) - применяется при обработке
|
||||
* @param alpha Прозрачность (0.0 - 1.0)
|
||||
*/
|
||||
@Composable
|
||||
fun BoxScope.BlurredAvatarBackground(
|
||||
publicKey: String,
|
||||
avatarRepository: AvatarRepository?,
|
||||
fallbackColor: Color,
|
||||
blurRadius: Float = 25f,
|
||||
alpha: Float = 0.3f
|
||||
) {
|
||||
// Получаем аватары из репозитория
|
||||
val avatars by avatarRepository?.getAvatars(publicKey, allDecode = false)?.collectAsState()
|
||||
?: remember { mutableStateOf(emptyList()) }
|
||||
|
||||
// Состояние для bitmap и размытого bitmap
|
||||
var originalBitmap by remember(avatars) { mutableStateOf<Bitmap?>(null) }
|
||||
var blurredBitmap by remember(avatars) { mutableStateOf<Bitmap?>(null) }
|
||||
|
||||
// Декодируем и размываем аватар
|
||||
LaunchedEffect(avatars) {
|
||||
if (avatars.isNotEmpty()) {
|
||||
originalBitmap = withContext(Dispatchers.IO) {
|
||||
AvatarFileManager.base64ToBitmap(avatars.first().base64Data)
|
||||
}
|
||||
|
||||
// Размываем bitmap (уменьшаем для производительности, затем применяем blur)
|
||||
originalBitmap?.let { bitmap ->
|
||||
blurredBitmap = withContext(Dispatchers.Default) {
|
||||
// Уменьшаем размер для быстрого blur
|
||||
val scaledBitmap = Bitmap.createScaledBitmap(
|
||||
bitmap,
|
||||
bitmap.width / 4,
|
||||
bitmap.height / 4,
|
||||
true
|
||||
)
|
||||
// Применяем blur несколько раз для более гладкого эффекта
|
||||
var result = scaledBitmap
|
||||
repeat(3) {
|
||||
result = fastBlur(result, (blurRadius / 4).toInt().coerceAtLeast(1))
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
} else {
|
||||
originalBitmap = null
|
||||
blurredBitmap = null
|
||||
}
|
||||
}
|
||||
|
||||
// Используем matchParentSize() чтобы занимать только размер родителя
|
||||
Box(modifier = Modifier.matchParentSize()) {
|
||||
if (blurredBitmap != null) {
|
||||
// Показываем размытое изображение
|
||||
Image(
|
||||
bitmap = blurredBitmap!!.asImageBitmap(),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer {
|
||||
this.alpha = alpha
|
||||
},
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
|
||||
// Дополнительный overlay для затемнения
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(fallbackColor.copy(alpha = 0.3f))
|
||||
)
|
||||
} else {
|
||||
// Fallback: цветной фон
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(fallbackColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Быстрое размытие по Гауссу (Box Blur - упрощенная версия)
|
||||
* Основано на Stack Blur Algorithm от Mario Klingemann
|
||||
*/
|
||||
private fun fastBlur(source: Bitmap, radius: Int): Bitmap {
|
||||
if (radius < 1) return source
|
||||
|
||||
val w = source.width
|
||||
val h = source.height
|
||||
|
||||
val bitmap = source.copy(source.config, true)
|
||||
|
||||
val pixels = IntArray(w * h)
|
||||
bitmap.getPixels(pixels, 0, w, 0, 0, w, h)
|
||||
|
||||
// Применяем горизонтальное размытие
|
||||
for (y in 0 until h) {
|
||||
blurRow(pixels, y, w, h, radius)
|
||||
}
|
||||
|
||||
// Применяем вертикальное размытие
|
||||
for (x in 0 until w) {
|
||||
blurColumn(pixels, x, w, h, radius)
|
||||
}
|
||||
|
||||
bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
private fun blurRow(pixels: IntArray, y: Int, w: Int, h: Int, radius: Int) {
|
||||
var sumR = 0
|
||||
var sumG = 0
|
||||
var sumB = 0
|
||||
var sumA = 0
|
||||
|
||||
val dv = radius * 2 + 1
|
||||
val offset = y * w
|
||||
|
||||
// Инициализация суммы
|
||||
for (i in -radius..radius) {
|
||||
val x = i.coerceIn(0, w - 1)
|
||||
val pixel = pixels[offset + x]
|
||||
sumA += (pixel shr 24) and 0xff
|
||||
sumR += (pixel shr 16) and 0xff
|
||||
sumG += (pixel shr 8) and 0xff
|
||||
sumB += pixel and 0xff
|
||||
}
|
||||
|
||||
// Применяем blur
|
||||
for (x in 0 until w) {
|
||||
pixels[offset + x] = ((sumA / dv) shl 24) or
|
||||
((sumR / dv) shl 16) or
|
||||
((sumG / dv) shl 8) or
|
||||
(sumB / dv)
|
||||
|
||||
// Обновляем сумму для следующего пикселя
|
||||
val xLeft = (x - radius).coerceIn(0, w - 1)
|
||||
val xRight = (x + radius + 1).coerceIn(0, w - 1)
|
||||
|
||||
val leftPixel = pixels[offset + xLeft]
|
||||
val rightPixel = pixels[offset + xRight]
|
||||
|
||||
sumA += ((rightPixel shr 24) and 0xff) - ((leftPixel shr 24) and 0xff)
|
||||
sumR += ((rightPixel shr 16) and 0xff) - ((leftPixel shr 16) and 0xff)
|
||||
sumG += ((rightPixel shr 8) and 0xff) - ((leftPixel shr 8) and 0xff)
|
||||
sumB += (rightPixel and 0xff) - (leftPixel and 0xff)
|
||||
}
|
||||
}
|
||||
|
||||
private fun blurColumn(pixels: IntArray, x: Int, w: Int, h: Int, radius: Int) {
|
||||
var sumR = 0
|
||||
var sumG = 0
|
||||
var sumB = 0
|
||||
var sumA = 0
|
||||
|
||||
val dv = radius * 2 + 1
|
||||
|
||||
// Инициализация суммы
|
||||
for (i in -radius..radius) {
|
||||
val y = i.coerceIn(0, h - 1)
|
||||
val pixel = pixels[y * w + x]
|
||||
sumA += (pixel shr 24) and 0xff
|
||||
sumR += (pixel shr 16) and 0xff
|
||||
sumG += (pixel shr 8) and 0xff
|
||||
sumB += pixel and 0xff
|
||||
}
|
||||
|
||||
// Применяем blur
|
||||
for (y in 0 until h) {
|
||||
val offset = y * w + x
|
||||
pixels[offset] = ((sumA / dv) shl 24) or
|
||||
((sumR / dv) shl 16) or
|
||||
((sumG / dv) shl 8) or
|
||||
(sumB / dv)
|
||||
|
||||
// Обновляем сумму для следующего пикселя
|
||||
val yTop = (y - radius).coerceIn(0, h - 1)
|
||||
val yBottom = (y + radius + 1).coerceIn(0, h - 1)
|
||||
|
||||
val topPixel = pixels[yTop * w + x]
|
||||
val bottomPixel = pixels[yBottom * w + x]
|
||||
|
||||
sumA += ((bottomPixel shr 24) and 0xff) - ((topPixel shr 24) and 0xff)
|
||||
sumR += ((bottomPixel shr 16) and 0xff) - ((topPixel shr 16) and 0xff)
|
||||
sumG += ((bottomPixel shr 8) and 0xff) - ((topPixel shr 8) and 0xff)
|
||||
sumB += (bottomPixel and 0xff) - (topPixel and 0xff)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
107
app/src/main/java/com/rosetta/messenger/utils/ImageCropHelper.kt
Normal file
107
app/src/main/java/com/rosetta/messenger/utils/ImageCropHelper.kt
Normal file
@@ -0,0 +1,107 @@
|
||||
package com.rosetta.messenger.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.core.content.FileProvider
|
||||
import com.yalantis.ucrop.UCrop
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Помощник для работы с обрезкой изображений через uCrop
|
||||
*/
|
||||
object ImageCropHelper {
|
||||
|
||||
private const val CROP_IMAGE_FILE_NAME = "cropped_avatar.png"
|
||||
|
||||
/**
|
||||
* Создает Intent для запуска uCrop
|
||||
* @param context Контекст
|
||||
* @param sourceUri URI исходного изображения
|
||||
* @param isDarkTheme Темная тема
|
||||
* @return Intent для запуска uCrop Activity
|
||||
*/
|
||||
fun createCropIntent(
|
||||
context: Context,
|
||||
sourceUri: Uri,
|
||||
isDarkTheme: Boolean
|
||||
): Intent {
|
||||
// Создаем файл для результата crop
|
||||
val destinationFile = File(context.cacheDir, CROP_IMAGE_FILE_NAME)
|
||||
val destinationUri = Uri.fromFile(destinationFile)
|
||||
|
||||
// Настройки uCrop
|
||||
val options = UCrop.Options().apply {
|
||||
// Круглый overlay для аватара
|
||||
setCircleDimmedLayer(true)
|
||||
|
||||
// Показываем сетку
|
||||
setShowCropGrid(true)
|
||||
setShowCropFrame(true)
|
||||
|
||||
// Цвета в зависимости от темы
|
||||
if (isDarkTheme) {
|
||||
setToolbarColor(Color.parseColor("#1A1A1A"))
|
||||
setStatusBarColor(Color.parseColor("#1A1A1A"))
|
||||
setActiveControlsWidgetColor(Color.parseColor("#0A84FF"))
|
||||
setToolbarWidgetColor(Color.WHITE)
|
||||
setRootViewBackgroundColor(Color.parseColor("#1A1A1A"))
|
||||
setDimmedLayerColor(Color.parseColor("#CC000000"))
|
||||
} else {
|
||||
setToolbarColor(Color.WHITE)
|
||||
setStatusBarColor(Color.WHITE)
|
||||
setActiveControlsWidgetColor(Color.parseColor("#007AFF"))
|
||||
setToolbarWidgetColor(Color.BLACK)
|
||||
setRootViewBackgroundColor(Color.WHITE)
|
||||
setDimmedLayerColor(Color.parseColor("#99000000"))
|
||||
}
|
||||
|
||||
// Скрываем кнопку поворота по желанию (можно оставить)
|
||||
setFreeStyleCropEnabled(false)
|
||||
|
||||
// Качество сжатия
|
||||
setCompressionFormat(Bitmap.CompressFormat.PNG)
|
||||
setCompressionQuality(100)
|
||||
|
||||
// Заголовок
|
||||
setToolbarTitle("Crop Avatar")
|
||||
|
||||
// Скрываем bottom controls если нужно
|
||||
setHideBottomControls(false)
|
||||
}
|
||||
|
||||
return UCrop.of(sourceUri, destinationUri)
|
||||
.withAspectRatio(1f, 1f) // Квадратный crop для аватара
|
||||
.withMaxResultSize(512, 512) // Максимальный размер
|
||||
.withOptions(options)
|
||||
.getIntent(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает результат crop из ActivityResult
|
||||
* @param result Результат Activity
|
||||
* @return URI обрезанного изображения или null при ошибке/отмене
|
||||
*/
|
||||
fun getCroppedImageUri(result: ActivityResult): Uri? {
|
||||
return if (result.resultCode == Activity.RESULT_OK && result.data != null) {
|
||||
UCrop.getOutput(result.data!!)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает ошибку crop если есть
|
||||
*/
|
||||
fun getCropError(result: ActivityResult): Throwable? {
|
||||
return if (result.data != null) {
|
||||
UCrop.getError(result.data!!)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,15 @@
|
||||
<item name="android:navigationBarColor">@color/splash_background</item>
|
||||
</style>
|
||||
|
||||
<!-- Theme for UCrop Activity (requires AppCompat) -->
|
||||
<style name="Theme.RosettaAndroid.UCrop" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:windowBackground">@color/splash_background</item>
|
||||
<item name="android:statusBarColor">@color/splash_background</item>
|
||||
<item name="android:navigationBarColor">@color/splash_background</item>
|
||||
<item name="colorPrimary">@color/primary_blue</item>
|
||||
<item name="colorPrimaryDark">@color/primary_blue</item>
|
||||
<item name="colorAccent">@color/primary_blue</item>
|
||||
</style>
|
||||
|
||||
<color name="splash_background">#1B1B1B</color>
|
||||
</resources>
|
||||
|
||||
@@ -10,6 +10,7 @@ dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user