mirror of
https://github.com/T8RIN/ImageToolbox.git
synced 2025-08-06 15:49:35 +08:00
Code refactor
This commit is contained in:
@ -44,7 +44,7 @@ import ru.tech.imageresizershrinker.core.ui.widget.modifier.shapeByInteraction
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShowOriginalButton(
|
fun ShowOriginalButton(
|
||||||
canShow: Boolean,
|
canShow: Boolean = true,
|
||||||
onStateChange: (Boolean) -> Unit
|
onStateChange: (Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
val haptics = LocalHapticFeedback.current
|
val haptics = LocalHapticFeedback.current
|
||||||
|
@ -18,91 +18,38 @@
|
|||||||
package ru.tech.imageresizershrinker.feature.gradient_maker.presentation
|
package ru.tech.imageresizershrinker.feature.gradient_maker.presentation
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.animation.AnimatedContent
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
|
||||||
import androidx.compose.foundation.layout.displayCutout
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.only
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.Collections
|
|
||||||
import androidx.compose.material.icons.rounded.Build
|
|
||||||
import androidx.compose.material.icons.rounded.DensitySmall
|
|
||||||
import androidx.compose.material.icons.rounded.GridOn
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.lerp
|
|
||||||
import ru.tech.imageresizershrinker.core.domain.model.IntegerSize
|
|
||||||
import ru.tech.imageresizershrinker.core.resources.R
|
import ru.tech.imageresizershrinker.core.resources.R
|
||||||
import ru.tech.imageresizershrinker.core.resources.icons.ImageOverlay
|
|
||||||
import ru.tech.imageresizershrinker.core.resources.icons.MeshDownload
|
|
||||||
import ru.tech.imageresizershrinker.core.resources.icons.MeshGradient
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.theme.blend
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.utils.content_pickers.Picker
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.utils.content_pickers.rememberImagePicker
|
import ru.tech.imageresizershrinker.core.ui.utils.content_pickers.rememberImagePicker
|
||||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.asClip
|
import ru.tech.imageresizershrinker.core.ui.utils.helper.asClip
|
||||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState
|
import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState
|
||||||
import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.utils.provider.LocalComponentActivity
|
import ru.tech.imageresizershrinker.core.ui.utils.provider.LocalComponentActivity
|
||||||
import ru.tech.imageresizershrinker.core.ui.utils.provider.rememberLocalEssentials
|
import ru.tech.imageresizershrinker.core.ui.utils.provider.rememberLocalEssentials
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.AdaptiveLayoutScreen
|
import ru.tech.imageresizershrinker.core.ui.widget.AdaptiveLayoutScreen
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.BottomButtonsBlock
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.CompareButton
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.ShareButton
|
import ru.tech.imageresizershrinker.core.ui.widget.buttons.ShareButton
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.ShowOriginalButton
|
import ru.tech.imageresizershrinker.core.ui.widget.buttons.ShowOriginalButton
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.controls.SaveExifWidget
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.controls.selection.AlphaSelector
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.controls.selection.ImageFormatSelector
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.ExitWithoutSavingDialog
|
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.ExitWithoutSavingDialog
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.LoadingDialog
|
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.LoadingDialog
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.OneTimeImagePickingDialog
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.enhanced.EnhancedSliderItem
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.image.ImageCounter
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.image.Picture
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.modifier.container
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.modifier.detectSwipes
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.modifier.withModifier
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.other.TopAppBarEmoji
|
import ru.tech.imageresizershrinker.core.ui.widget.other.TopAppBarEmoji
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.preferences.PreferenceItem
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.sheets.PickImageFromUrisSheet
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.sheets.ProcessImagesPreferenceSheet
|
import ru.tech.imageresizershrinker.core.ui.widget.sheets.ProcessImagesPreferenceSheet
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.text.TitleItem
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.text.TopAppBarTitle
|
import ru.tech.imageresizershrinker.core.ui.widget.text.TopAppBarTitle
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.utils.AutoContentBasedColors
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientMakerAppColorSchemeHandler
|
||||||
import ru.tech.imageresizershrinker.feature.compare.presentation.components.CompareSheet
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientMakerBottomButtons
|
||||||
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.ColorStopSelection
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientMakerCompareButton
|
||||||
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientPreview
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientMakerControls
|
||||||
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientPropertiesSelector
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientMakerImagePreview
|
||||||
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientSizeSelector
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientMakerNoDataControls
|
||||||
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientTypeSelector
|
|
||||||
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.MeshGradientEditor
|
|
||||||
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.TileModeSelector
|
|
||||||
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.generateMesh
|
|
||||||
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.rememberGradientState
|
|
||||||
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GradientMakerContent(
|
fun GradientMakerContent(
|
||||||
@ -113,23 +60,9 @@ fun GradientMakerContent(
|
|||||||
val essentials = rememberLocalEssentials()
|
val essentials = rememberLocalEssentials()
|
||||||
val showConfetti: () -> Unit = essentials::showConfetti
|
val showConfetti: () -> Unit = essentials::showConfetti
|
||||||
|
|
||||||
var allowPickingImage by rememberSaveable(component.initialUris) {
|
val allowPickingImage = component.allowPickingImage
|
||||||
mutableStateOf(
|
|
||||||
if (component.initialUris.isNullOrEmpty()) null
|
|
||||||
else true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoContentBasedColors(
|
GradientMakerAppColorSchemeHandler(component)
|
||||||
model = Triple(component.brush, component.meshPoints, component.selectedUri),
|
|
||||||
selector = { (_, uri) ->
|
|
||||||
component.createGradientBitmap(
|
|
||||||
data = uri,
|
|
||||||
integerSize = IntegerSize(1000, 1000)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
allowChangeColor = allowPickingImage != null
|
|
||||||
)
|
|
||||||
|
|
||||||
LaunchedEffect(allowPickingImage) {
|
LaunchedEffect(allowPickingImage) {
|
||||||
if (allowPickingImage != true && !component.isMeshGradient) {
|
if (allowPickingImage != true && !component.isMeshGradient) {
|
||||||
@ -139,29 +72,7 @@ fun GradientMakerContent(
|
|||||||
|
|
||||||
var showExitDialog by rememberSaveable { mutableStateOf(false) }
|
var showExitDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
|
||||||
LaunchedEffect(component.colorStops) {
|
|
||||||
if (component.colorStops.isEmpty()) {
|
|
||||||
colorScheme.apply {
|
|
||||||
component.addColorStop(
|
|
||||||
pair = 0f to primary.blend(primaryContainer, 0.5f),
|
|
||||||
isInitial = true
|
|
||||||
)
|
|
||||||
component.addColorStop(
|
|
||||||
pair = 0.5f to secondary.blend(secondaryContainer, 0.5f),
|
|
||||||
isInitial = true
|
|
||||||
)
|
|
||||||
component.addColorStop(
|
|
||||||
pair = 1f to tertiary.blend(tertiaryContainer, 0.5f),
|
|
||||||
isInitial = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val imagePicker = rememberImagePicker { uris: List<Uri> ->
|
val imagePicker = rememberImagePicker { uris: List<Uri> ->
|
||||||
allowPickingImage = true
|
|
||||||
component.setUris(
|
component.setUris(
|
||||||
uris = uris,
|
uris = uris,
|
||||||
onFailure = essentials::showFailureToast
|
onFailure = essentials::showFailureToast
|
||||||
@ -173,11 +84,6 @@ fun GradientMakerContent(
|
|||||||
|
|
||||||
val isPortrait by isPortraitOrientationAsState()
|
val isPortrait by isPortraitOrientationAsState()
|
||||||
|
|
||||||
var showPickImageFromUrisSheet by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
var showCompareSheet by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var showOriginal by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
AdaptiveLayoutScreen(
|
AdaptiveLayoutScreen(
|
||||||
shouldDisableBackHandler = !component.haveChanges,
|
shouldDisableBackHandler = !component.haveChanges,
|
||||||
isPortrait = isPortrait,
|
isPortrait = isPortrait,
|
||||||
@ -209,10 +115,7 @@ fun GradientMakerContent(
|
|||||||
actions = {
|
actions = {
|
||||||
if (component.uris.isNotEmpty()) {
|
if (component.uris.isNotEmpty()) {
|
||||||
ShowOriginalButton(
|
ShowOriginalButton(
|
||||||
canShow = true,
|
onStateChange = component::setShowOriginal
|
||||||
onStateChange = {
|
|
||||||
showOriginal = it
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
var editSheetData by remember {
|
var editSheetData by remember {
|
||||||
@ -248,406 +151,36 @@ fun GradientMakerContent(
|
|||||||
if (allowPickingImage == null) {
|
if (allowPickingImage == null) {
|
||||||
TopAppBarEmoji()
|
TopAppBarEmoji()
|
||||||
}
|
}
|
||||||
CompareButton(
|
|
||||||
onClick = { showCompareSheet = true },
|
GradientMakerCompareButton(component)
|
||||||
visible = component.brush != null && allowPickingImage == true && component.selectedUri != Uri.EMPTY
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
imagePreview = {
|
imagePreview = {
|
||||||
Box(
|
GradientMakerImagePreview(component)
|
||||||
modifier = Modifier
|
|
||||||
.detectSwipes(
|
|
||||||
onSwipeRight = component::selectLeftUri,
|
|
||||||
onSwipeLeft = component::selectRightUri
|
|
||||||
)
|
|
||||||
.container()
|
|
||||||
.padding(4.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
if (component.isMeshGradient) {
|
|
||||||
GradientPreview(
|
|
||||||
meshGradientState = component.meshGradientState,
|
|
||||||
gradientAlpha = if (showOriginal) 0f else component.gradientAlpha,
|
|
||||||
allowPickingImage = allowPickingImage,
|
|
||||||
gradientSize = component.gradientSize,
|
|
||||||
selectedUri = component.selectedUri,
|
|
||||||
imageAspectRatio = component.imageAspectRatio
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
GradientPreview(
|
|
||||||
brush = component.brush,
|
|
||||||
gradientAlpha = if (showOriginal) 0f else component.gradientAlpha,
|
|
||||||
allowPickingImage = allowPickingImage,
|
|
||||||
gradientSize = component.gradientSize,
|
|
||||||
onSizeChanged = component::setPreviewSize,
|
|
||||||
selectedUri = component.selectedUri,
|
|
||||||
imageAspectRatio = component.imageAspectRatio
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
controls = {
|
controls = {
|
||||||
ImageCounter(
|
GradientMakerControls(component)
|
||||||
imageCount = component.uris.size.takeIf { it > 1 },
|
|
||||||
onRepick = {
|
|
||||||
showPickImageFromUrisSheet = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
AnimatedContent(
|
|
||||||
allowPickingImage == false
|
|
||||||
) { canChangeSize ->
|
|
||||||
if (canChangeSize) {
|
|
||||||
GradientSizeSelector(
|
|
||||||
value = component.gradientSize,
|
|
||||||
onWidthChange = component::updateWidth,
|
|
||||||
onHeightChange = component::updateHeight
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
AlphaSelector(
|
|
||||||
value = component.gradientAlpha,
|
|
||||||
onValueChange = component::updateGradientAlpha,
|
|
||||||
modifier = Modifier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
|
|
||||||
if (component.isMeshGradient) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.container(
|
|
||||||
resultPadding = 0.dp
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
TitleItem(
|
|
||||||
text = stringResource(R.string.points_customization),
|
|
||||||
icon = Icons.Rounded.Build,
|
|
||||||
modifier = Modifier.padding(
|
|
||||||
horizontal = 16.dp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
MeshGradientEditor(
|
|
||||||
state = component.meshGradientState,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.aspectRatio(1f)
|
|
||||||
.padding(16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
EnhancedSliderItem(
|
|
||||||
value = component.meshGradientState.gridSize,
|
|
||||||
title = stringResource(R.string.grid_size),
|
|
||||||
icon = Icons.Rounded.GridOn,
|
|
||||||
valueRange = 2f..6f,
|
|
||||||
internalStateTransformation = { it.roundToInt() },
|
|
||||||
onValueChange = { value ->
|
|
||||||
if (value.roundToInt() != component.meshGradientState.gridSize) {
|
|
||||||
val size = value.roundToInt()
|
|
||||||
component.setResolution(lerp(1f, 16f, 2f / size))
|
|
||||||
component.meshGradientState.points.apply {
|
|
||||||
clear()
|
|
||||||
addAll(generateMesh(size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
EnhancedSliderItem(
|
|
||||||
value = component.meshResolutionX,
|
|
||||||
title = stringResource(R.string.resolution),
|
|
||||||
icon = Icons.Rounded.DensitySmall,
|
|
||||||
valueRange = 1f..64f,
|
|
||||||
internalStateTransformation = { it.roundToInt() },
|
|
||||||
onValueChange = component::setResolution
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
GradientTypeSelector(
|
|
||||||
value = component.gradientType,
|
|
||||||
onValueChange = component::setGradientType
|
|
||||||
) {
|
|
||||||
GradientPropertiesSelector(
|
|
||||||
gradientType = component.gradientType,
|
|
||||||
linearAngle = component.angle,
|
|
||||||
onLinearAngleChange = component::updateLinearAngle,
|
|
||||||
centerFriction = component.centerFriction,
|
|
||||||
radiusFriction = component.radiusFriction,
|
|
||||||
onRadialDimensionsChange = component::setRadialProperties
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
ColorStopSelection(
|
|
||||||
colorStops = component.colorStops,
|
|
||||||
onRemoveClick = component::removeColorStop,
|
|
||||||
onValueChange = component::updateColorStop,
|
|
||||||
onAddColorStop = component::addColorStop
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
TileModeSelector(
|
|
||||||
value = component.tileMode,
|
|
||||||
onValueChange = component::setTileMode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (allowPickingImage == true) {
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
SaveExifWidget(
|
|
||||||
checked = component.keepExif,
|
|
||||||
imageFormat = component.imageFormat,
|
|
||||||
onCheckedChange = component::toggleKeepExif
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
ImageFormatSelector(
|
|
||||||
value = component.imageFormat,
|
|
||||||
forceEnabled = allowPickingImage == false,
|
|
||||||
onValueChange = component::setImageFormat
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
insetsForNoData = WindowInsets(0),
|
insetsForNoData = WindowInsets(0),
|
||||||
noDataControls = {
|
noDataControls = {
|
||||||
val preference1 = @Composable {
|
GradientMakerNoDataControls(
|
||||||
val screen = remember {
|
component = component,
|
||||||
Screen.GradientMaker()
|
onPickImage = pickImage
|
||||||
}
|
|
||||||
PreferenceItem(
|
|
||||||
title = stringResource(screen.title),
|
|
||||||
subtitle = stringResource(screen.subtitle),
|
|
||||||
startIcon = screen.icon,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = {
|
|
||||||
component.setIsMeshGradient(false)
|
|
||||||
allowPickingImage = false
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
val preference2 = @Composable {
|
|
||||||
PreferenceItem(
|
|
||||||
title = stringResource(R.string.gradient_maker_type_image),
|
|
||||||
subtitle = stringResource(R.string.gradient_maker_type_image_sub),
|
|
||||||
startIcon = Icons.Outlined.Collections,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = {
|
|
||||||
component.setIsMeshGradient(false)
|
|
||||||
pickImage()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val preference3 = @Composable {
|
|
||||||
PreferenceItem(
|
|
||||||
title = stringResource(R.string.mesh_gradients),
|
|
||||||
subtitle = stringResource(R.string.mesh_gradients_sub),
|
|
||||||
startIcon = Icons.Outlined.MeshGradient,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = {
|
|
||||||
component.setIsMeshGradient(true)
|
|
||||||
allowPickingImage = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val preference4 = @Composable {
|
|
||||||
PreferenceItem(
|
|
||||||
title = stringResource(R.string.gradient_maker_type_image_mesh),
|
|
||||||
subtitle = stringResource(R.string.gradient_maker_type_image_mesh_sub),
|
|
||||||
startIcon = Icons.Outlined.ImageOverlay,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = {
|
|
||||||
component.setIsMeshGradient(true)
|
|
||||||
pickImage()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val preference5 = @Composable {
|
|
||||||
PreferenceItem(
|
|
||||||
title = stringResource(R.string.collection_mesh_gradients),
|
|
||||||
subtitle = stringResource(R.string.collection_mesh_gradients_sub),
|
|
||||||
startIcon = Icons.Outlined.MeshDownload,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = {
|
|
||||||
component.onNavigate(Screen.MeshGradients)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (isPortrait) {
|
|
||||||
Column {
|
|
||||||
preference1()
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
preference2()
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
preference3()
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
preference4()
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
preference5()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.windowInsetsPadding(
|
|
||||||
WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
preference1.withModifier(modifier = Modifier.weight(1f))
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
preference2.withModifier(modifier = Modifier.weight(1f))
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.windowInsetsPadding(
|
|
||||||
WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
preference3.withModifier(modifier = Modifier.weight(1f))
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
preference4.withModifier(modifier = Modifier.weight(1f))
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
preference5.withModifier(modifier = Modifier.fillMaxWidth(0.5f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
buttons = { actions ->
|
buttons = { actions ->
|
||||||
val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = {
|
GradientMakerBottomButtons(
|
||||||
if (component.brush != null) {
|
component = component,
|
||||||
component.saveBitmaps(
|
actions = actions,
|
||||||
oneTimeSaveLocationUri = it,
|
imagePicker = imagePicker
|
||||||
onStandaloneGradientSaveResult = essentials::parseSaveResult,
|
|
||||||
onResult = essentials::parseSaveResults
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var showFolderSelectionDialog by rememberSaveable {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
var showOneTimeImagePickingDialog by rememberSaveable {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
BottomButtonsBlock(
|
|
||||||
targetState = (allowPickingImage == null) to isPortrait,
|
|
||||||
onSecondaryButtonClick = pickImage,
|
|
||||||
isSecondaryButtonVisible = allowPickingImage == true,
|
|
||||||
isPrimaryButtonVisible = component.brush != null,
|
|
||||||
showNullDataButtonAsContainer = true,
|
|
||||||
onPrimaryButtonClick = {
|
|
||||||
saveBitmap(null)
|
|
||||||
},
|
|
||||||
onPrimaryButtonLongClick = {
|
|
||||||
showFolderSelectionDialog = true
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
if (isPortrait) actions()
|
|
||||||
},
|
|
||||||
onSecondaryButtonLongClick = {
|
|
||||||
showOneTimeImagePickingDialog = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
OneTimeSaveLocationSelectionDialog(
|
|
||||||
visible = showFolderSelectionDialog,
|
|
||||||
onDismiss = { showFolderSelectionDialog = false },
|
|
||||||
onSaveRequest = saveBitmap,
|
|
||||||
formatForFilenameSelection = component.getFormatForFilenameSelection()
|
|
||||||
)
|
|
||||||
OneTimeImagePickingDialog(
|
|
||||||
onDismiss = { showOneTimeImagePickingDialog = false },
|
|
||||||
picker = Picker.Multiple,
|
|
||||||
imagePicker = imagePicker,
|
|
||||||
visible = showOneTimeImagePickingDialog
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
forceImagePreviewToMax = showOriginal,
|
forceImagePreviewToMax = component.showOriginal,
|
||||||
contentPadding = animateDpAsState(
|
contentPadding = animateDpAsState(
|
||||||
if (allowPickingImage == null) 12.dp
|
if (allowPickingImage == null) 12.dp
|
||||||
else 20.dp
|
else 20.dp
|
||||||
).value
|
).value
|
||||||
)
|
)
|
||||||
|
|
||||||
val transformations by remember(
|
|
||||||
component.brush,
|
|
||||||
component.isMeshGradient,
|
|
||||||
component.meshPoints,
|
|
||||||
component.meshResolutionX,
|
|
||||||
component.meshResolutionY,
|
|
||||||
component.gradientAlpha
|
|
||||||
) {
|
|
||||||
derivedStateOf {
|
|
||||||
listOf(
|
|
||||||
component.getGradientTransformation()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PickImageFromUrisSheet(
|
|
||||||
transformations = transformations,
|
|
||||||
visible = showPickImageFromUrisSheet,
|
|
||||||
onDismiss = {
|
|
||||||
showPickImageFromUrisSheet = false
|
|
||||||
},
|
|
||||||
uris = component.uris,
|
|
||||||
selectedUri = component.selectedUri,
|
|
||||||
onUriPicked = { uri ->
|
|
||||||
component.updateSelectedUri(
|
|
||||||
uri = uri,
|
|
||||||
onFailure = essentials::showFailureToast
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onUriRemoved = { uri ->
|
|
||||||
component.updateUrisSilently(removedUri = uri)
|
|
||||||
},
|
|
||||||
columns = if (isPortrait) 2 else 4,
|
|
||||||
)
|
|
||||||
|
|
||||||
CompareSheet(
|
|
||||||
beforeContent = {
|
|
||||||
Picture(
|
|
||||||
model = component.selectedUri,
|
|
||||||
modifier = Modifier.aspectRatio(
|
|
||||||
component.imageAspectRatio
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
afterContent = {
|
|
||||||
if (component.isMeshGradient) {
|
|
||||||
GradientPreview(
|
|
||||||
meshGradientState = component.meshGradientState,
|
|
||||||
gradientAlpha = component.gradientAlpha,
|
|
||||||
allowPickingImage = allowPickingImage,
|
|
||||||
gradientSize = component.gradientSize,
|
|
||||||
selectedUri = component.selectedUri,
|
|
||||||
imageAspectRatio = component.imageAspectRatio
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val gradientState = rememberGradientState()
|
|
||||||
LaunchedEffect(component.brush) {
|
|
||||||
gradientState.gradientType = component.gradientType
|
|
||||||
gradientState.linearGradientAngle = component.angle
|
|
||||||
gradientState.centerFriction = component.centerFriction
|
|
||||||
gradientState.radiusFriction = component.radiusFriction
|
|
||||||
gradientState.colorStops.apply {
|
|
||||||
clear()
|
|
||||||
addAll(component.colorStops)
|
|
||||||
}
|
|
||||||
gradientState.tileMode = component.tileMode
|
|
||||||
}
|
|
||||||
GradientPreview(
|
|
||||||
brush = gradientState.brush,
|
|
||||||
gradientAlpha = component.gradientAlpha,
|
|
||||||
allowPickingImage = allowPickingImage,
|
|
||||||
gradientSize = component.gradientSize,
|
|
||||||
onSizeChanged = {
|
|
||||||
gradientState.size = it
|
|
||||||
},
|
|
||||||
selectedUri = component.selectedUri,
|
|
||||||
imageAspectRatio = component.imageAspectRatio
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
visible = showCompareSheet,
|
|
||||||
onDismiss = {
|
|
||||||
showCompareSheet = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
LoadingDialog(
|
LoadingDialog(
|
||||||
visible = component.isSaving || component.isImageLoading,
|
visible = component.isSaving || component.isImageLoading,
|
||||||
done = component.done,
|
done = component.done,
|
||||||
@ -659,7 +192,6 @@ fun GradientMakerContent(
|
|||||||
ExitWithoutSavingDialog(
|
ExitWithoutSavingDialog(
|
||||||
onExit = {
|
onExit = {
|
||||||
if (allowPickingImage != null) {
|
if (allowPickingImage != null) {
|
||||||
allowPickingImage = null
|
|
||||||
component.resetState()
|
component.resetState()
|
||||||
} else {
|
} else {
|
||||||
component.onGoBack()
|
component.onGoBack()
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* ImageToolbox is an image editor for android
|
||||||
|
* Copyright (c) 2025 T8RIN (Malik Mukhametzyanov)
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the Apache License
|
||||||
|
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import ru.tech.imageresizershrinker.core.domain.model.IntegerSize
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.theme.blend
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.utils.AutoContentBasedColors
|
||||||
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun GradientMakerAppColorSchemeHandler(component: GradientMakerComponent) {
|
||||||
|
AutoContentBasedColors(
|
||||||
|
model = Triple(component.brush, component.meshPoints, component.selectedUri),
|
||||||
|
selector = { (_, uri) ->
|
||||||
|
component.createGradientBitmap(
|
||||||
|
data = uri,
|
||||||
|
integerSize = IntegerSize(1000, 1000)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
allowChangeColor = component.allowPickingImage != null
|
||||||
|
)
|
||||||
|
|
||||||
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
|
LaunchedEffect(component.colorStops) {
|
||||||
|
if (component.colorStops.isEmpty()) {
|
||||||
|
colorScheme.apply {
|
||||||
|
component.addColorStop(
|
||||||
|
pair = 0f to primary.blend(primaryContainer, 0.5f),
|
||||||
|
isInitial = true
|
||||||
|
)
|
||||||
|
component.addColorStop(
|
||||||
|
pair = 0.5f to secondary.blend(secondaryContainer, 0.5f),
|
||||||
|
isInitial = true
|
||||||
|
)
|
||||||
|
component.addColorStop(
|
||||||
|
pair = 1f to tertiary.blend(tertiaryContainer, 0.5f),
|
||||||
|
isInitial = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* ImageToolbox is an image editor for android
|
||||||
|
* Copyright (c) 2025 T8RIN (Malik Mukhametzyanov)
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the Apache License
|
||||||
|
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.utils.content_pickers.ImagePicker
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.utils.content_pickers.Picker
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.utils.provider.rememberLocalEssentials
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.buttons.BottomButtonsBlock
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.OneTimeImagePickingDialog
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog
|
||||||
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun GradientMakerBottomButtons(
|
||||||
|
component: GradientMakerComponent,
|
||||||
|
actions: @Composable RowScope.() -> Unit,
|
||||||
|
imagePicker: ImagePicker
|
||||||
|
) {
|
||||||
|
val essentials = rememberLocalEssentials()
|
||||||
|
val isPortrait by isPortraitOrientationAsState()
|
||||||
|
|
||||||
|
val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = {
|
||||||
|
if (component.brush != null) {
|
||||||
|
component.saveBitmaps(
|
||||||
|
oneTimeSaveLocationUri = it,
|
||||||
|
onStandaloneGradientSaveResult = essentials::parseSaveResult,
|
||||||
|
onResult = essentials::parseSaveResults
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var showFolderSelectionDialog by rememberSaveable {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
var showOneTimeImagePickingDialog by rememberSaveable {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
BottomButtonsBlock(
|
||||||
|
targetState = (component.allowPickingImage == null) to isPortrait,
|
||||||
|
onSecondaryButtonClick = imagePicker::pickImage,
|
||||||
|
isSecondaryButtonVisible = component.allowPickingImage == true,
|
||||||
|
isPrimaryButtonVisible = component.brush != null,
|
||||||
|
showNullDataButtonAsContainer = true,
|
||||||
|
onPrimaryButtonClick = {
|
||||||
|
saveBitmap(null)
|
||||||
|
},
|
||||||
|
onPrimaryButtonLongClick = {
|
||||||
|
showFolderSelectionDialog = true
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
if (isPortrait) actions()
|
||||||
|
},
|
||||||
|
onSecondaryButtonLongClick = {
|
||||||
|
showOneTimeImagePickingDialog = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
OneTimeSaveLocationSelectionDialog(
|
||||||
|
visible = showFolderSelectionDialog,
|
||||||
|
onDismiss = { showFolderSelectionDialog = false },
|
||||||
|
onSaveRequest = saveBitmap,
|
||||||
|
formatForFilenameSelection = component.getFormatForFilenameSelection()
|
||||||
|
)
|
||||||
|
OneTimeImagePickingDialog(
|
||||||
|
onDismiss = { showOneTimeImagePickingDialog = false },
|
||||||
|
picker = Picker.Multiple,
|
||||||
|
imagePicker = imagePicker,
|
||||||
|
visible = showOneTimeImagePickingDialog
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* ImageToolbox is an image editor for android
|
||||||
|
* Copyright (c) 2025 T8RIN (Malik Mukhametzyanov)
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the Apache License
|
||||||
|
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.buttons.CompareButton
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.image.Picture
|
||||||
|
import ru.tech.imageresizershrinker.feature.compare.presentation.components.CompareSheet
|
||||||
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun GradientMakerCompareButton(component: GradientMakerComponent) {
|
||||||
|
var showCompareSheet by rememberSaveable { mutableStateOf(false) }
|
||||||
|
CompareButton(
|
||||||
|
onClick = { showCompareSheet = true },
|
||||||
|
visible = component.brush != null && component.allowPickingImage == true && component.selectedUri != Uri.EMPTY
|
||||||
|
)
|
||||||
|
|
||||||
|
CompareSheet(
|
||||||
|
beforeContent = {
|
||||||
|
Picture(
|
||||||
|
model = component.selectedUri,
|
||||||
|
modifier = Modifier.aspectRatio(
|
||||||
|
component.imageAspectRatio
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
afterContent = {
|
||||||
|
if (component.isMeshGradient) {
|
||||||
|
MeshGradientPreview(
|
||||||
|
meshGradientState = component.meshGradientState,
|
||||||
|
gradientAlpha = component.gradientAlpha,
|
||||||
|
allowPickingImage = component.allowPickingImage,
|
||||||
|
gradientSize = component.gradientSize,
|
||||||
|
selectedUri = component.selectedUri,
|
||||||
|
imageAspectRatio = component.imageAspectRatio
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val gradientState = rememberGradientState()
|
||||||
|
LaunchedEffect(component.brush) {
|
||||||
|
gradientState.gradientType = component.gradientType
|
||||||
|
gradientState.linearGradientAngle = component.angle
|
||||||
|
gradientState.centerFriction = component.centerFriction
|
||||||
|
gradientState.radiusFriction = component.radiusFriction
|
||||||
|
gradientState.colorStops.apply {
|
||||||
|
clear()
|
||||||
|
addAll(component.colorStops)
|
||||||
|
}
|
||||||
|
gradientState.tileMode = component.tileMode
|
||||||
|
}
|
||||||
|
GradientPreview(
|
||||||
|
brush = gradientState.brush,
|
||||||
|
gradientAlpha = component.gradientAlpha,
|
||||||
|
allowPickingImage = component.allowPickingImage,
|
||||||
|
gradientSize = component.gradientSize,
|
||||||
|
onSizeChanged = {
|
||||||
|
gradientState.size = it
|
||||||
|
},
|
||||||
|
selectedUri = component.selectedUri,
|
||||||
|
imageAspectRatio = component.imageAspectRatio
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
visible = showCompareSheet,
|
||||||
|
onDismiss = {
|
||||||
|
showCompareSheet = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* ImageToolbox is an image editor for android
|
||||||
|
* Copyright (c) 2025 T8RIN (Malik Mukhametzyanov)
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the Apache License
|
||||||
|
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Build
|
||||||
|
import androidx.compose.material.icons.rounded.DensitySmall
|
||||||
|
import androidx.compose.material.icons.rounded.GridOn
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.util.lerp
|
||||||
|
import ru.tech.imageresizershrinker.core.resources.R
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.utils.provider.rememberLocalEssentials
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.controls.SaveExifWidget
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.controls.selection.AlphaSelector
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.controls.selection.ImageFormatSelector
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.enhanced.EnhancedSliderItem
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.image.ImageCounter
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.modifier.container
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.sheets.PickImageFromUrisSheet
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.text.TitleItem
|
||||||
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun GradientMakerControls(component: GradientMakerComponent) {
|
||||||
|
var showPickImageFromUrisSheet by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
ImageCounter(
|
||||||
|
imageCount = component.uris.size.takeIf { it > 1 },
|
||||||
|
onRepick = {
|
||||||
|
showPickImageFromUrisSheet = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
AnimatedContent(
|
||||||
|
component.allowPickingImage == false
|
||||||
|
) { canChangeSize ->
|
||||||
|
if (canChangeSize) {
|
||||||
|
GradientSizeSelector(
|
||||||
|
value = component.gradientSize,
|
||||||
|
onWidthChange = component::updateWidth,
|
||||||
|
onHeightChange = component::updateHeight
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AlphaSelector(
|
||||||
|
value = component.gradientAlpha,
|
||||||
|
onValueChange = component::updateGradientAlpha,
|
||||||
|
modifier = Modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
|
||||||
|
if (component.isMeshGradient) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.container(
|
||||||
|
resultPadding = 0.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
TitleItem(
|
||||||
|
text = stringResource(R.string.points_customization),
|
||||||
|
icon = Icons.Rounded.Build,
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
horizontal = 16.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
MeshGradientEditor(
|
||||||
|
state = component.meshGradientState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
EnhancedSliderItem(
|
||||||
|
value = component.meshGradientState.gridSize,
|
||||||
|
title = stringResource(R.string.grid_size),
|
||||||
|
icon = Icons.Rounded.GridOn,
|
||||||
|
valueRange = 2f..6f,
|
||||||
|
internalStateTransformation = { it.roundToInt() },
|
||||||
|
onValueChange = { value ->
|
||||||
|
if (value.roundToInt() != component.meshGradientState.gridSize) {
|
||||||
|
val size = value.roundToInt()
|
||||||
|
component.setResolution(lerp(1f, 16f, 2f / size))
|
||||||
|
component.meshGradientState.points.apply {
|
||||||
|
clear()
|
||||||
|
addAll(generateMesh(size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
EnhancedSliderItem(
|
||||||
|
value = component.meshResolutionX,
|
||||||
|
title = stringResource(R.string.resolution),
|
||||||
|
icon = Icons.Rounded.DensitySmall,
|
||||||
|
valueRange = 1f..64f,
|
||||||
|
internalStateTransformation = { it.roundToInt() },
|
||||||
|
onValueChange = component::setResolution
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
GradientTypeSelector(
|
||||||
|
value = component.gradientType,
|
||||||
|
onValueChange = component::setGradientType
|
||||||
|
) {
|
||||||
|
GradientPropertiesSelector(
|
||||||
|
gradientType = component.gradientType,
|
||||||
|
linearAngle = component.angle,
|
||||||
|
onLinearAngleChange = component::updateLinearAngle,
|
||||||
|
centerFriction = component.centerFriction,
|
||||||
|
radiusFriction = component.radiusFriction,
|
||||||
|
onRadialDimensionsChange = component::setRadialProperties
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
ColorStopSelection(
|
||||||
|
colorStops = component.colorStops,
|
||||||
|
onRemoveClick = component::removeColorStop,
|
||||||
|
onValueChange = component::updateColorStop,
|
||||||
|
onAddColorStop = component::addColorStop
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
TileModeSelector(
|
||||||
|
value = component.tileMode,
|
||||||
|
onValueChange = component::setTileMode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (component.allowPickingImage == true) {
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
SaveExifWidget(
|
||||||
|
checked = component.keepExif,
|
||||||
|
imageFormat = component.imageFormat,
|
||||||
|
onCheckedChange = component::toggleKeepExif
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
ImageFormatSelector(
|
||||||
|
value = component.imageFormat,
|
||||||
|
forceEnabled = component.allowPickingImage == false,
|
||||||
|
onValueChange = component::setImageFormat
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val transformations by remember(
|
||||||
|
component.brush,
|
||||||
|
component.isMeshGradient,
|
||||||
|
component.meshPoints,
|
||||||
|
component.meshResolutionX,
|
||||||
|
component.meshResolutionY,
|
||||||
|
component.gradientAlpha
|
||||||
|
) {
|
||||||
|
derivedStateOf {
|
||||||
|
listOf(
|
||||||
|
component.getGradientTransformation()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isPortrait by isPortraitOrientationAsState()
|
||||||
|
val essentials = rememberLocalEssentials()
|
||||||
|
|
||||||
|
PickImageFromUrisSheet(
|
||||||
|
transformations = transformations,
|
||||||
|
visible = showPickImageFromUrisSheet,
|
||||||
|
onDismiss = {
|
||||||
|
showPickImageFromUrisSheet = false
|
||||||
|
},
|
||||||
|
uris = component.uris,
|
||||||
|
selectedUri = component.selectedUri,
|
||||||
|
onUriPicked = { uri ->
|
||||||
|
component.updateSelectedUri(
|
||||||
|
uri = uri,
|
||||||
|
onFailure = essentials::showFailureToast
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onUriRemoved = { uri ->
|
||||||
|
component.updateUrisSilently(removedUri = uri)
|
||||||
|
},
|
||||||
|
columns = if (isPortrait) 2 else 4,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* ImageToolbox is an image editor for android
|
||||||
|
* Copyright (c) 2025 T8RIN (Malik Mukhametzyanov)
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the Apache License
|
||||||
|
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.modifier.container
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.modifier.detectSwipes
|
||||||
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun GradientMakerImagePreview(component: GradientMakerComponent) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.detectSwipes(
|
||||||
|
onSwipeRight = component::selectLeftUri,
|
||||||
|
onSwipeLeft = component::selectRightUri
|
||||||
|
)
|
||||||
|
.container()
|
||||||
|
.padding(4.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (component.isMeshGradient) {
|
||||||
|
MeshGradientPreview(
|
||||||
|
meshGradientState = component.meshGradientState,
|
||||||
|
gradientAlpha = if (component.showOriginal) 0f else component.gradientAlpha,
|
||||||
|
allowPickingImage = component.allowPickingImage,
|
||||||
|
gradientSize = component.gradientSize,
|
||||||
|
selectedUri = component.selectedUri,
|
||||||
|
imageAspectRatio = component.imageAspectRatio
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
GradientPreview(
|
||||||
|
brush = component.brush,
|
||||||
|
gradientAlpha = if (component.showOriginal) 0f else component.gradientAlpha,
|
||||||
|
allowPickingImage = component.allowPickingImage,
|
||||||
|
gradientSize = component.gradientSize,
|
||||||
|
onSizeChanged = component::setPreviewSize,
|
||||||
|
selectedUri = component.selectedUri,
|
||||||
|
imageAspectRatio = component.imageAspectRatio
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* ImageToolbox is an image editor for android
|
||||||
|
* Copyright (c) 2025 T8RIN (Malik Mukhametzyanov)
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the Apache License
|
||||||
|
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
|
import androidx.compose.foundation.layout.displayCutout
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.only
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Collections
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ru.tech.imageresizershrinker.core.resources.R
|
||||||
|
import ru.tech.imageresizershrinker.core.resources.icons.ImageOverlay
|
||||||
|
import ru.tech.imageresizershrinker.core.resources.icons.MeshDownload
|
||||||
|
import ru.tech.imageresizershrinker.core.resources.icons.MeshGradient
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.modifier.withModifier
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.preferences.PreferenceItem
|
||||||
|
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun GradientMakerNoDataControls(
|
||||||
|
component: GradientMakerComponent,
|
||||||
|
onPickImage: () -> Unit
|
||||||
|
) {
|
||||||
|
val isPortrait by isPortraitOrientationAsState()
|
||||||
|
|
||||||
|
val preference1 = @Composable {
|
||||||
|
val screen = remember {
|
||||||
|
Screen.GradientMaker()
|
||||||
|
}
|
||||||
|
PreferenceItem(
|
||||||
|
title = stringResource(screen.title),
|
||||||
|
subtitle = stringResource(screen.subtitle),
|
||||||
|
startIcon = screen.icon,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
component.setIsMeshGradient(
|
||||||
|
isMeshGradient = false,
|
||||||
|
allowPickingImage = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val preference2 = @Composable {
|
||||||
|
PreferenceItem(
|
||||||
|
title = stringResource(R.string.gradient_maker_type_image),
|
||||||
|
subtitle = stringResource(R.string.gradient_maker_type_image_sub),
|
||||||
|
startIcon = Icons.Outlined.Collections,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
component.setIsMeshGradient(
|
||||||
|
isMeshGradient = false,
|
||||||
|
allowPickingImage = true
|
||||||
|
)
|
||||||
|
onPickImage()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val preference3 = @Composable {
|
||||||
|
PreferenceItem(
|
||||||
|
title = stringResource(R.string.mesh_gradients),
|
||||||
|
subtitle = stringResource(R.string.mesh_gradients_sub),
|
||||||
|
startIcon = Icons.Outlined.MeshGradient,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
component.setIsMeshGradient(
|
||||||
|
isMeshGradient = true,
|
||||||
|
allowPickingImage = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val preference4 = @Composable {
|
||||||
|
PreferenceItem(
|
||||||
|
title = stringResource(R.string.gradient_maker_type_image_mesh),
|
||||||
|
subtitle = stringResource(R.string.gradient_maker_type_image_mesh_sub),
|
||||||
|
startIcon = Icons.Outlined.ImageOverlay,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
component.setIsMeshGradient(
|
||||||
|
isMeshGradient = true,
|
||||||
|
allowPickingImage = true
|
||||||
|
)
|
||||||
|
onPickImage()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val preference5 = @Composable {
|
||||||
|
PreferenceItem(
|
||||||
|
title = stringResource(R.string.collection_mesh_gradients),
|
||||||
|
subtitle = stringResource(R.string.collection_mesh_gradients_sub),
|
||||||
|
startIcon = Icons.Outlined.MeshDownload,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
component.onNavigate(Screen.MeshGradients)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isPortrait) {
|
||||||
|
Column {
|
||||||
|
preference1()
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
preference2()
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
preference3()
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
preference4()
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
preference5()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.windowInsetsPadding(
|
||||||
|
WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
preference1.withModifier(modifier = Modifier.weight(1f))
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
preference2.withModifier(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.windowInsetsPadding(
|
||||||
|
WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
preference3.withModifier(modifier = Modifier.weight(1f))
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
preference4.withModifier(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
preference5.withModifier(modifier = Modifier.fillMaxWidth(0.5f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,6 @@ import androidx.compose.animation.AnimatedContent
|
|||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -43,11 +42,10 @@ import com.smarttoolfactory.colordetector.util.ColorUtil.roundToTwoDigits
|
|||||||
import ru.tech.imageresizershrinker.core.domain.model.IntegerSize
|
import ru.tech.imageresizershrinker.core.domain.model.IntegerSize
|
||||||
import ru.tech.imageresizershrinker.core.resources.icons.BrokenImageAlt
|
import ru.tech.imageresizershrinker.core.resources.icons.BrokenImageAlt
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.image.Picture
|
import ru.tech.imageresizershrinker.core.ui.widget.image.Picture
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.modifier.meshGradient
|
|
||||||
import ru.tech.imageresizershrinker.core.ui.widget.modifier.transparencyChecker
|
import ru.tech.imageresizershrinker.core.ui.widget.modifier.transparencyChecker
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GradientPreview(
|
internal fun GradientPreview(
|
||||||
brush: ShaderBrush?,
|
brush: ShaderBrush?,
|
||||||
gradientAlpha: Float,
|
gradientAlpha: Float,
|
||||||
allowPickingImage: Boolean?,
|
allowPickingImage: Boolean?,
|
||||||
@ -116,53 +114,3 @@ fun GradientPreview(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GradientPreview(
|
|
||||||
meshGradientState: UiMeshGradientState,
|
|
||||||
gradientAlpha: Float,
|
|
||||||
allowPickingImage: Boolean?,
|
|
||||||
gradientSize: IntegerSize,
|
|
||||||
imageAspectRatio: Float,
|
|
||||||
selectedUri: Uri
|
|
||||||
) {
|
|
||||||
val alpha by animateFloatAsState(gradientAlpha)
|
|
||||||
AnimatedContent(
|
|
||||||
targetState = if (allowPickingImage == true) {
|
|
||||||
imageAspectRatio
|
|
||||||
} else {
|
|
||||||
gradientSize
|
|
||||||
.aspectRatio
|
|
||||||
.roundToTwoDigits()
|
|
||||||
.coerceIn(0.01f..100f)
|
|
||||||
}
|
|
||||||
) { aspectRatio ->
|
|
||||||
Box {
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.aspectRatio(aspectRatio)
|
|
||||||
.clip(MaterialTheme.shapes.medium)
|
|
||||||
.then(
|
|
||||||
if (allowPickingImage != true) {
|
|
||||||
Modifier.transparencyChecker()
|
|
||||||
} else Modifier
|
|
||||||
)
|
|
||||||
.meshGradient(
|
|
||||||
points = meshGradientState.points,
|
|
||||||
resolutionX = meshGradientState.resolutionX,
|
|
||||||
resolutionY = meshGradientState.resolutionY,
|
|
||||||
alpha = alpha
|
|
||||||
)
|
|
||||||
.zIndex(2f)
|
|
||||||
)
|
|
||||||
if (allowPickingImage == true) {
|
|
||||||
Picture(
|
|
||||||
model = selectedUri,
|
|
||||||
modifier = Modifier.matchParentSize(),
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
size = 1500
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* ImageToolbox is an image editor for android
|
||||||
|
* Copyright (c) 2025 T8RIN (Malik Mukhametzyanov)
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the Apache License
|
||||||
|
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
|
import com.smarttoolfactory.colordetector.util.ColorUtil.roundToTwoDigits
|
||||||
|
import ru.tech.imageresizershrinker.core.domain.model.IntegerSize
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.image.Picture
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.modifier.meshGradient
|
||||||
|
import ru.tech.imageresizershrinker.core.ui.widget.modifier.transparencyChecker
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun MeshGradientPreview(
|
||||||
|
meshGradientState: UiMeshGradientState,
|
||||||
|
gradientAlpha: Float,
|
||||||
|
allowPickingImage: Boolean?,
|
||||||
|
gradientSize: IntegerSize,
|
||||||
|
imageAspectRatio: Float,
|
||||||
|
selectedUri: Uri
|
||||||
|
) {
|
||||||
|
val alpha by animateFloatAsState(gradientAlpha)
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = if (allowPickingImage == true) {
|
||||||
|
imageAspectRatio
|
||||||
|
} else {
|
||||||
|
gradientSize
|
||||||
|
.aspectRatio
|
||||||
|
.roundToTwoDigits()
|
||||||
|
.coerceIn(0.01f..100f)
|
||||||
|
}
|
||||||
|
) { aspectRatio ->
|
||||||
|
Box {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.aspectRatio(aspectRatio)
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.then(
|
||||||
|
if (allowPickingImage != true) {
|
||||||
|
Modifier.transparencyChecker()
|
||||||
|
} else Modifier
|
||||||
|
)
|
||||||
|
.meshGradient(
|
||||||
|
points = meshGradientState.points,
|
||||||
|
resolutionX = meshGradientState.resolutionX,
|
||||||
|
resolutionY = meshGradientState.resolutionY,
|
||||||
|
alpha = alpha
|
||||||
|
)
|
||||||
|
.zIndex(2f)
|
||||||
|
)
|
||||||
|
if (allowPickingImage == true) {
|
||||||
|
Picture(
|
||||||
|
model = selectedUri,
|
||||||
|
modifier = Modifier.matchParentSize(),
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
size = 1500
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -90,6 +90,15 @@ class GradientMakerComponent @AssistedInject internal constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val _allowPickingImage: MutableState<Boolean?> = mutableStateOf(
|
||||||
|
if (initialUris.isNullOrEmpty()) null
|
||||||
|
else true
|
||||||
|
)
|
||||||
|
val allowPickingImage by _allowPickingImage
|
||||||
|
|
||||||
|
private val _showOriginal: MutableState<Boolean> = mutableStateOf(false)
|
||||||
|
val showOriginal by _showOriginal
|
||||||
|
|
||||||
private var _gradientState = UiGradientState()
|
private var _gradientState = UiGradientState()
|
||||||
private val gradientState: UiGradientState get() = _gradientState
|
private val gradientState: UiGradientState get() = _gradientState
|
||||||
|
|
||||||
@ -371,8 +380,12 @@ class GradientMakerComponent @AssistedInject internal constructor(
|
|||||||
registerChanges()
|
registerChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setIsMeshGradient(value: Boolean) {
|
fun setIsMeshGradient(
|
||||||
_isMeshGradient.update { value }
|
isMeshGradient: Boolean,
|
||||||
|
allowPickingImage: Boolean
|
||||||
|
) {
|
||||||
|
_isMeshGradient.update { isMeshGradient }
|
||||||
|
_allowPickingImage.update { allowPickingImage }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addColorStop(
|
fun addColorStop(
|
||||||
@ -437,6 +450,7 @@ class GradientMakerComponent @AssistedInject internal constructor(
|
|||||||
_gradientState = UiGradientState()
|
_gradientState = UiGradientState()
|
||||||
_meshGradientState = UiMeshGradientState()
|
_meshGradientState = UiMeshGradientState()
|
||||||
_isMeshGradient.update { false }
|
_isMeshGradient.update { false }
|
||||||
|
_allowPickingImage.update { null }
|
||||||
registerChangesCleared()
|
registerChangesCleared()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -463,6 +477,7 @@ class GradientMakerComponent @AssistedInject internal constructor(
|
|||||||
uris: List<Uri>,
|
uris: List<Uri>,
|
||||||
onFailure: (Throwable) -> Unit = {}
|
onFailure: (Throwable) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
_allowPickingImage.update { true }
|
||||||
_uris.update { uris }
|
_uris.update { uris }
|
||||||
uris.firstOrNull()?.let { updateSelectedUri(it, onFailure) }
|
uris.firstOrNull()?.let { updateSelectedUri(it, onFailure) }
|
||||||
}
|
}
|
||||||
@ -584,6 +599,10 @@ class GradientMakerComponent @AssistedInject internal constructor(
|
|||||||
fun getFormatForFilenameSelection(): ImageFormat? = if (uris.size < 2) imageFormat
|
fun getFormatForFilenameSelection(): ImageFormat? = if (uris.size < 2) imageFormat
|
||||||
else null
|
else null
|
||||||
|
|
||||||
|
fun setShowOriginal(value: Boolean) {
|
||||||
|
_showOriginal.update { value }
|
||||||
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
fun interface Factory {
|
fun interface Factory {
|
||||||
operator fun invoke(
|
operator fun invoke(
|
||||||
|
@ -93,7 +93,6 @@ fun ToneCurvesEditOption(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
ShowOriginalButton(
|
ShowOriginalButton(
|
||||||
canShow = true,
|
|
||||||
onStateChange = {
|
onStateChange = {
|
||||||
showOriginal = it
|
showOriginal = it
|
||||||
}
|
}
|
||||||
|
@ -183,7 +183,6 @@ fun WatermarkingContent(
|
|||||||
}
|
}
|
||||||
if (component.internalBitmap != null) {
|
if (component.internalBitmap != null) {
|
||||||
ShowOriginalButton(
|
ShowOriginalButton(
|
||||||
canShow = true,
|
|
||||||
onStateChange = {
|
onStateChange = {
|
||||||
showOriginal = it
|
showOriginal = it
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user