feat: add in-app camera component for photo capture
This commit is contained in:
@@ -75,6 +75,7 @@ import com.rosetta.messenger.utils.MediaUtils
|
|||||||
import com.rosetta.messenger.ui.chats.components.ImageEditorScreen
|
import com.rosetta.messenger.ui.chats.components.ImageEditorScreen
|
||||||
import com.rosetta.messenger.ui.chats.components.MultiImageEditorScreen
|
import com.rosetta.messenger.ui.chats.components.MultiImageEditorScreen
|
||||||
import com.rosetta.messenger.ui.chats.components.ImageWithCaption
|
import com.rosetta.messenger.ui.chats.components.ImageWithCaption
|
||||||
|
import com.rosetta.messenger.ui.chats.components.InAppCameraScreen
|
||||||
import app.rosette.android.ui.keyboard.KeyboardTransitionCoordinator
|
import app.rosette.android.ui.keyboard.KeyboardTransitionCoordinator
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
@@ -220,7 +221,10 @@ fun ChatDetailScreen(
|
|||||||
// 📷 Состояние для flow камеры: фото → редактор с caption → отправка
|
// 📷 Состояние для flow камеры: фото → редактор с caption → отправка
|
||||||
var pendingCameraPhotoUri by remember { mutableStateOf<Uri?>(null) } // Фото для редактирования
|
var pendingCameraPhotoUri by remember { mutableStateOf<Uri?>(null) } // Фото для редактирования
|
||||||
|
|
||||||
// <EFBFBD> Состояние для multi-image editor (галерея)
|
// 📷 Показать встроенную камеру (без системного превью)
|
||||||
|
var showInAppCamera by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// 🖼 Состояние для multi-image editor (галерея)
|
||||||
var pendingGalleryImages by remember { mutableStateOf<List<Uri>>(emptyList()) }
|
var pendingGalleryImages by remember { mutableStateOf<List<Uri>>(emptyList()) }
|
||||||
|
|
||||||
// <20>📷 Camera launcher
|
// <20>📷 Camera launcher
|
||||||
@@ -1877,25 +1881,10 @@ fun ChatDetailScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onOpenCamera = {
|
onOpenCamera = {
|
||||||
// 📷 Очищаем фокус перед открытием камеры
|
// 📷 Открываем встроенную камеру (без системного превью!)
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
|
showInAppCamera = true
|
||||||
// Создаём временный файл для фото
|
|
||||||
try {
|
|
||||||
val photoFile = File.createTempFile(
|
|
||||||
"photo_${System.currentTimeMillis()}",
|
|
||||||
".jpg",
|
|
||||||
context.cacheDir
|
|
||||||
)
|
|
||||||
cameraImageUri = FileProvider.getUriForFile(
|
|
||||||
context,
|
|
||||||
"${context.packageName}.provider",
|
|
||||||
photoFile
|
|
||||||
)
|
|
||||||
cameraLauncher.launch(cameraImageUri!!)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onOpenFilePicker = {
|
onOpenFilePicker = {
|
||||||
// 📄 Открываем файловый пикер
|
// 📄 Открываем файловый пикер
|
||||||
@@ -2143,6 +2132,18 @@ fun ChatDetailScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 📷 In-App Camera (без системного превью!)
|
||||||
|
if (showInAppCamera) {
|
||||||
|
InAppCameraScreen(
|
||||||
|
onDismiss = { showInAppCamera = false },
|
||||||
|
onPhotoTaken = { photoUri ->
|
||||||
|
// Сразу открываем редактор!
|
||||||
|
showInAppCamera = false
|
||||||
|
pendingCameraPhotoUri = photoUri
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 📷 Image Editor для фото с камеры (с caption как в Telegram)
|
// 📷 Image Editor для фото с камеры (с caption как в Telegram)
|
||||||
pendingCameraPhotoUri?.let { uri ->
|
pendingCameraPhotoUri?.let { uri ->
|
||||||
ImageEditorScreen(
|
ImageEditorScreen(
|
||||||
|
|||||||
@@ -426,20 +426,13 @@ fun ImageEditorScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════
|
||||||
// 🎛️ TOP BAR - Transparent overlay (Telegram style)
|
// 🎛️ TOP BAR - Solid black (Telegram style)
|
||||||
// ═══════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
.background(
|
.background(Color.Black)
|
||||||
Brush.verticalGradient(
|
|
||||||
colors = listOf(
|
|
||||||
Color.Black.copy(alpha = 0.5f),
|
|
||||||
Color.Transparent
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
.padding(horizontal = 4.dp, vertical = 8.dp)
|
.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
@@ -1716,14 +1709,7 @@ fun MultiImageEditorScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
.background(
|
.background(Color.Black)
|
||||||
Brush.verticalGradient(
|
|
||||||
colors = listOf(
|
|
||||||
Color.Black.copy(alpha = 0.5f),
|
|
||||||
Color.Transparent
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
.padding(horizontal = 4.dp, vertical = 8.dp)
|
.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -0,0 +1,375 @@
|
|||||||
|
package com.rosetta.messenger.ui.chats.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.camera.core.CameraSelector
|
||||||
|
import androidx.camera.core.ImageCapture
|
||||||
|
import androidx.camera.core.ImageCaptureException
|
||||||
|
import androidx.camera.core.Preview
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
|
import androidx.camera.view.PreviewView
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.FlipCameraAndroid
|
||||||
|
import androidx.compose.material.icons.filled.FlashOff
|
||||||
|
import androidx.compose.material.icons.filled.FlashOn
|
||||||
|
import androidx.compose.material.icons.filled.FlashAuto
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
import android.app.Activity
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.LinearEasing
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 📷 In-App Camera Screen - как в Telegram
|
||||||
|
* Кастомная камера без системного превью, сразу переходит в ImageEditorScreen
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun InAppCameraScreen(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onPhotoTaken: (Uri) -> Unit // Вызывается с URI сделанного фото
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val view = LocalView.current
|
||||||
|
|
||||||
|
// Camera state
|
||||||
|
var lensFacing by remember { mutableStateOf(CameraSelector.LENS_FACING_BACK) }
|
||||||
|
var flashMode by remember { mutableStateOf(ImageCapture.FLASH_MODE_AUTO) }
|
||||||
|
var isCapturing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// Camera references
|
||||||
|
var imageCapture by remember { mutableStateOf<ImageCapture?>(null) }
|
||||||
|
var cameraProvider by remember { mutableStateOf<ProcessCameraProvider?>(null) }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// 🎬 FADE ANIMATION (как в ImageEditorScreen)
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
var isClosing by remember { mutableStateOf(false) }
|
||||||
|
val animationProgress = remember { Animatable(0f) }
|
||||||
|
|
||||||
|
// Enter animation
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
animationProgress.animateTo(
|
||||||
|
targetValue = 1f,
|
||||||
|
animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animated dismiss function
|
||||||
|
fun animatedDismiss() {
|
||||||
|
if (isClosing) return
|
||||||
|
isClosing = true
|
||||||
|
scope.launch {
|
||||||
|
animationProgress.animateTo(
|
||||||
|
targetValue = 0f,
|
||||||
|
animationSpec = tween(durationMillis = 150, easing = LinearEasing)
|
||||||
|
)
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// 🎨 Status bar (черный как в ImageEditorScreen)
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
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 }
|
||||||
|
|
||||||
|
LaunchedEffect(animationProgress.value) {
|
||||||
|
if (window == null || insetsController == null) return@LaunchedEffect
|
||||||
|
|
||||||
|
val progress = animationProgress.value
|
||||||
|
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
|
||||||
|
insetsController.isAppearanceLightStatusBars = progress < 0.5f && originalLightStatusBars
|
||||||
|
insetsController.isAppearanceLightNavigationBars = progress < 0.5f && originalLightNavigationBars
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(window) {
|
||||||
|
onDispose {
|
||||||
|
if (window == null || insetsController == null) return@onDispose
|
||||||
|
window.statusBarColor = originalStatusBarColor
|
||||||
|
window.navigationBarColor = originalNavigationBarColor
|
||||||
|
insetsController.isAppearanceLightStatusBars = originalLightStatusBars
|
||||||
|
insetsController.isAppearanceLightNavigationBars = originalLightNavigationBars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler { animatedDismiss() }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// 📷 CAMERA SETUP
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// Shutter button animation
|
||||||
|
val shutterScale by animateFloatAsState(
|
||||||
|
targetValue = if (isCapturing) 0.85f else 1f,
|
||||||
|
animationSpec = tween(100)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Take photo function
|
||||||
|
fun takePhoto() {
|
||||||
|
val capture = imageCapture ?: return
|
||||||
|
if (isCapturing) return
|
||||||
|
|
||||||
|
isCapturing = true
|
||||||
|
|
||||||
|
// Create output file
|
||||||
|
val photoFile = File(
|
||||||
|
context.cacheDir,
|
||||||
|
"photo_${SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())}.jpg"
|
||||||
|
)
|
||||||
|
|
||||||
|
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
|
||||||
|
|
||||||
|
capture.takePicture(
|
||||||
|
outputOptions,
|
||||||
|
ContextCompat.getMainExecutor(context),
|
||||||
|
object : ImageCapture.OnImageSavedCallback {
|
||||||
|
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
|
||||||
|
val savedUri = Uri.fromFile(photoFile)
|
||||||
|
// Сразу открываем редактор без промежуточного превью!
|
||||||
|
onPhotoTaken(savedUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(exc: ImageCaptureException) {
|
||||||
|
Log.e("InAppCamera", "Photo capture failed: ${exc.message}", exc)
|
||||||
|
isCapturing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// 🎨 UI
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// PreviewView reference
|
||||||
|
var previewView by remember { mutableStateOf<PreviewView?>(null) }
|
||||||
|
|
||||||
|
// Bind camera when previewView, lensFacing or flashMode changes
|
||||||
|
LaunchedEffect(previewView, lensFacing, flashMode) {
|
||||||
|
val pv = previewView ?: return@LaunchedEffect
|
||||||
|
|
||||||
|
val provider = context.getCameraProvider()
|
||||||
|
cameraProvider = provider
|
||||||
|
|
||||||
|
val preview = Preview.Builder().build().also {
|
||||||
|
it.setSurfaceProvider(pv.surfaceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
val capture = ImageCapture.Builder()
|
||||||
|
.setFlashMode(flashMode)
|
||||||
|
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
||||||
|
.build()
|
||||||
|
imageCapture = capture
|
||||||
|
|
||||||
|
val cameraSelector = CameraSelector.Builder()
|
||||||
|
.requireLensFacing(lensFacing)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
try {
|
||||||
|
provider.unbindAll()
|
||||||
|
provider.bindToLifecycle(
|
||||||
|
lifecycleOwner,
|
||||||
|
cameraSelector,
|
||||||
|
preview,
|
||||||
|
capture
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("InAppCamera", "Use case binding failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unbind on dispose
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
cameraProvider?.unbindAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.graphicsLayer { alpha = animationProgress.value }
|
||||||
|
.background(Color.Black)
|
||||||
|
) {
|
||||||
|
// Camera Preview
|
||||||
|
AndroidView(
|
||||||
|
factory = { ctx ->
|
||||||
|
PreviewView(ctx).apply {
|
||||||
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
scaleType = PreviewView.ScaleType.FILL_CENTER
|
||||||
|
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
|
||||||
|
previewView = this
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Top controls (Close + Flash)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.statusBarsPadding()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
// Close button
|
||||||
|
IconButton(
|
||||||
|
onClick = { animatedDismiss() },
|
||||||
|
modifier = Modifier
|
||||||
|
.size(44.dp)
|
||||||
|
.background(Color.Black.copy(alpha = 0.3f), CircleShape)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Close,
|
||||||
|
contentDescription = "Close",
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flash button
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
flashMode = when (flashMode) {
|
||||||
|
ImageCapture.FLASH_MODE_OFF -> ImageCapture.FLASH_MODE_AUTO
|
||||||
|
ImageCapture.FLASH_MODE_AUTO -> ImageCapture.FLASH_MODE_ON
|
||||||
|
else -> ImageCapture.FLASH_MODE_OFF
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.size(44.dp)
|
||||||
|
.background(Color.Black.copy(alpha = 0.3f), CircleShape)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
when (flashMode) {
|
||||||
|
ImageCapture.FLASH_MODE_OFF -> Icons.Default.FlashOff
|
||||||
|
ImageCapture.FLASH_MODE_AUTO -> Icons.Default.FlashAuto
|
||||||
|
else -> Icons.Default.FlashOn
|
||||||
|
},
|
||||||
|
contentDescription = "Flash",
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom controls (Shutter + Flip)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.padding(bottom = 32.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// Placeholder for symmetry
|
||||||
|
Spacer(modifier = Modifier.size(60.dp))
|
||||||
|
|
||||||
|
// Shutter button
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(80.dp)
|
||||||
|
.scale(shutterScale)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(Color.White.copy(alpha = 0.2f))
|
||||||
|
.border(4.dp, Color.White, CircleShape)
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null
|
||||||
|
) { takePhoto() },
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(64.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(Color.White)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flip camera button
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
lensFacing = if (lensFacing == CameraSelector.LENS_FACING_BACK) {
|
||||||
|
CameraSelector.LENS_FACING_FRONT
|
||||||
|
} else {
|
||||||
|
CameraSelector.LENS_FACING_BACK
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.background(Color.Black.copy(alpha = 0.3f), CircleShape)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.FlipCameraAndroid,
|
||||||
|
contentDescription = "Flip camera",
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(28.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить CameraProvider с suspend
|
||||||
|
*/
|
||||||
|
private suspend fun Context.getCameraProvider(): ProcessCameraProvider {
|
||||||
|
return suspendCoroutine { continuation ->
|
||||||
|
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||||
|
cameraProviderFuture.addListener({
|
||||||
|
continuation.resume(cameraProviderFuture.get())
|
||||||
|
}, ContextCompat.getMainExecutor(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user