Code refactor

This commit is contained in:
T8RIN
2025-03-22 00:39:02 +03:00
parent 0cd5784f0d
commit 6ddf77123f
13 changed files with 822 additions and 548 deletions

View File

@ -44,7 +44,7 @@ import ru.tech.imageresizershrinker.core.ui.widget.modifier.shapeByInteraction
@Composable
fun ShowOriginalButton(
canShow: Boolean,
canShow: Boolean = true,
onStateChange: (Boolean) -> Unit
) {
val haptics = LocalHapticFeedback.current

View File

@ -18,91 +18,38 @@
package ru.tech.imageresizershrinker.feature.gradient_maker.presentation
import android.net.Uri
import androidx.compose.animation.AnimatedContent
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.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.LaunchedEffect
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.domain.model.IntegerSize
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.helper.asClip
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.rememberLocalEssentials
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.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.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.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.text.TitleItem
import ru.tech.imageresizershrinker.core.ui.widget.text.TopAppBarTitle
import ru.tech.imageresizershrinker.core.ui.widget.utils.AutoContentBasedColors
import ru.tech.imageresizershrinker.feature.compare.presentation.components.CompareSheet
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.ColorStopSelection
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientPreview
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientPropertiesSelector
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientSizeSelector
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.components.GradientMakerAppColorSchemeHandler
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientMakerBottomButtons
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientMakerCompareButton
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientMakerControls
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientMakerImagePreview
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.components.GradientMakerNoDataControls
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent
import kotlin.math.roundToInt
@Composable
fun GradientMakerContent(
@ -113,23 +60,9 @@ fun GradientMakerContent(
val essentials = rememberLocalEssentials()
val showConfetti: () -> Unit = essentials::showConfetti
var allowPickingImage by rememberSaveable(component.initialUris) {
mutableStateOf(
if (component.initialUris.isNullOrEmpty()) null
else true
)
}
val allowPickingImage = component.allowPickingImage
AutoContentBasedColors(
model = Triple(component.brush, component.meshPoints, component.selectedUri),
selector = { (_, uri) ->
component.createGradientBitmap(
data = uri,
integerSize = IntegerSize(1000, 1000)
)
},
allowChangeColor = allowPickingImage != null
)
GradientMakerAppColorSchemeHandler(component)
LaunchedEffect(allowPickingImage) {
if (allowPickingImage != true && !component.isMeshGradient) {
@ -139,29 +72,7 @@ fun GradientMakerContent(
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> ->
allowPickingImage = true
component.setUris(
uris = uris,
onFailure = essentials::showFailureToast
@ -173,11 +84,6 @@ fun GradientMakerContent(
val isPortrait by isPortraitOrientationAsState()
var showPickImageFromUrisSheet by rememberSaveable { mutableStateOf(false) }
var showCompareSheet by rememberSaveable { mutableStateOf(false) }
var showOriginal by rememberSaveable { mutableStateOf(false) }
AdaptiveLayoutScreen(
shouldDisableBackHandler = !component.haveChanges,
isPortrait = isPortrait,
@ -209,10 +115,7 @@ fun GradientMakerContent(
actions = {
if (component.uris.isNotEmpty()) {
ShowOriginalButton(
canShow = true,
onStateChange = {
showOriginal = it
}
onStateChange = component::setShowOriginal
)
}
var editSheetData by remember {
@ -248,406 +151,36 @@ fun GradientMakerContent(
if (allowPickingImage == null) {
TopAppBarEmoji()
}
CompareButton(
onClick = { showCompareSheet = true },
visible = component.brush != null && allowPickingImage == true && component.selectedUri != Uri.EMPTY
)
GradientMakerCompareButton(component)
},
imagePreview = {
Box(
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
)
}
}
GradientMakerImagePreview(component)
},
controls = {
ImageCounter(
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
)
GradientMakerControls(component)
},
insetsForNoData = WindowInsets(0),
noDataControls = {
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(false)
allowPickingImage = false
}
GradientMakerNoDataControls(
component = component,
onPickImage = pickImage
)
}
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 ->
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 = (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
GradientMakerBottomButtons(
component = component,
actions = actions,
imagePicker = imagePicker
)
},
forceImagePreviewToMax = showOriginal,
forceImagePreviewToMax = component.showOriginal,
contentPadding = animateDpAsState(
if (allowPickingImage == null) 12.dp
else 20.dp
).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(
visible = component.isSaving || component.isImageLoading,
done = component.done,
@ -659,7 +192,6 @@ fun GradientMakerContent(
ExitWithoutSavingDialog(
onExit = {
if (allowPickingImage != null) {
allowPickingImage = null
component.resetState()
} else {
component.onGoBack()

View File

@ -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
)
}
}
}
}

View File

@ -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
)
}

View File

@ -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
}
)
}

View File

@ -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,
)
}

View File

@ -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
)
}
}
}

View File

@ -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))
}
}
}

View File

@ -22,7 +22,6 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
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.foundation.layout.fillMaxSize
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.resources.icons.BrokenImageAlt
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
fun GradientPreview(
internal fun GradientPreview(
brush: ShaderBrush?,
gradientAlpha: Float,
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
)
}
}
}
}

View File

@ -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
)
}
}
}
}

View File

@ -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 val gradientState: UiGradientState get() = _gradientState
@ -371,8 +380,12 @@ class GradientMakerComponent @AssistedInject internal constructor(
registerChanges()
}
fun setIsMeshGradient(value: Boolean) {
_isMeshGradient.update { value }
fun setIsMeshGradient(
isMeshGradient: Boolean,
allowPickingImage: Boolean
) {
_isMeshGradient.update { isMeshGradient }
_allowPickingImage.update { allowPickingImage }
}
fun addColorStop(
@ -437,6 +450,7 @@ class GradientMakerComponent @AssistedInject internal constructor(
_gradientState = UiGradientState()
_meshGradientState = UiMeshGradientState()
_isMeshGradient.update { false }
_allowPickingImage.update { null }
registerChangesCleared()
}
}
@ -463,6 +477,7 @@ class GradientMakerComponent @AssistedInject internal constructor(
uris: List<Uri>,
onFailure: (Throwable) -> Unit = {}
) {
_allowPickingImage.update { true }
_uris.update { uris }
uris.firstOrNull()?.let { updateSelectedUri(it, onFailure) }
}
@ -584,6 +599,10 @@ class GradientMakerComponent @AssistedInject internal constructor(
fun getFormatForFilenameSelection(): ImageFormat? = if (uris.size < 2) imageFormat
else null
fun setShowOriginal(value: Boolean) {
_showOriginal.update { value }
}
@AssistedFactory
fun interface Factory {
operator fun invoke(

View File

@ -93,7 +93,6 @@ fun ToneCurvesEditOption(
)
}
ShowOriginalButton(
canShow = true,
onStateChange = {
showOriginal = it
}

View File

@ -183,7 +183,6 @@ fun WatermarkingContent(
}
if (component.internalBitmap != null) {
ShowOriginalButton(
canShow = true,
onStateChange = {
showOriginal = it
}