From 6ddf77123fea0a7884920e0986200316cc80ef09 Mon Sep 17 00:00:00 2001 From: T8RIN Date: Sat, 22 Mar 2025 00:39:02 +0300 Subject: [PATCH] Code refactor --- .../ui/widget/buttons/ShowOriginalButton.kt | 2 +- .../presentation/GradientMakerContent.kt | 512 +----------------- .../GradientMakerAppColorSchemeHandler.kt | 60 ++ .../components/GradientMakerBottomButtons.kt | 90 +++ .../components/GradientMakerCompareButton.kt | 92 ++++ .../components/GradientMakerControls.kt | 218 ++++++++ .../components/GradientMakerImagePreview.kt | 63 +++ .../components/GradientMakerNoDataControls.kt | 168 ++++++ .../components/GradientPreview.kt | 54 +- .../components/MeshGradientPreview.kt | 86 +++ .../screenLogic/GradientMakerComponent.kt | 23 +- .../components/ToneCurvesEditOption.kt | 1 - .../presentation/WatermarkingContent.kt | 1 - 13 files changed, 822 insertions(+), 548 deletions(-) create mode 100644 feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerAppColorSchemeHandler.kt create mode 100644 feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerBottomButtons.kt create mode 100644 feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerCompareButton.kt create mode 100644 feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerControls.kt create mode 100644 feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerImagePreview.kt create mode 100644 feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerNoDataControls.kt create mode 100644 feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/MeshGradientPreview.kt diff --git a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/buttons/ShowOriginalButton.kt b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/buttons/ShowOriginalButton.kt index a5facaa3f..dd8eaa22e 100644 --- a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/buttons/ShowOriginalButton.kt +++ b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/buttons/ShowOriginalButton.kt @@ -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 diff --git a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/GradientMakerContent.kt b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/GradientMakerContent.kt index 8e0601035..98336ab79 100644 --- a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/GradientMakerContent.kt +++ b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/GradientMakerContent.kt @@ -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 -> - 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 - } - ) - } - 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)) - } - } + GradientMakerNoDataControls( + component = component, + onPickImage = pickImage + ) }, 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() diff --git a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerAppColorSchemeHandler.kt b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerAppColorSchemeHandler.kt new file mode 100644 index 000000000..48f0fd69a --- /dev/null +++ b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerAppColorSchemeHandler.kt @@ -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 . + */ + +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 + ) + } + } + } +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerBottomButtons.kt b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerBottomButtons.kt new file mode 100644 index 000000000..f846c95b8 --- /dev/null +++ b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerBottomButtons.kt @@ -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 . + */ + +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 + ) +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerCompareButton.kt b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerCompareButton.kt new file mode 100644 index 000000000..692dcd7ef --- /dev/null +++ b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerCompareButton.kt @@ -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 . + */ + +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 + } + ) +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerControls.kt b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerControls.kt new file mode 100644 index 000000000..a58eb82c2 --- /dev/null +++ b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerControls.kt @@ -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 . + */ + +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, + ) +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerImagePreview.kt b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerImagePreview.kt new file mode 100644 index 000000000..99b406b2c --- /dev/null +++ b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerImagePreview.kt @@ -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 . + */ + +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 + ) + } + } +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerNoDataControls.kt b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerNoDataControls.kt new file mode 100644 index 000000000..f8d3ceaca --- /dev/null +++ b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientMakerNoDataControls.kt @@ -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 . + */ + +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)) + } + } +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientPreview.kt b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientPreview.kt index 22d597227..0b5fe2667 100644 --- a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientPreview.kt +++ b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/GradientPreview.kt @@ -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?, @@ -115,54 +113,4 @@ 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 - ) - } - } - } } \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/MeshGradientPreview.kt b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/MeshGradientPreview.kt new file mode 100644 index 000000000..153b08dbe --- /dev/null +++ b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/components/MeshGradientPreview.kt @@ -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 . + */ + +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 + ) + } + } + } +} \ No newline at end of file diff --git a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/screenLogic/GradientMakerComponent.kt b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/screenLogic/GradientMakerComponent.kt index 031c3112d..adea2cfab 100644 --- a/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/screenLogic/GradientMakerComponent.kt +++ b/feature/gradient-maker/src/main/java/ru/tech/imageresizershrinker/feature/gradient_maker/presentation/screenLogic/GradientMakerComponent.kt @@ -90,6 +90,15 @@ class GradientMakerComponent @AssistedInject internal constructor( } } + private val _allowPickingImage: MutableState = mutableStateOf( + if (initialUris.isNullOrEmpty()) null + else true + ) + val allowPickingImage by _allowPickingImage + + private val _showOriginal: MutableState = 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, 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( diff --git a/feature/single-edit/src/main/java/ru/tech/imageresizershrinker/feature/single_edit/presentation/components/ToneCurvesEditOption.kt b/feature/single-edit/src/main/java/ru/tech/imageresizershrinker/feature/single_edit/presentation/components/ToneCurvesEditOption.kt index 550a4788e..dd23bfee6 100644 --- a/feature/single-edit/src/main/java/ru/tech/imageresizershrinker/feature/single_edit/presentation/components/ToneCurvesEditOption.kt +++ b/feature/single-edit/src/main/java/ru/tech/imageresizershrinker/feature/single_edit/presentation/components/ToneCurvesEditOption.kt @@ -93,7 +93,6 @@ fun ToneCurvesEditOption( ) } ShowOriginalButton( - canShow = true, onStateChange = { showOriginal = it } diff --git a/feature/watermarking/src/main/java/ru/tech/imageresizershrinker/feature/watermarking/presentation/WatermarkingContent.kt b/feature/watermarking/src/main/java/ru/tech/imageresizershrinker/feature/watermarking/presentation/WatermarkingContent.kt index 2db1d3dbf..183726fb0 100644 --- a/feature/watermarking/src/main/java/ru/tech/imageresizershrinker/feature/watermarking/presentation/WatermarkingContent.kt +++ b/feature/watermarking/src/main/java/ru/tech/imageresizershrinker/feature/watermarking/presentation/WatermarkingContent.kt @@ -183,7 +183,6 @@ fun WatermarkingContent( } if (component.internalBitmap != null) { ShowOriginalButton( - canShow = true, onStateChange = { showOriginal = it }