feat: implement system bars style utility for consistent UI behavior
This commit is contained in:
@@ -52,7 +52,6 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -79,6 +78,7 @@ import com.rosetta.messenger.ui.chats.utils.*
|
||||
import com.rosetta.messenger.ui.components.AvatarImage
|
||||
import com.rosetta.messenger.ui.components.VerifiedBadge
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
import com.rosetta.messenger.ui.utils.SystemBarsStyleUtils
|
||||
import com.rosetta.messenger.utils.MediaUtils
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
@@ -191,22 +191,17 @@ fun ChatDetailScreen(
|
||||
var imageViewerSourceBounds by remember { mutableStateOf<ImageSourceBounds?>(null) }
|
||||
|
||||
// 🎨 Управление статус баром
|
||||
DisposableEffect(isDarkTheme, showImageViewer) {
|
||||
val insetsController = window?.let { WindowCompat.getInsetsController(it, view) }
|
||||
|
||||
DisposableEffect(isDarkTheme, showImageViewer, window, view) {
|
||||
if (showImageViewer) {
|
||||
// 📸 При просмотре фото - чёрный статус бар
|
||||
window?.statusBarColor = android.graphics.Color.BLACK
|
||||
insetsController?.isAppearanceLightStatusBars = false
|
||||
// 📸 При просмотре фото - чёрные system bars
|
||||
SystemBarsStyleUtils.applyFullscreenDark(window, view)
|
||||
} else {
|
||||
// Обычный режим - прозрачный статус бар, иконки по теме
|
||||
window?.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||
insetsController?.isAppearanceLightStatusBars = !isDarkTheme
|
||||
// Обычный режим чата
|
||||
SystemBarsStyleUtils.applyChatStatusBar(window, view, isDarkTheme)
|
||||
}
|
||||
|
||||
onDispose {
|
||||
// Восстанавливаем прозрачный статус бар при выходе
|
||||
window?.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||
SystemBarsStyleUtils.applyChatStatusBar(window, view, isDarkTheme)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1990,11 +1985,7 @@ fun ChatDetailScreen(
|
||||
},
|
||||
onClosingStart = {
|
||||
// Сразу сбрасываем status bar при начале закрытия (до анимации)
|
||||
window?.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||
window?.let { w ->
|
||||
WindowCompat.getInsetsController(w, view)
|
||||
?.isAppearanceLightStatusBars = !isDarkTheme
|
||||
}
|
||||
SystemBarsStyleUtils.applyChatStatusBar(window, view, isDarkTheme)
|
||||
},
|
||||
isDarkTheme = isDarkTheme,
|
||||
sourceBounds = imageViewerSourceBounds
|
||||
|
||||
@@ -60,6 +60,7 @@ import com.rosetta.messenger.ui.components.AppleEmojiTextField
|
||||
import com.rosetta.messenger.ui.components.KeyboardHeightProvider
|
||||
import com.rosetta.messenger.ui.components.OptimizedEmojiPicker
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
import com.rosetta.messenger.ui.utils.SystemBarsStyleUtils
|
||||
import com.yalantis.ucrop.UCrop
|
||||
import compose.icons.TablerIcons
|
||||
import compose.icons.tablericons.*
|
||||
@@ -68,7 +69,6 @@ import ja.burhanrashid52.photoeditor.PhotoEditorView
|
||||
import ja.burhanrashid52.photoeditor.SaveSettings
|
||||
import java.io.File
|
||||
import kotlin.coroutines.resume
|
||||
import androidx.core.view.WindowCompat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -170,59 +170,26 @@ fun ImageEditorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// 🎨 Плавная анимация status bar синхронно с fade
|
||||
// 🎨 System bars style
|
||||
val activity = context as? Activity
|
||||
val window = activity?.window
|
||||
|
||||
// Сохраняем оригинальные цвета один раз
|
||||
val originalStatusBarColor = remember { window?.statusBarColor ?: android.graphics.Color.WHITE }
|
||||
val originalNavigationBarColor = remember { window?.navigationBarColor ?: android.graphics.Color.WHITE }
|
||||
val insetsController = remember(window, view) { window?.let { WindowCompat.getInsetsController(it, view) } }
|
||||
val originalLightStatusBars = remember { insetsController?.isAppearanceLightStatusBars ?: true }
|
||||
val originalLightNavigationBars = remember { insetsController?.isAppearanceLightNavigationBars ?: true }
|
||||
|
||||
// ⚡ Анимация цвета status bar синхронно с fade
|
||||
LaunchedEffect(animationProgress.value) {
|
||||
if (window == null || insetsController == null) return@LaunchedEffect
|
||||
|
||||
val progress = animationProgress.value
|
||||
|
||||
// Интерполируем цвет: оригинальный (progress=0) -> черный (progress=1)
|
||||
val currentStatusColor = androidx.core.graphics.ColorUtils.blendARGB(
|
||||
originalStatusBarColor,
|
||||
android.graphics.Color.BLACK,
|
||||
progress
|
||||
)
|
||||
val currentNavColor = androidx.core.graphics.ColorUtils.blendARGB(
|
||||
originalNavigationBarColor,
|
||||
android.graphics.Color.BLACK,
|
||||
progress
|
||||
)
|
||||
|
||||
window.statusBarColor = currentStatusColor
|
||||
window.navigationBarColor = currentNavColor
|
||||
|
||||
// Иконки: светлые (false) когда progress > 0.5, иначе оригинальные
|
||||
insetsController.isAppearanceLightStatusBars = progress < 0.5f && originalLightStatusBars
|
||||
insetsController.isAppearanceLightNavigationBars = progress < 0.5f && originalLightNavigationBars
|
||||
val systemBarsState = remember(window, view) {
|
||||
SystemBarsStyleUtils.capture(window, view)
|
||||
}
|
||||
|
||||
// Восстановление при dispose
|
||||
DisposableEffect(window) {
|
||||
onDispose {
|
||||
if (window == null || insetsController == null) return@onDispose
|
||||
window.statusBarColor = originalStatusBarColor
|
||||
insetsController.isAppearanceLightStatusBars = originalLightStatusBars
|
||||
LaunchedEffect(window, view) {
|
||||
SystemBarsStyleUtils.applyFullscreenDark(window, view)
|
||||
}
|
||||
|
||||
// Navigation bar: восстанавливаем только если есть нативные кнопки
|
||||
if (com.rosetta.messenger.ui.utils.NavigationModeUtils.hasNativeNavigationBar(context)) {
|
||||
window.navigationBarColor = originalNavigationBarColor
|
||||
insetsController.isAppearanceLightNavigationBars = originalLightNavigationBars
|
||||
} else {
|
||||
insetsController.hide(androidx.core.view.WindowInsetsCompat.Type.navigationBars())
|
||||
insetsController.systemBarsBehavior =
|
||||
androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
DisposableEffect(window, view, isDarkTheme, systemBarsState) {
|
||||
onDispose {
|
||||
SystemBarsStyleUtils.restoreChatAfterFullscreen(
|
||||
window = window,
|
||||
view = view,
|
||||
context = context,
|
||||
isDarkTheme = isDarkTheme,
|
||||
state = systemBarsState
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,43 +375,47 @@ fun ImageEditorScreen(
|
||||
down.consume() // Поглощаем все touch события
|
||||
}
|
||||
}
|
||||
.graphicsLayer { alpha = animationProgress.value } // ⚡ Всё плавно fade
|
||||
.background(Color.Black)
|
||||
) {
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 📸 FULLSCREEN PHOTO - занимает ВЕСЬ экран, не реагирует на клавиатуру
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
AndroidView(
|
||||
factory = { ctx ->
|
||||
PhotoEditorView(ctx).apply {
|
||||
photoEditorView = this
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.graphicsLayer { alpha = animationProgress.value }
|
||||
) {
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 📸 FULLSCREEN PHOTO - занимает ВЕСЬ экран, не реагирует на клавиатуру
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
AndroidView(
|
||||
factory = { ctx ->
|
||||
PhotoEditorView(ctx).apply {
|
||||
photoEditorView = this
|
||||
|
||||
// Убираем отступы
|
||||
setPadding(0, 0, 0, 0)
|
||||
setBackgroundColor(android.graphics.Color.BLACK)
|
||||
|
||||
// Простой FIT_CENTER - показывает ВСЁ фото, центрирует
|
||||
source.apply {
|
||||
setImageURI(currentImageUri)
|
||||
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
adjustViewBounds = true
|
||||
// Убираем отступы
|
||||
setPadding(0, 0, 0, 0)
|
||||
}
|
||||
setBackgroundColor(android.graphics.Color.BLACK)
|
||||
|
||||
photoEditor = PhotoEditor.Builder(ctx, this)
|
||||
.setPinchTextScalable(true)
|
||||
.setClipSourceImage(true)
|
||||
.build()
|
||||
}
|
||||
},
|
||||
update = { view ->
|
||||
view.source.rotation = rotationAngle
|
||||
view.source.scaleX = if (isFlippedHorizontally) -1f else 1f
|
||||
view.source.scaleY = if (isFlippedVertically) -1f else 1f
|
||||
},
|
||||
// КРИТИЧНО: fillMaxSize без imePadding - фото НЕ сжимается при клавиатуре
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
// Простой FIT_CENTER - показывает ВСЁ фото, центрирует
|
||||
source.apply {
|
||||
setImageURI(currentImageUri)
|
||||
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
adjustViewBounds = true
|
||||
setPadding(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
photoEditor = PhotoEditor.Builder(ctx, this)
|
||||
.setPinchTextScalable(true)
|
||||
.setClipSourceImage(true)
|
||||
.build()
|
||||
}
|
||||
},
|
||||
update = { view ->
|
||||
view.source.rotation = rotationAngle
|
||||
view.source.scaleX = if (isFlippedHorizontally) -1f else 1f
|
||||
view.source.scaleY = if (isFlippedVertically) -1f else 1f
|
||||
},
|
||||
// КРИТИЧНО: fillMaxSize без imePadding - фото НЕ сжимается при клавиатуре
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 🎛️ TOP BAR - Solid black (Telegram style)
|
||||
@@ -682,14 +653,14 @@ fun ImageEditorScreen(
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
val isKeyboardOpen = WindowInsets.ime.getBottom(LocalDensity.current) > 0
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = !isKeyboardOpen && !showEmojiPicker && !coordinator.isEmojiBoxVisible,
|
||||
enter = fadeIn() + slideInVertically { it },
|
||||
exit = fadeOut() + slideOutVertically { it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = !isKeyboardOpen && !showEmojiPicker && !coordinator.isEmojiBoxVisible,
|
||||
enter = fadeIn() + slideInVertically { it },
|
||||
exit = fadeOut() + slideOutVertically { it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -779,6 +750,7 @@ fun ImageEditorScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1539,59 +1511,26 @@ fun MultiImageEditorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// 🎨 Плавная анимация status bar синхронно с fade
|
||||
// 🎨 System bars style
|
||||
val activity = context as? Activity
|
||||
val window = activity?.window
|
||||
|
||||
// Сохраняем оригинальные цвета один раз
|
||||
val originalStatusBarColor = remember { window?.statusBarColor ?: android.graphics.Color.WHITE }
|
||||
val originalNavigationBarColor = remember { window?.navigationBarColor ?: android.graphics.Color.WHITE }
|
||||
val insetsController = remember(window, view) { window?.let { WindowCompat.getInsetsController(it, view) } }
|
||||
val originalLightStatusBars = remember { insetsController?.isAppearanceLightStatusBars ?: true }
|
||||
val originalLightNavigationBars = remember { insetsController?.isAppearanceLightNavigationBars ?: true }
|
||||
|
||||
// ⚡ Анимация цвета status bar синхронно с fade
|
||||
LaunchedEffect(animationProgress.value) {
|
||||
if (window == null || insetsController == null) return@LaunchedEffect
|
||||
|
||||
val progress = animationProgress.value
|
||||
|
||||
// Интерполируем цвет: оригинальный (progress=0) -> черный (progress=1)
|
||||
val currentStatusColor = androidx.core.graphics.ColorUtils.blendARGB(
|
||||
originalStatusBarColor,
|
||||
android.graphics.Color.BLACK,
|
||||
progress
|
||||
)
|
||||
val currentNavColor = androidx.core.graphics.ColorUtils.blendARGB(
|
||||
originalNavigationBarColor,
|
||||
android.graphics.Color.BLACK,
|
||||
progress
|
||||
)
|
||||
|
||||
window.statusBarColor = currentStatusColor
|
||||
window.navigationBarColor = currentNavColor
|
||||
|
||||
// Иконки: светлые (false) когда progress > 0.5, иначе оригинальные
|
||||
insetsController.isAppearanceLightStatusBars = progress < 0.5f && originalLightStatusBars
|
||||
insetsController.isAppearanceLightNavigationBars = progress < 0.5f && originalLightNavigationBars
|
||||
val systemBarsState = remember(window, view) {
|
||||
SystemBarsStyleUtils.capture(window, view)
|
||||
}
|
||||
|
||||
// Восстановление при dispose
|
||||
DisposableEffect(window) {
|
||||
onDispose {
|
||||
if (window == null || insetsController == null) return@onDispose
|
||||
window.statusBarColor = originalStatusBarColor
|
||||
insetsController.isAppearanceLightStatusBars = originalLightStatusBars
|
||||
LaunchedEffect(window, view) {
|
||||
SystemBarsStyleUtils.applyFullscreenDark(window, view)
|
||||
}
|
||||
|
||||
// Navigation bar: восстанавливаем только если есть нативные кнопки
|
||||
if (com.rosetta.messenger.ui.utils.NavigationModeUtils.hasNativeNavigationBar(context)) {
|
||||
window.navigationBarColor = originalNavigationBarColor
|
||||
insetsController.isAppearanceLightNavigationBars = originalLightNavigationBars
|
||||
} else {
|
||||
insetsController.hide(androidx.core.view.WindowInsetsCompat.Type.navigationBars())
|
||||
insetsController.systemBarsBehavior =
|
||||
androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
DisposableEffect(window, view, isDarkTheme, systemBarsState) {
|
||||
onDispose {
|
||||
SystemBarsStyleUtils.restoreChatAfterFullscreen(
|
||||
window = window,
|
||||
view = view,
|
||||
context = context,
|
||||
isDarkTheme = isDarkTheme,
|
||||
state = systemBarsState
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1648,76 +1587,82 @@ fun MultiImageEditorScreen(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer { alpha = animationProgress.value } // ⚡ Всё плавно fade
|
||||
.background(Color.Black)
|
||||
) {
|
||||
// Pager
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
userScrollEnabled = currentTool == EditorTool.NONE
|
||||
) { page ->
|
||||
Box(
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.graphicsLayer { alpha = animationProgress.value }
|
||||
) {
|
||||
// Pager
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { ctx ->
|
||||
PhotoEditorView(ctx).apply {
|
||||
photoEditorViews[page] = this
|
||||
|
||||
setPadding(0, 0, 0, 0)
|
||||
setBackgroundColor(android.graphics.Color.BLACK)
|
||||
|
||||
// Загружаем изображение
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val inputStream = ctx.contentResolver.openInputStream(imagesWithCaptions[page].uri)
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
inputStream?.close()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
source.apply {
|
||||
setImageBitmap(bitmap)
|
||||
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
adjustViewBounds = true
|
||||
setPadding(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
val editor = PhotoEditor.Builder(ctx, this@apply)
|
||||
.setPinchTextScalable(true)
|
||||
.build()
|
||||
photoEditors[page] = editor
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Handle error
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
userScrollEnabled = currentTool == EditorTool.NONE
|
||||
) { page ->
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
update = { view ->
|
||||
val currentUri = imagesWithCaptions.getOrNull(page)?.uri
|
||||
if (currentUri != null) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val inputStream = context.contentResolver.openInputStream(currentUri)
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
inputStream?.close()
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { ctx ->
|
||||
PhotoEditorView(ctx).apply {
|
||||
photoEditorViews[page] = this
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
view.source.setImageBitmap(bitmap)
|
||||
view.source.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
setPadding(0, 0, 0, 0)
|
||||
setBackgroundColor(android.graphics.Color.BLACK)
|
||||
|
||||
// Загружаем изображение
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val inputStream =
|
||||
ctx.contentResolver.openInputStream(imagesWithCaptions[page].uri)
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
inputStream?.close()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
source.apply {
|
||||
setImageBitmap(bitmap)
|
||||
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
adjustViewBounds = true
|
||||
setPadding(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
val editor = PhotoEditor.Builder(ctx, this@apply)
|
||||
.setPinchTextScalable(true)
|
||||
.build()
|
||||
photoEditors[page] = editor
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Handle error
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
update = { view ->
|
||||
val currentUri = imagesWithCaptions.getOrNull(page)?.uri
|
||||
if (currentUri != null) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val inputStream =
|
||||
context.contentResolver.openInputStream(currentUri)
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
inputStream?.close()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
view.source.setImageBitmap(bitmap)
|
||||
view.source.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Handle error
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Handle error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Top bar
|
||||
Box(
|
||||
@@ -1789,19 +1734,19 @@ fun MultiImageEditorScreen(
|
||||
}
|
||||
|
||||
// Bottom section - без imePadding, фото не сжимается
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Black.copy(alpha = 0.7f)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Black.copy(alpha = 0.7f)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
) {
|
||||
// Color picker
|
||||
AnimatedVisibility(
|
||||
visible = showColorPicker && currentTool == EditorTool.DRAW,
|
||||
@@ -1956,7 +1901,8 @@ fun MultiImageEditorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,14 +618,22 @@ fun MediaPickerBottomSheet(
|
||||
animatedClose()
|
||||
onOpenCamera()
|
||||
},
|
||||
onItemClick = { item, position ->
|
||||
// Telegram-style: клик на фото сразу открывает редактор
|
||||
onItemClick = { item, _ ->
|
||||
// Telegram-style selection:
|
||||
// Tap toggles selection for both photos and videos.
|
||||
if (item.id in selectedItems) {
|
||||
selectedItems = selectedItems - item.id
|
||||
} else if (selectedItems.size < maxSelection) {
|
||||
selectedItems = selectedItems + item.id
|
||||
}
|
||||
},
|
||||
onItemLongClick = { item ->
|
||||
// Long press keeps quick edit for photos.
|
||||
if (!item.isVideo) {
|
||||
thumbnailPosition = position
|
||||
// Сразу открываем редактор - галерея закроется автоматически
|
||||
thumbnailPosition = null
|
||||
editingItem = item
|
||||
} else {
|
||||
// Для видео - добавляем/убираем из selection
|
||||
// Videos: keep long-press toggle behavior.
|
||||
if (item.id in selectedItems) {
|
||||
selectedItems = selectedItems - item.id
|
||||
} else if (selectedItems.size < maxSelection) {
|
||||
@@ -633,14 +641,6 @@ fun MediaPickerBottomSheet(
|
||||
}
|
||||
}
|
||||
},
|
||||
onItemLongClick = { item ->
|
||||
// Long press - снять выделение если выбрана
|
||||
if (item.id in selectedItems) {
|
||||
selectedItems = selectedItems - item.id
|
||||
} else if (selectedItems.size < maxSelection) {
|
||||
selectedItems = selectedItems + item.id
|
||||
}
|
||||
},
|
||||
isDarkTheme = isDarkTheme,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
@@ -1372,17 +1372,20 @@ private fun TelegramTextField(
|
||||
targetValue = if (hasError) errorColor else Color.Transparent,
|
||||
label = "profile_field_border_color"
|
||||
)
|
||||
val fieldModifier =
|
||||
if (hasError) {
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(containerColor)
|
||||
.border(1.dp, borderColor, RoundedCornerShape(12.dp))
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
} else {
|
||||
Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
}
|
||||
|
||||
Column {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(containerColor)
|
||||
.border(1.dp, borderColor, RoundedCornerShape(12.dp))
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
) {
|
||||
Column(modifier = fieldModifier) {
|
||||
if (isEditable && onValueChange != null) {
|
||||
BasicTextField(
|
||||
value = value,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.rosetta.messenger.ui.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
|
||||
data class SystemBarsState(
|
||||
val statusBarColor: Int,
|
||||
val navigationBarColor: Int,
|
||||
val isLightStatusBars: Boolean,
|
||||
val isLightNavigationBars: Boolean
|
||||
)
|
||||
|
||||
object SystemBarsStyleUtils {
|
||||
|
||||
fun capture(window: Window?, view: View?): SystemBarsState? {
|
||||
if (window == null || view == null) return null
|
||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||
return SystemBarsState(
|
||||
statusBarColor = window.statusBarColor,
|
||||
navigationBarColor = window.navigationBarColor,
|
||||
isLightStatusBars = insetsController.isAppearanceLightStatusBars,
|
||||
isLightNavigationBars = insetsController.isAppearanceLightNavigationBars
|
||||
)
|
||||
}
|
||||
|
||||
fun applyFullscreenDark(window: Window?, view: View?) {
|
||||
if (window == null || view == null) return
|
||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||
window.statusBarColor = Color.BLACK
|
||||
window.navigationBarColor = Color.BLACK
|
||||
insetsController.isAppearanceLightStatusBars = false
|
||||
insetsController.isAppearanceLightNavigationBars = false
|
||||
}
|
||||
|
||||
fun applyChatStatusBar(window: Window?, view: View?, isDarkTheme: Boolean) {
|
||||
if (window == null || view == null) return
|
||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
insetsController.isAppearanceLightStatusBars = !isDarkTheme
|
||||
}
|
||||
|
||||
fun restoreNavigationBar(window: Window?, view: View?, context: Context, state: SystemBarsState?) {
|
||||
if (window == null || view == null) return
|
||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||
|
||||
if (NavigationModeUtils.hasNativeNavigationBar(context)) {
|
||||
insetsController.show(WindowInsetsCompat.Type.navigationBars())
|
||||
if (state != null) {
|
||||
window.navigationBarColor = state.navigationBarColor
|
||||
insetsController.isAppearanceLightNavigationBars = state.isLightNavigationBars
|
||||
}
|
||||
} else {
|
||||
insetsController.hide(WindowInsetsCompat.Type.navigationBars())
|
||||
insetsController.systemBarsBehavior =
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
}
|
||||
|
||||
fun restoreChatAfterFullscreen(
|
||||
window: Window?,
|
||||
view: View?,
|
||||
context: Context,
|
||||
isDarkTheme: Boolean,
|
||||
state: SystemBarsState?
|
||||
) {
|
||||
applyChatStatusBar(window, view, isDarkTheme)
|
||||
restoreNavigationBar(window, view, context, state)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user