mirror of
https://github.com/T8RIN/ImageToolbox.git
synced 2025-05-17 21:45:59 +08:00
Added Collage Maker by #881
This commit is contained in:
@ -121,11 +121,8 @@ internal class AndroidFileController @Inject constructor(
|
||||
}
|
||||
|
||||
val originalUri = saveTarget.originalUri.toUri()
|
||||
val hasOriginalUri = runCatching {
|
||||
context.contentResolver.openFileDescriptor(originalUri, "r")
|
||||
}.isSuccess
|
||||
|
||||
if (settingsState.overwriteFiles && hasOriginalUri) {
|
||||
if (settingsState.overwriteFiles) {
|
||||
runCatching {
|
||||
if (originalUri == Uri.EMPTY) throw IllegalStateException()
|
||||
|
||||
@ -258,7 +255,8 @@ internal class AndroidFileController @Inject constructor(
|
||||
|
||||
return@withContext SaveResult.Success(
|
||||
message = if (savingPath.isNotEmpty()) {
|
||||
val isFile = documentFile?.isDirectory != true
|
||||
val isFile =
|
||||
(documentFile?.isDirectory != true && oneTimeSaveLocationUri != null)
|
||||
if (isFile) {
|
||||
context.getString(R.string.saved_to_custom)
|
||||
} else if (filename.isNotEmpty()) {
|
||||
|
@ -1406,4 +1406,8 @@
|
||||
<string name="custom_filename">Custom Filename</string>
|
||||
<string name="custom_filename_sub">Select location and filename which are will be used to save current image</string>
|
||||
<string name="saved_to_custom">Saved to folder with custom name</string>
|
||||
<string name="collage_maker">Collage Maker</string>
|
||||
<string name="collage_maker_sub">Make various collages from 2..10 images</string>
|
||||
<string name="collage_type">Collage Type</string>
|
||||
<string name="pick_images_collage">Pick 2..10 images</string>
|
||||
</resources>
|
@ -25,6 +25,7 @@ import androidx.compose.material.icons.automirrored.outlined.BrandingWatermark
|
||||
import androidx.compose.material.icons.filled.AutoAwesome
|
||||
import androidx.compose.material.icons.filled.FilterHdr
|
||||
import androidx.compose.material.icons.outlined.AutoAwesome
|
||||
import androidx.compose.material.icons.outlined.AutoAwesomeMosaic
|
||||
import androidx.compose.material.icons.outlined.AutoFixHigh
|
||||
import androidx.compose.material.icons.outlined.Collections
|
||||
import androidx.compose.material.icons.outlined.ColorLens
|
||||
@ -118,6 +119,7 @@ sealed class Screen(
|
||||
is ColorTools -> "Color_Tools"
|
||||
is WebpTools -> "WEBP_Tools"
|
||||
is NoiseGeneration -> "Noise_Generation"
|
||||
is CollageMaker -> "Collage_Maker"
|
||||
}
|
||||
|
||||
val icon: ImageVector?
|
||||
@ -159,6 +161,7 @@ sealed class Screen(
|
||||
ColorTools -> Icons.Outlined.ColorLens
|
||||
is WebpTools -> Icons.Rounded.WebpBox
|
||||
NoiseGeneration -> Icons.Outlined.Grain
|
||||
is CollageMaker -> Icons.Outlined.AutoAwesomeMosaic
|
||||
}
|
||||
|
||||
data object Settings : Screen(
|
||||
@ -707,6 +710,14 @@ sealed class Screen(
|
||||
subtitle = R.string.noise_generation_sub
|
||||
)
|
||||
|
||||
data class CollageMaker(
|
||||
val uris: List<Uri>? = null
|
||||
) : Screen(
|
||||
id = 33,
|
||||
title = R.string.collage_maker,
|
||||
subtitle = R.string.collage_maker_sub
|
||||
)
|
||||
|
||||
companion object {
|
||||
val typedEntries by lazy {
|
||||
listOf(
|
||||
@ -726,6 +737,7 @@ sealed class Screen(
|
||||
Filter(),
|
||||
Draw(),
|
||||
EraseBackground(),
|
||||
CollageMaker(),
|
||||
ImageStitching(),
|
||||
ImageStacking(),
|
||||
ImageSplitting(),
|
||||
@ -773,6 +785,6 @@ sealed class Screen(
|
||||
typedEntries.flatMap { it.first }.sortedBy { it.id }
|
||||
}
|
||||
|
||||
const val FEATURES_COUNT = 52
|
||||
const val FEATURES_COUNT = 53
|
||||
}
|
||||
}
|
@ -40,6 +40,18 @@ fun DrawLockScreenOrientation() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LockScreenOrientation() {
|
||||
val activity = LocalContext.current as Activity
|
||||
DisposableEffect(activity) {
|
||||
val originalOrientation = activity.requestedOrientation
|
||||
activity.lockOrientation()
|
||||
onDispose {
|
||||
activity.requestedOrientation = originalOrientation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Activity.lockOrientation() {
|
||||
val display = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
||||
display
|
||||
|
@ -174,6 +174,9 @@ internal fun List<Uri>.screenList(
|
||||
add(Screen.ImageStitching(uris))
|
||||
add(Screen.PdfTools(Screen.PdfTools.Type.ImagesToPdf(uris)))
|
||||
if (uris.size == 2) add(Screen.Compare(uris))
|
||||
if (uris.size in 2..10) {
|
||||
add(Screen.CollageMaker(uris))
|
||||
}
|
||||
add(Screen.GradientMaker(uris))
|
||||
add(Screen.Watermarking(uris))
|
||||
add(
|
||||
|
1
feature/colllage-maker/.gitignore
vendored
Normal file
1
feature/colllage-maker/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
12
feature/colllage-maker/build.gradle.kts
Normal file
12
feature/colllage-maker/build.gradle.kts
Normal file
@ -0,0 +1,12 @@
|
||||
plugins {
|
||||
alias(libs.plugins.image.toolbox.library)
|
||||
alias(libs.plugins.image.toolbox.feature)
|
||||
alias(libs.plugins.image.toolbox.hilt)
|
||||
alias(libs.plugins.image.toolbox.compose)
|
||||
}
|
||||
|
||||
android.namespace = "ru.tech.imageresizershrinker.feature.collage_maker"
|
||||
|
||||
dependencies {
|
||||
implementation(libs.toolbox.collages)
|
||||
}
|
4
feature/colllage-maker/src/main/AndroidManifest.xml
Normal file
4
feature/colllage-maker/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,544 @@
|
||||
/*
|
||||
* ImageToolbox is an image editor for android
|
||||
* Copyright (c) 2024 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.colllage_maker.presentation
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.AutoAwesomeMosaic
|
||||
import androidx.compose.material.icons.rounded.FormatLineSpacing
|
||||
import androidx.compose.material.icons.rounded.RoundedCorner
|
||||
import androidx.compose.material.icons.rounded.Tune
|
||||
import androidx.compose.material3.BottomSheetScaffold
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||
import androidx.compose.material3.rememberStandardBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.t8rin.collages.Collage
|
||||
import com.t8rin.collages.CollageTypeSelection
|
||||
import dev.olshevski.navigation.reimagined.hilt.hiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.tech.imageresizershrinker.colllage_maker.presentation.viewModel.CollageMakerViewModel
|
||||
import ru.tech.imageresizershrinker.core.resources.R
|
||||
import ru.tech.imageresizershrinker.core.settings.presentation.provider.LocalSettingsState
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostState
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.Picker
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.asClip
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.localImagePickerMode
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.parseSaveResult
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.rememberImagePicker
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.provider.ProvideContainerDefaults
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.BottomButtonsBlock
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.EnhancedIconButton
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.ShareButton
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.controls.EnhancedSliderItem
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.controls.selection.BackgroundColorSelector
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.controls.selection.ImageFormatSelector
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.controls.selection.QualitySelector
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.ExitWithoutSavingDialog
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.image.AutoFilePicker
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.image.ImageNotPickedWidget
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.modifier.container
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.modifier.fadingEdges
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.modifier.transparencyChecker
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.other.EnhancedTopAppBar
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.other.EnhancedTopAppBarType
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.other.LoadingDialog
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.other.LocalToastHostState
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.other.LockScreenOrientation
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.sheets.ProcessImagesPreferenceSheet
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.sheets.SimpleSheetDefaults
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.text.TopAppBarTitle
|
||||
|
||||
@Composable
|
||||
fun CollageMakerContent(
|
||||
uriState: List<Uri>?,
|
||||
onGoBack: () -> Unit,
|
||||
onNavigate: (Screen) -> Unit,
|
||||
viewModel: CollageMakerViewModel = hiltViewModel()
|
||||
) {
|
||||
LockScreenOrientation()
|
||||
val context = LocalContext.current as ComponentActivity
|
||||
val toastHostState = LocalToastHostState.current
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val confettiHostState = LocalConfettiHostState.current
|
||||
val showConfetti: () -> Unit = {
|
||||
scope.launch {
|
||||
confettiHostState.showConfetti()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(uriState) {
|
||||
uriState?.takeIf { it.isNotEmpty() }?.let {
|
||||
if (it.size in 2..10) {
|
||||
viewModel.updateUris(it)
|
||||
} else {
|
||||
scope.launch {
|
||||
toastHostState.showToast(
|
||||
message = context.getString(R.string.pick_images_collage),
|
||||
icon = Icons.Outlined.AutoAwesomeMosaic
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val pickImageLauncher = rememberImagePicker(
|
||||
mode = localImagePickerMode(Picker.Multiple)
|
||||
) { list ->
|
||||
list.takeIf { it.isNotEmpty() }?.let {
|
||||
if (list.size in 2..10) {
|
||||
viewModel.updateUris(list)
|
||||
} else {
|
||||
scope.launch {
|
||||
toastHostState.showToast(
|
||||
message = context.getString(R.string.pick_images_collage),
|
||||
icon = Icons.Outlined.AutoAwesomeMosaic
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val pickImage = pickImageLauncher::pickImage
|
||||
|
||||
AutoFilePicker(
|
||||
onAutoPick = pickImage,
|
||||
isPickedAlready = !uriState.isNullOrEmpty()
|
||||
)
|
||||
|
||||
val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = {
|
||||
viewModel.saveBitmap(it) { saveResult ->
|
||||
context.parseSaveResult(
|
||||
scope = scope,
|
||||
saveResult = saveResult,
|
||||
toastHostState = toastHostState,
|
||||
onSuccess = showConfetti
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val isPortrait by isPortraitOrientationAsState()
|
||||
|
||||
var showExitDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val onBack = {
|
||||
if (viewModel.haveChanges) showExitDialog = true
|
||||
else onGoBack()
|
||||
}
|
||||
|
||||
val scaffoldState = rememberBottomSheetScaffoldState(
|
||||
bottomSheetState = rememberStandardBottomSheetState(
|
||||
confirmValueChange = {
|
||||
when (it) {
|
||||
SheetValue.Hidden -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
val focus = LocalFocusManager.current
|
||||
|
||||
LaunchedEffect(scaffoldState.bottomSheetState.currentValue) {
|
||||
if (scaffoldState.bottomSheetState.currentValue != SheetValue.Expanded) {
|
||||
focus.clearFocus()
|
||||
}
|
||||
}
|
||||
|
||||
val collagePreview: @Composable () -> Unit = {
|
||||
Box(
|
||||
modifier = Modifier.container(
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
resultPadding = 0.dp
|
||||
)
|
||||
) {
|
||||
Collage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(4.dp)
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.transparencyChecker(),
|
||||
images = viewModel.uris ?: emptyList(),
|
||||
collageType = viewModel.collageType,
|
||||
collageCreationTrigger = viewModel.collageCreationTrigger,
|
||||
onCollageCreated = viewModel::updateCollageBitmap,
|
||||
backgroundColor = viewModel.backgroundColor,
|
||||
spacing = viewModel.spacing,
|
||||
cornerRadius = viewModel.cornerRadius
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val controls: @Composable () -> Unit = {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.container(
|
||||
resultPadding = 0.dp,
|
||||
shape = RoundedCornerShape(24.dp)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
fontWeight = FontWeight.Medium,
|
||||
text = stringResource(R.string.collage_type),
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
fontSize = 18.sp
|
||||
)
|
||||
val state = rememberLazyListState()
|
||||
CollageTypeSelection(
|
||||
state = state,
|
||||
imagesCount = viewModel.uris?.size ?: 0,
|
||||
value = viewModel.collageType,
|
||||
onValueChange = viewModel::setCollageType,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(100.dp)
|
||||
.fadingEdges(state),
|
||||
contentPadding = PaddingValues(10.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
itemModifierFactory = { isSelected ->
|
||||
Modifier
|
||||
.container(
|
||||
resultPadding = 0.dp,
|
||||
color = animateColorAsState(
|
||||
targetValue = if (isSelected) {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
} else MaterialTheme.colorScheme.surfaceContainerLowest,
|
||||
).value,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(8.dp)
|
||||
.clip(RoundedCornerShape(2.dp))
|
||||
}
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
BackgroundColorSelector(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.container(
|
||||
shape = RoundedCornerShape(24.dp),
|
||||
resultPadding = 0.dp
|
||||
),
|
||||
value = viewModel.backgroundColor,
|
||||
onValueChange = viewModel::setBackgroundColor
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
EnhancedSliderItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = viewModel.spacing,
|
||||
title = stringResource(R.string.spacing),
|
||||
valueRange = 0f..50f,
|
||||
onValueChange = viewModel::setSpacing,
|
||||
sliderModifier = Modifier
|
||||
.padding(
|
||||
top = 14.dp,
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
bottom = 10.dp
|
||||
),
|
||||
icon = Icons.Rounded.FormatLineSpacing,
|
||||
shape = RoundedCornerShape(24.dp)
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
EnhancedSliderItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = viewModel.cornerRadius,
|
||||
title = stringResource(R.string.corners),
|
||||
valueRange = 0f..50f,
|
||||
onValueChange = viewModel::setCornerRadius,
|
||||
sliderModifier = Modifier
|
||||
.padding(
|
||||
top = 14.dp,
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
bottom = 10.dp
|
||||
),
|
||||
icon = Icons.Rounded.RoundedCorner,
|
||||
shape = RoundedCornerShape(24.dp)
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
QualitySelector(
|
||||
imageFormat = viewModel.imageFormat,
|
||||
quality = viewModel.quality,
|
||||
onQualityChange = viewModel::setQuality
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
ImageFormatSelector(
|
||||
value = viewModel.imageFormat,
|
||||
onValueChange = viewModel::setImageFormat,
|
||||
forceEnabled = true
|
||||
)
|
||||
}
|
||||
|
||||
val actions: @Composable RowScope.() -> Unit = {
|
||||
var editSheetData by remember {
|
||||
mutableStateOf(listOf<Uri>())
|
||||
}
|
||||
EnhancedIconButton(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = LocalContentColor.current,
|
||||
enableAutoShadowAndBorder = false,
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded) {
|
||||
scaffoldState.bottomSheetState.partialExpand()
|
||||
} else {
|
||||
scaffoldState.bottomSheetState.expand()
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Tune,
|
||||
contentDescription = stringResource(R.string.properties)
|
||||
)
|
||||
}
|
||||
ShareButton(
|
||||
onShare = {
|
||||
viewModel.performSharing(showConfetti)
|
||||
},
|
||||
onCopy = { manager ->
|
||||
viewModel.cacheImage { uri ->
|
||||
manager.setClip(uri.asClip(context))
|
||||
showConfetti()
|
||||
}
|
||||
},
|
||||
onEdit = {
|
||||
viewModel.cacheImage {
|
||||
editSheetData = listOf(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
ProcessImagesPreferenceSheet(
|
||||
uris = editSheetData,
|
||||
visible = editSheetData.isNotEmpty(),
|
||||
onDismiss = {
|
||||
if (!it) {
|
||||
editSheetData = emptyList()
|
||||
}
|
||||
},
|
||||
onNavigate = { screen ->
|
||||
scope.launch {
|
||||
editSheetData = emptyList()
|
||||
delay(200)
|
||||
onNavigate(screen)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val buttons: @Composable () -> Unit = {
|
||||
var showFolderSelectionDialog by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
BottomButtonsBlock(
|
||||
targetState = (viewModel.uris.isNullOrEmpty()) to isPortrait,
|
||||
onSecondaryButtonClick = pickImage,
|
||||
onPrimaryButtonClick = {
|
||||
saveBitmaps(null)
|
||||
},
|
||||
onPrimaryButtonLongClick = {
|
||||
showFolderSelectionDialog = true
|
||||
},
|
||||
actions = {
|
||||
if (isPortrait) actions()
|
||||
}
|
||||
)
|
||||
if (showFolderSelectionDialog) {
|
||||
OneTimeSaveLocationSelectionDialog(
|
||||
onDismiss = { showFolderSelectionDialog = false },
|
||||
onSaveRequest = saveBitmaps,
|
||||
formatForFilenameSelection = viewModel.getFormatForFilenameSelection()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val noDataControls: @Composable () -> Unit = {
|
||||
if (!viewModel.isImageLoading) {
|
||||
ImageNotPickedWidget(onPickImage = pickImage)
|
||||
}
|
||||
}
|
||||
|
||||
val topAppBar: @Composable () -> Unit = {
|
||||
EnhancedTopAppBar(
|
||||
title = {
|
||||
TopAppBarTitle(
|
||||
title = stringResource(R.string.collage_maker),
|
||||
input = viewModel.uris,
|
||||
isLoading = viewModel.isImageLoading,
|
||||
size = null
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
EnhancedIconButton(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = LocalContentColor.current,
|
||||
enableAutoShadowAndBorder = false,
|
||||
onClick = onBack
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
contentDescription = stringResource(R.string.exit)
|
||||
)
|
||||
}
|
||||
},
|
||||
type = if (viewModel.uris.isNullOrEmpty()) EnhancedTopAppBarType.Large
|
||||
else EnhancedTopAppBarType.Normal
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedContent(viewModel.uris.isNullOrEmpty()) { noData ->
|
||||
if (noData) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Column {
|
||||
topAppBar()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(20.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
noDataControls()
|
||||
}
|
||||
}
|
||||
val settingsState = LocalSettingsState.current
|
||||
if (isPortrait) {
|
||||
Box(
|
||||
modifier = Modifier.align(settingsState.fabAlignment)
|
||||
) {
|
||||
buttons()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BottomSheetScaffold(
|
||||
sheetContent = {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxHeight(0.6f)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures { focus.clearFocus() }
|
||||
}
|
||||
) {
|
||||
buttons()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
ProvideContainerDefaults(
|
||||
color = SimpleSheetDefaults.contentContainerColor
|
||||
) {
|
||||
controls()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
sheetPeekHeight = 80.dp + WindowInsets.navigationBars.asPaddingValues()
|
||||
.calculateBottomPadding(),
|
||||
sheetDragHandle = null,
|
||||
sheetShape = RectangleShape,
|
||||
scaffoldState = scaffoldState
|
||||
) {
|
||||
Column(modifier = Modifier.padding(it)) {
|
||||
topAppBar()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.padding(20.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
collagePreview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(onBack = onBack)
|
||||
|
||||
|
||||
|
||||
ExitWithoutSavingDialog(
|
||||
onExit = onGoBack,
|
||||
onDismiss = { showExitDialog = false },
|
||||
visible = showExitDialog
|
||||
)
|
||||
|
||||
if (viewModel.isSaving || viewModel.isImageLoading) {
|
||||
LoadingDialog(
|
||||
onCancelLoading = viewModel::cancelSaving
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,254 @@
|
||||
/*
|
||||
* ImageToolbox is an image editor for android
|
||||
* Copyright (c) 2024 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.colllage_maker.presentation.viewModel
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.core.net.toUri
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.t8rin.collages.CollageType
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Job
|
||||
import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder
|
||||
import ru.tech.imageresizershrinker.core.domain.image.ImageCompressor
|
||||
import ru.tech.imageresizershrinker.core.domain.image.ImageGetter
|
||||
import ru.tech.imageresizershrinker.core.domain.image.ShareProvider
|
||||
import ru.tech.imageresizershrinker.core.domain.image.model.ImageFormat
|
||||
import ru.tech.imageresizershrinker.core.domain.image.model.ImageInfo
|
||||
import ru.tech.imageresizershrinker.core.domain.image.model.Quality
|
||||
import ru.tech.imageresizershrinker.core.domain.model.IntegerSize
|
||||
import ru.tech.imageresizershrinker.core.domain.saving.FileController
|
||||
import ru.tech.imageresizershrinker.core.domain.saving.model.ImageSaveTarget
|
||||
import ru.tech.imageresizershrinker.core.domain.saving.model.SaveResult
|
||||
import ru.tech.imageresizershrinker.core.domain.utils.smartJob
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.BaseViewModel
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.state.update
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class CollageMakerViewModel @Inject constructor(
|
||||
private val fileController: FileController,
|
||||
private val imageCompressor: ImageCompressor<Bitmap>,
|
||||
private val shareProvider: ShareProvider<Bitmap>,
|
||||
private val imageGetter: ImageGetter<Bitmap, ExifInterface>,
|
||||
dispatchersHolder: DispatchersHolder
|
||||
) : BaseViewModel(dispatchersHolder) {
|
||||
|
||||
private val _spacing = mutableFloatStateOf(0f)
|
||||
val spacing: Float by _spacing
|
||||
|
||||
private val _cornerRadius = mutableFloatStateOf(0f)
|
||||
val cornerRadius: Float by _cornerRadius
|
||||
|
||||
private val _backgroundColor = mutableStateOf(Color.Black)
|
||||
val backgroundColor: Color by _backgroundColor
|
||||
|
||||
private val _collageCreationTrigger = mutableStateOf(false)
|
||||
val collageCreationTrigger by _collageCreationTrigger
|
||||
|
||||
private val _collageType: MutableState<CollageType> = mutableStateOf(CollageType.Empty)
|
||||
val collageType by _collageType
|
||||
|
||||
private val _collageBitmap = mutableStateOf<Bitmap?>(null)
|
||||
private val collageBitmap by _collageBitmap
|
||||
|
||||
private val _uris = mutableStateOf<List<Uri>?>(null)
|
||||
val uris by _uris
|
||||
|
||||
private val _imageFormat: MutableState<ImageFormat> = mutableStateOf(ImageFormat.Default)
|
||||
val imageFormat: ImageFormat by _imageFormat
|
||||
|
||||
private val _quality: MutableState<Quality> = mutableStateOf(Quality.Base())
|
||||
val quality: Quality by _quality
|
||||
|
||||
private val _isSaving: MutableState<Boolean> = mutableStateOf(false)
|
||||
val isSaving: Boolean by _isSaving
|
||||
|
||||
private var requestedOperation: () -> Unit = {}
|
||||
|
||||
fun setCollageType(collageType: CollageType) {
|
||||
_collageType.update { collageType }
|
||||
registerChanges()
|
||||
}
|
||||
|
||||
fun updateCollageBitmap(bitmap: Bitmap) {
|
||||
_collageCreationTrigger.update { false }
|
||||
_collageBitmap.update { bitmap }
|
||||
requestedOperation()
|
||||
}
|
||||
|
||||
fun updateUris(uris: List<Uri>?) {
|
||||
viewModelScope.launch {
|
||||
_isImageLoading.update { true }
|
||||
_uris.update {
|
||||
uris?.mapNotNull {
|
||||
val image =
|
||||
imageGetter.getImage(it, IntegerSize(2000, 2000)) ?: return@mapNotNull null
|
||||
|
||||
shareProvider.cacheImage(
|
||||
image = image,
|
||||
imageInfo = ImageInfo(
|
||||
width = image.width,
|
||||
height = image.height,
|
||||
quality = Quality.Base(100),
|
||||
imageFormat = ImageFormat.Png.Lossless
|
||||
)
|
||||
)?.toUri()
|
||||
}
|
||||
}
|
||||
_isImageLoading.update { false }
|
||||
}
|
||||
}
|
||||
|
||||
fun setQuality(quality: Quality) {
|
||||
_quality.update { quality }
|
||||
registerChanges()
|
||||
}
|
||||
|
||||
fun setImageFormat(imageFormat: ImageFormat) {
|
||||
_imageFormat.update { imageFormat }
|
||||
registerChanges()
|
||||
}
|
||||
|
||||
private var savingJob: Job? by smartJob {
|
||||
_isSaving.update { false }
|
||||
}
|
||||
|
||||
fun saveBitmap(
|
||||
oneTimeSaveLocationUri: String?,
|
||||
onComplete: (SaveResult) -> Unit
|
||||
) {
|
||||
_isSaving.update { true }
|
||||
_collageCreationTrigger.update { true }
|
||||
requestedOperation = {
|
||||
savingJob = viewModelScope.launch(defaultDispatcher) {
|
||||
collageBitmap?.let { image ->
|
||||
_isSaving.update { true }
|
||||
val imageInfo = ImageInfo(
|
||||
width = image.width,
|
||||
height = image.height,
|
||||
quality = quality,
|
||||
imageFormat = imageFormat
|
||||
)
|
||||
val result = fileController.save(
|
||||
saveTarget = ImageSaveTarget<ExifInterface>(
|
||||
imageInfo = imageInfo,
|
||||
originalUri = "",
|
||||
sequenceNumber = null,
|
||||
data = imageCompressor.compress(
|
||||
image = image,
|
||||
imageFormat = imageFormat,
|
||||
quality = quality
|
||||
)
|
||||
),
|
||||
keepOriginalMetadata = false,
|
||||
oneTimeSaveLocationUri = oneTimeSaveLocationUri
|
||||
)
|
||||
|
||||
onComplete(result.onSuccess(::registerSave))
|
||||
_isSaving.update { false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun performSharing(
|
||||
onComplete: () -> Unit
|
||||
) {
|
||||
_isSaving.update { true }
|
||||
_collageCreationTrigger.update { true }
|
||||
requestedOperation = {
|
||||
collageBitmap?.let { image ->
|
||||
savingJob = viewModelScope.launch {
|
||||
_isSaving.update { true }
|
||||
shareProvider.cacheImage(
|
||||
image = image,
|
||||
imageInfo = ImageInfo(
|
||||
width = image.width,
|
||||
height = image.height,
|
||||
quality = quality,
|
||||
imageFormat = imageFormat
|
||||
)
|
||||
)?.let { uri ->
|
||||
shareProvider.shareUri(
|
||||
uri = uri,
|
||||
onComplete = onComplete
|
||||
)
|
||||
}
|
||||
_isSaving.update { false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun cacheImage(
|
||||
onComplete: (Uri) -> Unit
|
||||
) {
|
||||
_isSaving.update { true }
|
||||
_collageCreationTrigger.update { true }
|
||||
requestedOperation = {
|
||||
collageBitmap?.let { image ->
|
||||
savingJob = viewModelScope.launch {
|
||||
_isSaving.update { true }
|
||||
shareProvider.cacheImage(
|
||||
image = image,
|
||||
imageInfo = ImageInfo(
|
||||
width = image.width,
|
||||
height = image.height,
|
||||
quality = quality,
|
||||
imageFormat = imageFormat
|
||||
)
|
||||
)?.let { uri ->
|
||||
onComplete(uri.toUri())
|
||||
}
|
||||
_isSaving.update { false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelSaving() {
|
||||
savingJob?.cancel()
|
||||
savingJob = null
|
||||
_isSaving.update { false }
|
||||
}
|
||||
|
||||
fun setBackgroundColor(color: Color) {
|
||||
_backgroundColor.update { color }
|
||||
registerChanges()
|
||||
}
|
||||
|
||||
fun setSpacing(value: Float) {
|
||||
_spacing.update { value }
|
||||
registerChanges()
|
||||
}
|
||||
|
||||
fun setCornerRadius(value: Float) {
|
||||
_cornerRadius.update { value }
|
||||
registerChanges()
|
||||
}
|
||||
|
||||
fun getFormatForFilenameSelection(): ImageFormat = imageFormat
|
||||
|
||||
}
|
@ -21,7 +21,6 @@ package ru.tech.imageresizershrinker.feature.draw.presentation
|
||||
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import androidx.activity.ComponentActivity
|
||||
@ -87,7 +86,6 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||
import androidx.compose.material3.rememberStandardBottomSheetState
|
||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@ -117,9 +115,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.t8rin.dynamic.theme.LocalDynamicThemeState
|
||||
import com.t8rin.dynamic.theme.observeAsState
|
||||
import com.t8rin.dynamic.theme.rememberAppColorTuple
|
||||
import dev.olshevski.navigation.reimagined.hilt.hiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
@ -136,11 +132,11 @@ import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostStat
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.ImageUtils.restrict
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.Picker
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.asClip
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.localImagePickerMode
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.parseSaveResult
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.rememberImagePicker
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.provider.LocalWindowSizeClass
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.provider.ProvideContainerDefaults
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.EnhancedButton
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.EnhancedFloatingActionButton
|
||||
@ -273,17 +269,7 @@ fun DrawContent(
|
||||
}
|
||||
|
||||
val configuration = LocalConfiguration.current
|
||||
val sizeClass = LocalWindowSizeClass.current.widthSizeClass
|
||||
val portrait =
|
||||
remember(
|
||||
LocalLifecycleOwner.current.lifecycle.observeAsState().value,
|
||||
sizeClass,
|
||||
configuration
|
||||
) {
|
||||
derivedStateOf {
|
||||
configuration.orientation != Configuration.ORIENTATION_LANDSCAPE || sizeClass == WindowWidthSizeClass.Compact
|
||||
}
|
||||
}.value
|
||||
val portrait by isPortraitOrientationAsState()
|
||||
|
||||
var showPickColorSheet by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
|
@ -100,6 +100,44 @@ fun NoiseGenerationContent(
|
||||
}
|
||||
}
|
||||
|
||||
val shareButton: @Composable () -> Unit = {
|
||||
var editSheetData by remember {
|
||||
mutableStateOf(listOf<Uri>())
|
||||
}
|
||||
ShareButton(
|
||||
onShare = {
|
||||
viewModel.shareNoise(showConfetti)
|
||||
},
|
||||
onCopy = { manager ->
|
||||
viewModel.cacheCurrentNoise { uri ->
|
||||
manager.setClip(uri.asClip(context))
|
||||
showConfetti()
|
||||
}
|
||||
},
|
||||
onEdit = {
|
||||
viewModel.cacheCurrentNoise {
|
||||
editSheetData = listOf(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
ProcessImagesPreferenceSheet(
|
||||
uris = editSheetData,
|
||||
visible = editSheetData.isNotEmpty(),
|
||||
onDismiss = {
|
||||
if (!it) {
|
||||
editSheetData = emptyList()
|
||||
}
|
||||
},
|
||||
onNavigate = { screen ->
|
||||
scope.launch {
|
||||
editSheetData = emptyList()
|
||||
delay(200)
|
||||
onNavigate(screen)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AdaptiveLayoutScreen(
|
||||
title = {
|
||||
Text(
|
||||
@ -109,43 +147,7 @@ fun NoiseGenerationContent(
|
||||
)
|
||||
},
|
||||
onGoBack = onGoBack,
|
||||
actions = {
|
||||
var editSheetData by remember {
|
||||
mutableStateOf(listOf<Uri>())
|
||||
}
|
||||
ShareButton(
|
||||
onShare = {
|
||||
viewModel.shareNoise(showConfetti)
|
||||
},
|
||||
onCopy = { manager ->
|
||||
viewModel.cacheCurrentNoise { uri ->
|
||||
manager.setClip(uri.asClip(context))
|
||||
showConfetti()
|
||||
}
|
||||
},
|
||||
onEdit = {
|
||||
viewModel.cacheCurrentNoise {
|
||||
editSheetData = listOf(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
ProcessImagesPreferenceSheet(
|
||||
uris = editSheetData,
|
||||
visible = editSheetData.isNotEmpty(),
|
||||
onDismiss = {
|
||||
if (!it) {
|
||||
editSheetData = emptyList()
|
||||
}
|
||||
},
|
||||
onNavigate = { screen ->
|
||||
scope.launch {
|
||||
editSheetData = emptyList()
|
||||
delay(200)
|
||||
onNavigate(screen)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
actions = {},
|
||||
topAppBarPersistentActions = {
|
||||
TopAppBarEmoji()
|
||||
},
|
||||
@ -183,7 +185,8 @@ fun NoiseGenerationContent(
|
||||
Spacer(Modifier.height(4.dp))
|
||||
ImageFormatSelector(
|
||||
value = viewModel.imageFormat,
|
||||
onValueChange = viewModel::setImageFormat
|
||||
onValueChange = viewModel::setImageFormat,
|
||||
forceEnabled = true
|
||||
)
|
||||
QualitySelector(
|
||||
quality = viewModel.quality,
|
||||
@ -206,7 +209,9 @@ fun NoiseGenerationContent(
|
||||
onPrimaryButtonLongClick = {
|
||||
showFolderSelectionDialog = true
|
||||
},
|
||||
actions = it
|
||||
actions = {
|
||||
shareButton()
|
||||
}
|
||||
)
|
||||
if (showFolderSelectionDialog) {
|
||||
OneTimeSaveLocationSelectionDialog(
|
||||
|
@ -106,7 +106,7 @@ class NoiseGenerationViewModel @Inject constructor(
|
||||
saveTarget = ImageSaveTarget(
|
||||
imageInfo = imageInfo,
|
||||
metadata = null,
|
||||
originalUri = "Noise",
|
||||
originalUri = "",
|
||||
sequenceNumber = null,
|
||||
data = imageCompressor.compress(
|
||||
image = bitmap,
|
||||
|
@ -61,4 +61,5 @@ dependencies {
|
||||
implementation(projects.feature.colorTools)
|
||||
implementation(projects.feature.webpTools)
|
||||
implementation(projects.feature.noiseGeneration)
|
||||
implementation(projects.feature.colllageMaker)
|
||||
}
|
@ -29,6 +29,7 @@ import dev.olshevski.navigation.reimagined.navigate
|
||||
import dev.olshevski.navigation.reimagined.popUpTo
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.tech.imageresizershrinker.colllage_maker.presentation.CollageMakerContent
|
||||
import ru.tech.imageresizershrinker.color_tools.presentation.ColorToolsContent
|
||||
import ru.tech.imageresizershrinker.core.domain.utils.Lambda
|
||||
import ru.tech.imageresizershrinker.core.settings.presentation.provider.LocalSettingsState
|
||||
@ -408,6 +409,14 @@ internal fun ScreenSelector(
|
||||
onNavigate = onNavigate
|
||||
)
|
||||
}
|
||||
|
||||
is Screen.CollageMaker -> {
|
||||
CollageMakerContent(
|
||||
uriState = screen.uris,
|
||||
onGoBack = onGoBack,
|
||||
onNavigate = onNavigate
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
ScreenBasedMaxBrightnessEnforcement(navController.currentDestination())
|
||||
|
@ -3,13 +3,13 @@ androidMinSdk = "21"
|
||||
androidTargetSdk = "34"
|
||||
androidCompileSdk = "34"
|
||||
|
||||
versionName = "3.0.1-alpha01"
|
||||
versionName = "3.1.0-alpha01"
|
||||
versionCode = "153"
|
||||
|
||||
jvmTarget = "17"
|
||||
compose-compiler = "1.5.15"
|
||||
|
||||
imageToolboxLibs = "2.5.5"
|
||||
imageToolboxLibs = "2.6.0"
|
||||
trickle = "1.1.2"
|
||||
|
||||
avifCoder = "1.8.0"
|
||||
@ -109,6 +109,7 @@ toolbox-awebp = { module = "com.github.T8RIN.ImageToolboxLibs:awebp", version.re
|
||||
toolbox-psd = { module = "com.github.T8RIN.ImageToolboxLibs:psd", version.ref = "imageToolboxLibs" }
|
||||
toolbox-djvuCoder = { module = "com.github.T8RIN.ImageToolboxLibs:djvu-coder", version.ref = "imageToolboxLibs" }
|
||||
toolbox-fastNoise = { module = "com.github.T8RIN.ImageToolboxLibs:fast-noise", version.ref = "imageToolboxLibs" }
|
||||
toolbox-collages = { module = "com.github.T8RIN.ImageToolboxLibs:collages", version.ref = "imageToolboxLibs" }
|
||||
|
||||
|
||||
aire = { module = "com.github.awxkee:aire", version.ref = "aire" }
|
||||
|
@ -101,6 +101,7 @@ include(":feature:image-splitting")
|
||||
include(":feature:color-tools")
|
||||
include(":feature:webp-tools")
|
||||
include(":feature:noise-generation")
|
||||
include(":feature:colllage-maker")
|
||||
|
||||
include(":feature:root")
|
||||
|
||||
|
Reference in New Issue
Block a user