refactor: Remove placeholder buttons for location and contact in QuickActionsRow

This commit is contained in:
k1ngsterr1
2026-01-25 19:37:24 +05:00
parent 636cd9f3b8
commit 2605035c26
3 changed files with 553 additions and 21 deletions

View File

@@ -0,0 +1,509 @@
package com.rosetta.messenger.ui.chats.components
import android.content.Context
import android.net.Uri
import android.util.Log
import android.widget.ImageView
import androidx.activity.compose.BackHandler
import androidx.compose.animation.*
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
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.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import ja.burhanrashid52.photoeditor.PhotoEditor
import ja.burhanrashid52.photoeditor.PhotoEditorView
import ja.burhanrashid52.photoeditor.SaveSettings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
private const val TAG = "ImageEditorScreen"
/**
* Available editing tools
*/
enum class EditorTool {
NONE,
DRAW
}
/**
* Drawing colors
*/
val drawingColors = listOf(
Color.White,
Color.Black,
Color.Red,
Color(0xFFFF9500), // Orange
Color.Yellow,
Color(0xFF34C759), // Green
Color(0xFF007AFF), // Blue
Color(0xFF5856D6), // Purple
Color(0xFFFF2D55), // Pink
)
/**
* Telegram-style image editor screen
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ImageEditorScreen(
imageUri: Uri,
onDismiss: () -> Unit,
onSave: (Uri) -> Unit,
isDarkTheme: Boolean = true
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
// Editor state
var currentTool by remember { mutableStateOf(EditorTool.NONE) }
var selectedColor by remember { mutableStateOf(Color.White) }
var brushSize by remember { mutableStateOf(10f) }
var showColorPicker by remember { mutableStateOf(false) }
var showBrushSizeSlider by remember { mutableStateOf(false) }
var isSaving by remember { mutableStateOf(false) }
// PhotoEditor reference
var photoEditor by remember { mutableStateOf<PhotoEditor?>(null) }
BackHandler {
onDismiss()
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
) {
Column(
modifier = Modifier.fillMaxSize()
) {
// Top toolbar
TopAppBar(
title = { },
navigationIcon = {
IconButton(onClick = onDismiss) {
Icon(
TablerIcons.X,
contentDescription = "Close",
tint = Color.White
)
}
},
actions = {
// Undo
IconButton(
onClick = { photoEditor?.undo() }
) {
Icon(
TablerIcons.ArrowBackUp,
contentDescription = "Undo",
tint = Color.White
)
}
// Redo
IconButton(
onClick = { photoEditor?.redo() }
) {
Icon(
TablerIcons.ArrowForwardUp,
contentDescription = "Redo",
tint = Color.White
)
}
// Done/Save button
TextButton(
onClick = {
scope.launch {
isSaving = true
saveEditedImage(context, photoEditor) { savedUri ->
isSaving = false
if (savedUri != null) {
onSave(savedUri)
}
}
}
},
enabled = !isSaving
) {
if (isSaving) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = PrimaryBlue,
strokeWidth = 2.dp
)
} else {
Text(
"Done",
color = PrimaryBlue,
fontWeight = FontWeight.SemiBold
)
}
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent
)
)
// Photo Editor View
Box(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
AndroidView(
factory = { ctx ->
PhotoEditorView(ctx).apply {
// Load image - fullscreen
source.setImageURI(imageUri)
source.scaleType = ImageView.ScaleType.CENTER_CROP
// Build PhotoEditor
photoEditor = PhotoEditor.Builder(ctx, this)
.setPinchTextScalable(true)
.setClipSourceImage(true)
.build()
}
},
modifier = Modifier.fillMaxSize()
)
}
// Color picker bar (when drawing)
AnimatedVisibility(
visible = currentTool == EditorTool.DRAW && showColorPicker,
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
) {
ColorPickerBar(
selectedColor = selectedColor,
onColorSelected = { color ->
selectedColor = color
photoEditor?.brushColor = color.toArgb()
}
)
}
// Brush size slider
AnimatedVisibility(
visible = currentTool == EditorTool.DRAW && showBrushSizeSlider,
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
) {
BrushSizeBar(
brushSize = brushSize,
onBrushSizeChanged = { size ->
brushSize = size
photoEditor?.brushSize = size
},
selectedColor = selectedColor
)
}
// Bottom toolbar with tools
BottomToolbar(
currentTool = currentTool,
onToolSelected = { tool ->
when (tool) {
EditorTool.DRAW -> {
if (currentTool == EditorTool.DRAW) {
showColorPicker = !showColorPicker
showBrushSizeSlider = false
} else {
currentTool = tool
photoEditor?.setBrushDrawingMode(true)
photoEditor?.brushColor = selectedColor.toArgb()
photoEditor?.brushSize = brushSize
showColorPicker = true
}
}
else -> {
currentTool = EditorTool.NONE
showColorPicker = false
showBrushSizeSlider = false
photoEditor?.setBrushDrawingMode(false)
}
}
},
onBrushSizeClick = {
showBrushSizeSlider = !showBrushSizeSlider
showColorPicker = false
},
onEraserClick = {
photoEditor?.brushEraser()
}
)
}
}
}
@Composable
private fun BottomToolbar(
currentTool: EditorTool,
onToolSelected: (EditorTool) -> Unit,
onBrushSizeClick: () -> Unit,
onEraserClick: () -> Unit
) {
Surface(
color = Color(0xFF1C1C1E),
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp)
.navigationBarsPadding(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
// Draw tool
ToolButton(
icon = TablerIcons.Pencil,
label = "Draw",
isSelected = currentTool == EditorTool.DRAW,
onClick = { onToolSelected(EditorTool.DRAW) }
)
// Eraser (when drawing)
AnimatedVisibility(
visible = currentTool == EditorTool.DRAW,
enter = fadeIn() + scaleIn(),
exit = fadeOut() + scaleOut()
) {
ToolButton(
icon = TablerIcons.Eraser,
label = "Eraser",
isSelected = false,
onClick = onEraserClick
)
}
// Brush size (when drawing)
AnimatedVisibility(
visible = currentTool == EditorTool.DRAW,
enter = fadeIn() + scaleIn(),
exit = fadeOut() + scaleOut()
) {
ToolButton(
icon = TablerIcons.Circle,
label = "Size",
isSelected = false,
onClick = onBrushSizeClick
)
}
}
}
}
@Composable
private fun ToolButton(
icon: androidx.compose.ui.graphics.vector.ImageVector,
label: String,
isSelected: Boolean,
onClick: () -> Unit
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onClick
)
.padding(8.dp)
) {
Icon(
imageVector = icon,
contentDescription = label,
tint = if (isSelected) PrimaryBlue else Color.White,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = label,
color = if (isSelected) PrimaryBlue else Color.White.copy(alpha = 0.7f),
fontSize = 10.sp
)
}
}
@Composable
private fun ColorPickerBar(
selectedColor: Color,
onColorSelected: (Color) -> Unit
) {
Surface(
color = Color(0xFF2C2C2E),
modifier = Modifier.fillMaxWidth()
) {
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
contentPadding = PaddingValues(horizontal = 8.dp)
) {
items(drawingColors) { color ->
ColorButton(
color = color,
isSelected = color == selectedColor,
onClick = { onColorSelected(color) }
)
}
}
}
}
@Composable
private fun ColorButton(
color: Color,
isSelected: Boolean,
onClick: () -> Unit
) {
Box(
modifier = Modifier
.size(32.dp)
.clip(CircleShape)
.background(color)
.then(
if (isSelected) {
Modifier.border(3.dp, Color.White, CircleShape)
} else {
Modifier.border(1.dp, Color.White.copy(alpha = 0.3f), CircleShape)
}
)
.clickable(onClick = onClick),
contentAlignment = Alignment.Center
) {
if (isSelected) {
Icon(
Icons.Default.Check,
contentDescription = null,
tint = if (color == Color.White) Color.Black else Color.White,
modifier = Modifier.size(16.dp)
)
}
}
}
@Composable
private fun BrushSizeBar(
brushSize: Float,
onBrushSizeChanged: (Float) -> Unit,
selectedColor: Color
) {
Surface(
color = Color(0xFF2C2C2E),
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Min indicator
Box(
modifier = Modifier
.size(8.dp)
.clip(CircleShape)
.background(selectedColor)
)
// Slider
Slider(
value = brushSize,
onValueChange = onBrushSizeChanged,
valueRange = 5f..50f,
modifier = Modifier
.weight(1f)
.padding(horizontal = 16.dp),
colors = SliderDefaults.colors(
thumbColor = selectedColor,
activeTrackColor = selectedColor
)
)
// Max indicator
Box(
modifier = Modifier
.size(24.dp)
.clip(CircleShape)
.background(selectedColor)
)
}
}
}
/**
* Save edited image and return the URI
*/
private suspend fun saveEditedImage(
context: Context,
photoEditor: PhotoEditor?,
onResult: (Uri?) -> Unit
) {
if (photoEditor == null) {
onResult(null)
return
}
withContext(Dispatchers.IO) {
try {
val file = File(context.cacheDir, "edited_${System.currentTimeMillis()}.png")
val saveSettings = SaveSettings.Builder()
.setClearViewsEnabled(false)
.setTransparencyEnabled(true)
.build()
photoEditor.saveAsFile(
file.absolutePath,
saveSettings,
object : PhotoEditor.OnSaveListener {
override fun onSuccess(imagePath: String) {
Log.d(TAG, "Image saved to: $imagePath")
onResult(Uri.fromFile(File(imagePath)))
}
override fun onFailure(exception: Exception) {
Log.e(TAG, "Failed to save image", exception)
onResult(null)
}
}
)
} catch (e: Exception) {
Log.e(TAG, "Error saving image", e)
withContext(Dispatchers.Main) {
onResult(null)
}
}
}
}

View File

@@ -85,6 +85,9 @@ fun MediaPickerBottomSheet(
// Selected items
var selectedItems by remember { mutableStateOf<Set<Long>>(emptySet()) }
// Editor state - when user taps on a photo, open editor
var editingItem by remember { mutableStateOf<MediaItem?>(null) }
// Permission launcher
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestMultiplePermissions()
@@ -253,6 +256,23 @@ fun MediaPickerBottomSheet(
mediaItems = mediaItems,
selectedItems = selectedItems,
onItemClick = { item ->
// Single tap - open editor for images, or select for videos
if (!item.isVideo) {
// Open image editor
editingItem = item
} else {
// For videos - just toggle selection
selectedItems = if (item.id in selectedItems) {
selectedItems - item.id
} else if (selectedItems.size < maxSelection) {
selectedItems + item.id
} else {
selectedItems
}
}
},
onItemLongClick = { item ->
// Long press - toggle selection (multi-select mode)
selectedItems = if (item.id in selectedItems) {
selectedItems - item.id
} else if (selectedItems.size < maxSelection) {
@@ -261,9 +281,6 @@ fun MediaPickerBottomSheet(
selectedItems
}
},
onItemLongClick = { item ->
// TODO: Preview image
},
isDarkTheme = isDarkTheme,
modifier = Modifier.weight(1f)
)
@@ -274,6 +291,27 @@ fun MediaPickerBottomSheet(
}
}
}
// Image Editor overlay
editingItem?.let { item ->
ImageEditorScreen(
imageUri = item.uri,
onDismiss = { editingItem = null },
onSave = { editedUri ->
editingItem = null
// Create a new MediaItem with the edited URI
val editedItem = MediaItem(
id = System.currentTimeMillis(),
uri = editedUri,
mimeType = "image/png",
dateModified = System.currentTimeMillis()
)
onMediaSelected(listOf(editedItem))
onDismiss()
},
isDarkTheme = isDarkTheme
)
}
}
@Composable
@@ -363,24 +401,6 @@ private fun QuickActionsRow(
iconColor = iconColor,
onClick = onFileClick
)
// Location button (placeholder)
QuickActionButton(
icon = TablerIcons.MapPin,
label = "Location",
backgroundColor = buttonColor,
iconColor = iconColor,
onClick = { /* TODO */ }
)
// Contact button (placeholder)
QuickActionButton(
icon = TablerIcons.User,
label = "Contact",
backgroundColor = buttonColor,
iconColor = iconColor,
onClick = { /* TODO */ }
)
}
}