feat: Enhance MultiImageEditorScreen with synchronous image saving and improved editing tools
This commit is contained in:
@@ -51,8 +51,10 @@ import ja.burhanrashid52.photoeditor.PhotoEditorView
|
|||||||
import ja.burhanrashid52.photoeditor.SaveSettings
|
import ja.burhanrashid52.photoeditor.SaveSettings
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
private const val TAG = "ImageEditorScreen"
|
private const val TAG = "ImageEditorScreen"
|
||||||
|
|
||||||
@@ -760,6 +762,48 @@ private suspend fun saveEditedImage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save edited image synchronously using suspendCoroutine
|
||||||
|
*/
|
||||||
|
private suspend fun saveEditedImageSync(
|
||||||
|
context: Context,
|
||||||
|
photoEditor: PhotoEditor?
|
||||||
|
): Uri? {
|
||||||
|
if (photoEditor == null) return null
|
||||||
|
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val file = File(context.cacheDir, "edited_${System.currentTimeMillis()}_${(0..9999).random()}.png")
|
||||||
|
|
||||||
|
val saveSettings = SaveSettings.Builder()
|
||||||
|
.setClearViewsEnabled(false)
|
||||||
|
.setTransparencyEnabled(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
photoEditor.saveAsFile(
|
||||||
|
file.absolutePath,
|
||||||
|
saveSettings,
|
||||||
|
object : PhotoEditor.OnSaveListener {
|
||||||
|
override fun onSuccess(imagePath: String) {
|
||||||
|
Log.d(TAG, "Image saved sync to: $imagePath")
|
||||||
|
continuation.resume(Uri.fromFile(File(imagePath)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(exception: Exception) {
|
||||||
|
Log.e(TAG, "Failed to save image sync", exception)
|
||||||
|
continuation.resume(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error saving image sync", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotate/Flip options bar
|
* Rotate/Flip options bar
|
||||||
*/
|
*/
|
||||||
@@ -893,9 +937,51 @@ fun MultiImageEditorScreen(
|
|||||||
// Current caption (для текущей страницы)
|
// Current caption (для текущей страницы)
|
||||||
var currentCaption by remember { mutableStateOf("") }
|
var currentCaption by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
// === EDITING STATE ===
|
||||||
|
var currentTool by remember { mutableStateOf(EditorTool.NONE) }
|
||||||
|
var selectedColor by remember { mutableStateOf(Color.White) }
|
||||||
|
var brushSize by remember { mutableFloatStateOf(15f) }
|
||||||
|
var showColorPicker by remember { mutableStateOf(false) }
|
||||||
|
var showBrushSizeSlider by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// PhotoEditor references for each page
|
||||||
|
val photoEditors = remember { mutableStateMapOf<Int, PhotoEditor?>() }
|
||||||
|
val photoEditorViews = remember { mutableStateMapOf<Int, PhotoEditorView?>() }
|
||||||
|
|
||||||
|
// Crop launcher
|
||||||
|
val cropLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
result.data?.let { data ->
|
||||||
|
UCrop.getOutput(data)?.let { croppedUri ->
|
||||||
|
val currentPage = pagerState.currentPage
|
||||||
|
if (currentPage < imagesWithCaptions.size) {
|
||||||
|
imagesWithCaptions[currentPage] = imagesWithCaptions[currentPage].copy(uri = croppedUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sync caption when page changes
|
// Sync caption when page changes
|
||||||
LaunchedEffect(pagerState.currentPage) {
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
currentCaption = imagesWithCaptions.getOrNull(pagerState.currentPage)?.caption ?: ""
|
currentCaption = imagesWithCaptions.getOrNull(pagerState.currentPage)?.caption ?: ""
|
||||||
|
// Reset editing tools when changing page
|
||||||
|
currentTool = EditorTool.NONE
|
||||||
|
showColorPicker = false
|
||||||
|
showBrushSizeSlider = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update brush settings when they change
|
||||||
|
LaunchedEffect(selectedColor, brushSize) {
|
||||||
|
val currentPage = pagerState.currentPage
|
||||||
|
photoEditors[currentPage]?.let { editor ->
|
||||||
|
if (currentTool == EditorTool.DRAW) {
|
||||||
|
editor.brushColor = selectedColor.toArgb()
|
||||||
|
editor.brushSize = brushSize
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler {
|
BackHandler {
|
||||||
@@ -910,16 +996,61 @@ fun MultiImageEditorScreen(
|
|||||||
// Horizontal Pager для свайпа между фото
|
// Horizontal Pager для свайпа между фото
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
userScrollEnabled = currentTool == EditorTool.NONE // Disable swipe when editing
|
||||||
) { page ->
|
) { page ->
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
// Отображаем изображение
|
// PhotoEditorView for editing
|
||||||
AsyncImageLoader(
|
AndroidView(
|
||||||
uri = imagesWithCaptions[page].uri,
|
factory = { ctx ->
|
||||||
modifier = Modifier.fillMaxSize()
|
PhotoEditorView(ctx).apply {
|
||||||
|
photoEditorViews[page] = this
|
||||||
|
|
||||||
|
// Load bitmap
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val inputStream = ctx.contentResolver.openInputStream(imagesWithCaptions[page].uri)
|
||||||
|
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||||
|
inputStream?.close()
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
source.setImageBitmap(bitmap)
|
||||||
|
|
||||||
|
// Create PhotoEditor
|
||||||
|
val editor = PhotoEditor.Builder(ctx, this@apply)
|
||||||
|
.setPinchTextScalable(true)
|
||||||
|
.build()
|
||||||
|
photoEditors[page] = editor
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error loading image for page $page", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
update = { view ->
|
||||||
|
// Reload if URI changed (after crop)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error reloading image", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -960,20 +1091,92 @@ fun MultiImageEditorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spacer for balance
|
// Undo button (when drawing)
|
||||||
Spacer(modifier = Modifier.size(48.dp))
|
if (currentTool == EditorTool.DRAW) {
|
||||||
|
IconButton(onClick = {
|
||||||
|
photoEditors[pagerState.currentPage]?.undo()
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
TablerIcons.ArrowBackUp,
|
||||||
|
contentDescription = "Undo",
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(28.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Spacer for balance
|
||||||
|
Spacer(modifier = Modifier.size(48.dp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom section - Caption + Send
|
// Bottom section - Tools + Caption + Send
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.imePadding()
|
.imePadding()
|
||||||
.navigationBarsPadding()
|
|
||||||
) {
|
) {
|
||||||
|
// Color picker bar (when drawing)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = showColorPicker && currentTool == EditorTool.DRAW,
|
||||||
|
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
|
||||||
|
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
|
||||||
|
) {
|
||||||
|
ColorPickerBar(
|
||||||
|
selectedColor = selectedColor,
|
||||||
|
onColorSelected = { color ->
|
||||||
|
selectedColor = color
|
||||||
|
photoEditors[pagerState.currentPage]?.brushColor = color.toArgb()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Brush size slider (when drawing)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = showBrushSizeSlider && currentTool == EditorTool.DRAW,
|
||||||
|
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
|
||||||
|
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
|
||||||
|
) {
|
||||||
|
BrushSizeBar(
|
||||||
|
brushSize = brushSize,
|
||||||
|
onBrushSizeChanged = { size ->
|
||||||
|
brushSize = size
|
||||||
|
photoEditors[pagerState.currentPage]?.brushSize = size
|
||||||
|
},
|
||||||
|
selectedColor = selectedColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate options bar
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = currentTool == EditorTool.ROTATE,
|
||||||
|
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
|
||||||
|
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
|
||||||
|
) {
|
||||||
|
RotateOptionsBar(
|
||||||
|
onRotateLeft = {
|
||||||
|
photoEditorViews[pagerState.currentPage]?.source?.rotation =
|
||||||
|
(photoEditorViews[pagerState.currentPage]?.source?.rotation ?: 0f) - 90f
|
||||||
|
},
|
||||||
|
onRotateRight = {
|
||||||
|
photoEditorViews[pagerState.currentPage]?.source?.rotation =
|
||||||
|
(photoEditorViews[pagerState.currentPage]?.source?.rotation ?: 0f) + 90f
|
||||||
|
},
|
||||||
|
onFlipHorizontal = {
|
||||||
|
photoEditorViews[pagerState.currentPage]?.source?.let { imageView ->
|
||||||
|
imageView.scaleX = -imageView.scaleX
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFlipVertical = {
|
||||||
|
photoEditorViews[pagerState.currentPage]?.source?.let { imageView ->
|
||||||
|
imageView.scaleY = -imageView.scaleY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Thumbnails strip (если больше 1 фото)
|
// Thumbnails strip (если больше 1 фото)
|
||||||
if (imagesWithCaptions.size > 1) {
|
if (imagesWithCaptions.size > 1 && currentTool == EditorTool.NONE) {
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -1006,43 +1209,48 @@ fun MultiImageEditorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Caption input bar
|
// Caption input bar (hidden during editing)
|
||||||
Row(
|
AnimatedVisibility(
|
||||||
modifier = Modifier
|
visible = currentTool == EditorTool.NONE,
|
||||||
.fillMaxWidth()
|
enter = fadeIn(),
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
exit = fadeOut()
|
||||||
verticalAlignment = Alignment.Bottom,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
|
||||||
) {
|
) {
|
||||||
// Caption text field
|
Row(
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(22.dp))
|
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||||
.background(Color.White.copy(alpha = 0.15f))
|
verticalAlignment = Alignment.Bottom,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
) {
|
) {
|
||||||
BasicTextField(
|
// Caption text field
|
||||||
value = currentCaption,
|
Box(
|
||||||
onValueChange = { newCaption ->
|
|
||||||
currentCaption = newCaption
|
|
||||||
// Update caption for current image
|
|
||||||
if (pagerState.currentPage < imagesWithCaptions.size) {
|
|
||||||
imagesWithCaptions[pagerState.currentPage] =
|
|
||||||
imagesWithCaptions[pagerState.currentPage].copy(caption = newCaption)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.weight(1f)
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
.clip(RoundedCornerShape(22.dp))
|
||||||
textStyle = androidx.compose.ui.text.TextStyle(
|
.background(Color.White.copy(alpha = 0.15f))
|
||||||
color = Color.White,
|
) {
|
||||||
fontSize = 16.sp
|
BasicTextField(
|
||||||
),
|
value = currentCaption,
|
||||||
maxLines = 4,
|
onValueChange = { newCaption ->
|
||||||
decorationBox = { innerTextField ->
|
currentCaption = newCaption
|
||||||
Box {
|
// Update caption for current image
|
||||||
if (currentCaption.isEmpty()) {
|
if (pagerState.currentPage < imagesWithCaptions.size) {
|
||||||
Text(
|
imagesWithCaptions[pagerState.currentPage] =
|
||||||
|
imagesWithCaptions[pagerState.currentPage].copy(caption = newCaption)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
textStyle = androidx.compose.ui.text.TextStyle(
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 16.sp
|
||||||
|
),
|
||||||
|
maxLines = 4,
|
||||||
|
decorationBox = { innerTextField ->
|
||||||
|
Box {
|
||||||
|
if (currentCaption.isEmpty()) {
|
||||||
|
Text(
|
||||||
"Add a caption...",
|
"Add a caption...",
|
||||||
color = Color.White.copy(alpha = 0.6f),
|
color = Color.White.copy(alpha = 0.6f),
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
@@ -1062,7 +1270,31 @@ fun MultiImageEditorScreen(
|
|||||||
.background(PrimaryBlue)
|
.background(PrimaryBlue)
|
||||||
.clickable(enabled = !isSaving) {
|
.clickable(enabled = !isSaving) {
|
||||||
isSaving = true
|
isSaving = true
|
||||||
onSendAll(imagesWithCaptions.toList())
|
// Save all edited images before sending
|
||||||
|
scope.launch {
|
||||||
|
val savedImages = mutableListOf<ImageWithCaption>()
|
||||||
|
|
||||||
|
for (i in imagesWithCaptions.indices) {
|
||||||
|
val editor = photoEditors[i]
|
||||||
|
val originalImage = imagesWithCaptions[i]
|
||||||
|
|
||||||
|
if (editor != null) {
|
||||||
|
// Save edited image
|
||||||
|
val savedUri = saveEditedImageSync(context, editor)
|
||||||
|
if (savedUri != null) {
|
||||||
|
savedImages.add(originalImage.copy(uri = savedUri))
|
||||||
|
} else {
|
||||||
|
// Fallback to original if save fails
|
||||||
|
savedImages.add(originalImage)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No editor for this page, use original
|
||||||
|
savedImages.add(originalImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSendAll(savedImages)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
@@ -1084,6 +1316,66 @@ fun MultiImageEditorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom toolbar with editing tools
|
||||||
|
BottomToolbar(
|
||||||
|
currentTool = currentTool,
|
||||||
|
onToolSelected = { tool ->
|
||||||
|
val currentEditor = photoEditors[pagerState.currentPage]
|
||||||
|
when (tool) {
|
||||||
|
EditorTool.DRAW -> {
|
||||||
|
currentTool = if (currentTool == EditorTool.DRAW) EditorTool.NONE else tool
|
||||||
|
if (currentTool == EditorTool.DRAW) {
|
||||||
|
currentEditor?.setBrushDrawingMode(true)
|
||||||
|
currentEditor?.brushColor = selectedColor.toArgb()
|
||||||
|
currentEditor?.brushSize = brushSize
|
||||||
|
showColorPicker = true
|
||||||
|
} else {
|
||||||
|
currentEditor?.setBrushDrawingMode(false)
|
||||||
|
showColorPicker = false
|
||||||
|
}
|
||||||
|
showBrushSizeSlider = false
|
||||||
|
}
|
||||||
|
EditorTool.CROP -> {
|
||||||
|
currentTool = EditorTool.NONE
|
||||||
|
showColorPicker = false
|
||||||
|
showBrushSizeSlider = false
|
||||||
|
currentEditor?.setBrushDrawingMode(false)
|
||||||
|
// Launch UCrop
|
||||||
|
launchCrop(context, imagesWithCaptions[pagerState.currentPage].uri, cropLauncher)
|
||||||
|
}
|
||||||
|
EditorTool.ROTATE -> {
|
||||||
|
currentTool = if (currentTool == EditorTool.ROTATE) EditorTool.NONE else tool
|
||||||
|
showColorPicker = false
|
||||||
|
showBrushSizeSlider = false
|
||||||
|
currentEditor?.setBrushDrawingMode(false)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
currentTool = EditorTool.NONE
|
||||||
|
showColorPicker = false
|
||||||
|
showBrushSizeSlider = false
|
||||||
|
currentEditor?.setBrushDrawingMode(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBrushSizeClick = {
|
||||||
|
showBrushSizeSlider = !showBrushSizeSlider
|
||||||
|
showColorPicker = false
|
||||||
|
},
|
||||||
|
onEraserClick = {
|
||||||
|
photoEditors[pagerState.currentPage]?.brushEraser()
|
||||||
|
},
|
||||||
|
onCropClick = {
|
||||||
|
launchCrop(context, imagesWithCaptions[pagerState.currentPage].uri, cropLauncher)
|
||||||
|
},
|
||||||
|
onRotateClick = {
|
||||||
|
currentTool = if (currentTool == EditorTool.ROTATE) EditorTool.NONE else EditorTool.ROTATE
|
||||||
|
showColorPicker = false
|
||||||
|
showBrushSizeSlider = false
|
||||||
|
photoEditors[pagerState.currentPage]?.setBrushDrawingMode(false)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -913,12 +913,12 @@ private fun CollapsingProfileHeader(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.padding(top = statusBarHeight)
|
.padding(top = statusBarHeight)
|
||||||
.padding(4.dp)
|
.padding(end = 52.dp, top = 4.dp) // Сдвинуто левее чтобы не накладываться на меню
|
||||||
) {
|
) {
|
||||||
TextButton(onClick = onSave) {
|
TextButton(onClick = onSave) {
|
||||||
Text(
|
Text(
|
||||||
text = "Save",
|
text = "Save",
|
||||||
color = Color.White,
|
color = if (isDarkTheme) Color.White else Color.Black,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1020,12 +1020,12 @@ fun ProfileCard(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
.padding(4.dp)
|
.padding(end = 52.dp, top = 4.dp) // Сдвинуто левее
|
||||||
) {
|
) {
|
||||||
TextButton(onClick = onSave) {
|
TextButton(onClick = onSave) {
|
||||||
Text(
|
Text(
|
||||||
text = "Save",
|
text = "Save",
|
||||||
color = Color.White,
|
color = if (isDarkTheme) Color.White else Color.Black,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user