mirror of
https://github.com/T8RIN/ImageToolbox.git
synced 2025-05-17 13:35:58 +08:00
added jxl transcoding
This commit is contained in:
@ -136,16 +136,21 @@ internal class AndroidShareProvider @Inject constructor(
|
||||
context.startActivity(shareIntent)
|
||||
}
|
||||
|
||||
override suspend fun shareImageUris(
|
||||
override suspend fun shareUris(
|
||||
uris: List<String>
|
||||
) = shareImageUris(uris.map { it.toUri() })
|
||||
|
||||
private fun shareImageUris(uris: List<Uri>) {
|
||||
if (uris.isEmpty()) return
|
||||
|
||||
val sendIntent = Intent(Intent.ACTION_SEND_MULTIPLE).apply {
|
||||
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
type = "image/*"
|
||||
type = MimeTypeMap.getSingleton()
|
||||
.getMimeTypeFromExtension(
|
||||
imageGetter.getExtension(uris.first().toString())
|
||||
) ?: "*/*"
|
||||
}
|
||||
val shareIntent = Intent.createChooser(sendIntent, context.getString(R.string.share))
|
||||
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
@ -28,6 +28,7 @@ import com.awxkee.jxlcoder.JxlCoder
|
||||
import com.awxkee.jxlcoder.JxlColorSpace
|
||||
import com.awxkee.jxlcoder.JxlCompressionOption
|
||||
import com.awxkee.jxlcoder.JxlDecodingSpeed
|
||||
import com.awxkee.jxlcoder.JxlEffort
|
||||
import com.radzivon.bartoshyk.avif.coder.HeifCoder
|
||||
import ru.tech.imageresizershrinker.core.domain.model.ImageFormat
|
||||
import ru.tech.imageresizershrinker.core.domain.model.Quality
|
||||
@ -344,12 +345,12 @@ internal abstract class SimpleCompressor {
|
||||
quality: Quality
|
||||
): ByteArray {
|
||||
val jxlQuality = quality as? Quality.Jxl ?: Quality.Jxl(quality.qualityValue)
|
||||
return JxlCoder().encode(
|
||||
return JxlCoder.encode(
|
||||
bitmap = image,
|
||||
colorSpace = JxlColorSpace.RGBA,
|
||||
compressionOption = JxlCompressionOption.LOSSY,
|
||||
quality = jxlQuality.qualityValue,
|
||||
effort = jxlQuality.effort,
|
||||
effort = JxlEffort.entries.first { it.ordinal == jxlQuality.effort },
|
||||
decodingSpeed = JxlDecodingSpeed.entries.first { it.ordinal == jxlQuality.speed }
|
||||
)
|
||||
}
|
||||
@ -363,12 +364,12 @@ internal abstract class SimpleCompressor {
|
||||
quality: Quality
|
||||
): ByteArray {
|
||||
val jxlQuality = quality as? Quality.Jxl ?: Quality.Jxl(quality.qualityValue)
|
||||
return JxlCoder().encode(
|
||||
return JxlCoder.encode(
|
||||
bitmap = image,
|
||||
colorSpace = JxlColorSpace.RGBA,
|
||||
compressionOption = JxlCompressionOption.LOSSLESS,
|
||||
quality = 100,
|
||||
effort = jxlQuality.effort,
|
||||
effort = JxlEffort.entries.first { it.ordinal == jxlQuality.effort },
|
||||
decodingSpeed = JxlDecodingSpeed.entries.first { it.ordinal == jxlQuality.speed }
|
||||
)
|
||||
}
|
||||
|
@ -354,7 +354,8 @@ internal class FileControllerImpl @Inject constructor(
|
||||
)
|
||||
|
||||
override fun constructImageFilename(
|
||||
saveTarget: ImageSaveTarget<*>
|
||||
saveTarget: ImageSaveTarget<*>,
|
||||
forceNotAddSizeInFilename: Boolean
|
||||
): String {
|
||||
val extension = saveTarget.imageInfo.imageFormat.extension
|
||||
|
||||
@ -382,7 +383,7 @@ internal class FileControllerImpl @Inject constructor(
|
||||
context.getString(R.string.original_filename)
|
||||
}
|
||||
}
|
||||
if (settingsState.addSizeInFilename) prefix += wh
|
||||
if (settingsState.addSizeInFilename && !forceNotAddSizeInFilename) prefix += wh
|
||||
|
||||
val timeStamp = SimpleDateFormat(
|
||||
"yyyy-MM-dd_HH-mm-ss",
|
||||
|
@ -56,6 +56,8 @@ interface ShareProvider<I> {
|
||||
type: String?
|
||||
)
|
||||
|
||||
suspend fun shareImageUris(uris: List<String>)
|
||||
suspend fun shareUris(
|
||||
uris: List<String>
|
||||
)
|
||||
|
||||
}
|
@ -49,7 +49,7 @@ sealed class ImageFormat(
|
||||
data object Jpg : ImageFormat(
|
||||
title = "JPG",
|
||||
extension = "jpg",
|
||||
type = "image/jpg",
|
||||
type = "image/jpeg",
|
||||
canChangeCompressionValue = true,
|
||||
canWriteExif = true
|
||||
)
|
||||
@ -65,7 +65,7 @@ sealed class ImageFormat(
|
||||
data object MozJpeg : ImageFormat(
|
||||
title = "MozJpeg",
|
||||
extension = "jpg",
|
||||
type = "image/jpg",
|
||||
type = "image/jpeg",
|
||||
canChangeCompressionValue = true,
|
||||
canWriteExif = true
|
||||
)
|
||||
|
@ -25,7 +25,7 @@ sealed class Quality(
|
||||
data class Jxl(
|
||||
@IntRange(from = 0, to = 100)
|
||||
override val qualityValue: Int = 100,
|
||||
@IntRange(from = 1, to = 9)
|
||||
@IntRange(from = 1, to = 10)
|
||||
val effort: Int = 7,
|
||||
@IntRange(from = 0, to = 5)
|
||||
val speed: Int = 2
|
||||
|
@ -29,7 +29,10 @@ interface FileController {
|
||||
|
||||
fun getSize(uri: String): Long?
|
||||
|
||||
fun constructImageFilename(saveTarget: ImageSaveTarget<*>): String
|
||||
fun constructImageFilename(
|
||||
saveTarget: ImageSaveTarget<*>,
|
||||
forceNotAddSizeInFilename: Boolean = false
|
||||
): String
|
||||
|
||||
fun clearCache(onComplete: (String) -> Unit = {})
|
||||
|
||||
|
@ -827,4 +827,11 @@
|
||||
<string name="explode">Explode</string>
|
||||
<string name="rain">Rain</string>
|
||||
<string name="corners">Corners</string>
|
||||
<string name="jxl_tools">JXL Tools</string>
|
||||
<string name="jxl_tools_sub">Transcode JXL to JPEG or JPEG to JXL with no quality loss</string>
|
||||
<string name="jxl_type_to_jpeg">JXL to JPEG</string>
|
||||
<string name="jxl_type_to_jpeg_sub">Perform lossless transcoding from JXL to JPEG</string>
|
||||
<string name="jpeg_type_to_jxl_sub">Perform lossless transcoding from JPEG to JXL</string>
|
||||
<string name="jpeg_type_to_jxl">JPEG to JXL</string>
|
||||
<string name="select_jxl_image_to_start">Pick JXL image to start</string>
|
||||
</resources>
|
@ -0,0 +1,79 @@
|
||||
package ru.tech.imageresizershrinker.core.ui.icons.material
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
|
||||
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.ImageVector.Builder
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val Icons.Outlined.Jpg: ImageVector by lazy {
|
||||
Builder(
|
||||
name = "Jpg", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth
|
||||
= 24.0f, viewportHeight = 24.0f
|
||||
).apply {
|
||||
path(
|
||||
fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero
|
||||
) {
|
||||
moveTo(8.1429f, 13.9286f)
|
||||
curveToRelative(0.0f, 1.4143f, -1.1571f, 1.9286f, -2.5714f, 1.9286f)
|
||||
reflectiveCurveTo(3.0f, 15.3429f, 3.0f, 13.9286f)
|
||||
verticalLineTo(12.0f)
|
||||
horizontalLineToRelative(1.9286f)
|
||||
verticalLineToRelative(1.9286f)
|
||||
horizontalLineToRelative(1.2857f)
|
||||
verticalLineTo(8.1429f)
|
||||
horizontalLineToRelative(1.9286f)
|
||||
verticalLineTo(13.9286f)
|
||||
}
|
||||
path(
|
||||
fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero
|
||||
) {
|
||||
moveTo(21.0f, 10.0714f)
|
||||
horizontalLineToRelative(-3.2143f)
|
||||
verticalLineToRelative(3.8571f)
|
||||
horizontalLineToRelative(1.2857f)
|
||||
verticalLineTo(12.0f)
|
||||
horizontalLineTo(21.0f)
|
||||
verticalLineToRelative(2.1857f)
|
||||
curveToRelative(0.0f, 0.9f, -0.6429f, 1.6714f, -1.6714f, 1.6714f)
|
||||
horizontalLineToRelative(-1.6714f)
|
||||
curveToRelative(-1.0286f, 0.0f, -1.6714f, -0.9f, -1.6714f, -1.6714f)
|
||||
verticalLineToRelative(-4.2429f)
|
||||
curveTo(15.8571f, 9.0429f, 16.5f, 8.1429f, 17.5286f, 8.1429f)
|
||||
horizontalLineToRelative(1.6714f)
|
||||
curveToRelative(1.0286f, 0.0f, 1.6714f, 0.9f, 1.6714f, 1.6714f)
|
||||
verticalLineToRelative(0.2571f)
|
||||
}
|
||||
path(
|
||||
fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero
|
||||
) {
|
||||
moveTo(12.6429f, 8.1429f)
|
||||
horizontalLineTo(9.4286f)
|
||||
verticalLineToRelative(7.7142f)
|
||||
horizontalLineToRelative(1.9285f)
|
||||
verticalLineToRelative(-2.5714f)
|
||||
horizontalLineToRelative(1.2858f)
|
||||
curveToRelative(1.0286f, 0.0f, 1.9285f, -0.9f, 1.9285f, -1.9286f)
|
||||
verticalLineToRelative(-1.2857f)
|
||||
curveTo(14.5714f, 9.0428f, 13.6714f, 8.1429f, 12.6429f, 8.1429f)
|
||||
close()
|
||||
moveTo(12.6429f, 11.3571f)
|
||||
horizontalLineToRelative(-1.2858f)
|
||||
verticalLineToRelative(-1.2857f)
|
||||
horizontalLineToRelative(1.2858f)
|
||||
verticalLineTo(11.3571f)
|
||||
close()
|
||||
}
|
||||
}.build()
|
||||
}
|
@ -30,87 +30,89 @@ import androidx.compose.ui.unit.dp
|
||||
|
||||
val Icons.Filled.Jxl: ImageVector by lazy {
|
||||
Builder(
|
||||
name = "Jxl", defaultWidth = 48.dp, defaultHeight = 48.dp,
|
||||
viewportWidth = 891.6f, viewportHeight = 836.9f
|
||||
name = "Jxl", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth
|
||||
= 24.0f, viewportHeight = 24.0f
|
||||
).apply {
|
||||
path(
|
||||
fill = SolidColor(Color(0xFF5fb4b1)), stroke = null, strokeLineWidth = 0.0f,
|
||||
fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero
|
||||
) {
|
||||
moveTo(495.7f, 420.6f)
|
||||
curveTo(533.0f, 348.3f, 570.2f, 276.0f, 607.4f, 203.7f)
|
||||
horizontalLineTo(503.8f)
|
||||
curveToRelative(-24.0f, 46.6f, -48.1f, 93.3f, -72.1f, 139.9f)
|
||||
curveToRelative(-38.8f, -46.6f, -77.6f, -93.3f, -116.4f, -139.9f)
|
||||
horizontalLineTo(211.7f)
|
||||
lineTo(392.0f, 420.6f)
|
||||
curveToRelative(-36.5f, 70.8f, -73.0f, 141.7f, -109.5f, 212.5f)
|
||||
horizontalLineToRelative(103.6f)
|
||||
curveToRelative(23.3f, -45.2f, 46.6f, -90.4f, 69.8f, -135.5f)
|
||||
curveToRelative(37.6f, 45.2f, 75.1f, 90.4f, 112.7f, 135.5f)
|
||||
horizontalLineToRelative(103.6f)
|
||||
curveToRelative(-58.7f, -70.8f, -117.6f, -141.6f, -176.5f, -212.5f)
|
||||
moveTo(13.1206f, 12.0514f)
|
||||
curveToRelative(0.8368f, -1.7278f, 1.6713f, -3.4556f, 2.5059f, -5.1834f)
|
||||
horizontalLineToRelative(-2.3242f)
|
||||
curveToRelative(-0.5384f, 1.1136f, -1.0791f, 2.2297f, -1.6175f, 3.3433f)
|
||||
curveTo(10.8144f, 9.0976f, 9.9439f, 7.9816f, 9.0735f, 6.868f)
|
||||
horizontalLineTo(6.7493f)
|
||||
lineToRelative(4.0449f, 5.1834f)
|
||||
curveToRelative(-0.8188f, 1.692f, -1.6377f, 3.3863f, -2.4565f, 5.0783f)
|
||||
horizontalLineToRelative(2.3242f)
|
||||
curveToRelative(0.5227f, -1.0802f, 1.0454f, -2.1604f, 1.5659f, -3.2381f)
|
||||
curveToRelative(0.8435f, 1.0802f, 1.6848f, 2.1604f, 2.5283f, 3.2381f)
|
||||
horizontalLineToRelative(2.3242f)
|
||||
curveToRelative(-1.3169f, -1.692f, -2.6382f, -3.3839f, -3.9596f, -5.0783f)
|
||||
verticalLineTo(12.0514f)
|
||||
close()
|
||||
moveTo(153.0f, 625.6f)
|
||||
lineToRelative(0.3f, 2.3f)
|
||||
lineToRelative(0.7f, 2.6f)
|
||||
curveToRelative(3.8f, 15.1f, 8.9f, 59.5f, -12.0f, 86.3f)
|
||||
curveToRelative(-6.2f, 8.0f, -14.8f, 14.5f, -25.6f, 19.3f)
|
||||
lineTo(53.9f, 836.9f)
|
||||
curveToRelative(36.9f, 0.0f, 69.4f, -5.8f, 96.5f, -17.4f)
|
||||
curveToRelative(25.9f, -11.0f, 47.2f, -27.2f, 63.2f, -48.1f)
|
||||
curveToRelative(22.2f, -28.9f, 33.9f, -66.6f, 33.8f, -109.1f)
|
||||
curveToRelative(0.0f, -24.8f, -4.0f, -44.6f, -5.7f, -52.0f)
|
||||
lineTo(200.8f, 337.0f)
|
||||
horizontalLineToRelative(0.1f)
|
||||
verticalLineToRelative(-90.2f)
|
||||
horizontalLineTo(0.0f)
|
||||
verticalLineTo(337.0f)
|
||||
horizontalLineToRelative(109.8f)
|
||||
lineTo(153.0f, 625.6f)
|
||||
moveTo(5.4324f, 16.9504f)
|
||||
lineToRelative(0.0067f, 0.055f)
|
||||
lineToRelative(0.0157f, 0.0621f)
|
||||
curveToRelative(0.0852f, 0.3609f, 0.1997f, 1.4219f, -0.2692f, 2.0624f)
|
||||
curveToRelative(-0.1391f, 0.1912f, -0.332f, 0.3465f, -0.5743f, 0.4612f)
|
||||
lineTo(3.2092f, 22.0f)
|
||||
curveToRelative(0.8278f, 0.0f, 1.5569f, -0.1386f, 2.1649f, -0.4158f)
|
||||
curveToRelative(0.581f, -0.2629f, 1.0589f, -0.65f, 1.4178f, -1.1495f)
|
||||
curveToRelative(0.498f, -0.6906f, 0.7605f, -1.5916f, 0.7583f, -2.6072f)
|
||||
curveToRelative(0.0f, -0.5927f, -0.0897f, -1.0658f, -0.1279f, -1.2427f)
|
||||
lineToRelative(-0.9176f, -6.5312f)
|
||||
horizontalLineToRelative(0.0022f)
|
||||
verticalLineTo(7.898f)
|
||||
horizontalLineTo(2.0f)
|
||||
verticalLineToRelative(2.1556f)
|
||||
horizontalLineToRelative(2.4633f)
|
||||
lineTo(5.4324f, 16.9504f)
|
||||
close()
|
||||
moveTo(738.5f, 211.2f)
|
||||
lineToRelative(-0.3f, -2.3f)
|
||||
lineToRelative(-0.7f, -2.6f)
|
||||
curveToRelative(-3.8f, -15.1f, -8.9f, -59.5f, 12.0f, -86.3f)
|
||||
curveToRelative(6.2f, -8.0f, 14.8f, -14.5f, 25.6f, -19.3f)
|
||||
lineTo(837.6f, 0.0f)
|
||||
curveToRelative(-36.9f, 0.0f, -69.4f, 5.8f, -96.5f, 17.4f)
|
||||
curveToRelative(-25.9f, 11.0f, -47.2f, 27.2f, -63.2f, 48.1f)
|
||||
curveToRelative(-22.2f, 28.9f, -33.9f, 66.6f, -33.8f, 109.1f)
|
||||
curveToRelative(0.0f, 24.8f, 4.0f, 44.6f, 5.7f, 52.0f)
|
||||
lineToRelative(40.9f, 273.3f)
|
||||
horizontalLineToRelative(-0.1f)
|
||||
verticalLineToRelative(90.2f)
|
||||
horizontalLineToRelative(200.9f)
|
||||
verticalLineToRelative(-90.2f)
|
||||
horizontalLineTo(781.7f)
|
||||
lineToRelative(-43.2f, -288.7f)
|
||||
moveTo(18.5676f, 7.0472f)
|
||||
lineToRelative(-0.0067f, -0.055f)
|
||||
lineToRelative(-0.0157f, -0.0621f)
|
||||
curveToRelative(-0.0852f, -0.3609f, -0.1997f, -1.4219f, 0.2692f, -2.0624f)
|
||||
curveToRelative(0.1391f, -0.1912f, 0.332f, -0.3465f, 0.5743f, -0.4612f)
|
||||
lineTo(20.7908f, 2.0f)
|
||||
curveToRelative(-0.8278f, 0.0f, -1.5569f, 0.1386f, -2.1649f, 0.4158f)
|
||||
curveToRelative(-0.581f, 0.2629f, -1.0589f, 0.65f, -1.4178f, 1.1495f)
|
||||
curveToRelative(-0.498f, 0.6906f, -0.7605f, 1.5916f, -0.7583f, 2.6072f)
|
||||
curveToRelative(0.0f, 0.5927f, 0.0897f, 1.0658f, 0.1279f, 1.2427f)
|
||||
lineToRelative(0.9176f, 6.5312f)
|
||||
horizontalLineToRelative(-0.0022f)
|
||||
verticalLineToRelative(2.1556f)
|
||||
horizontalLineTo(22.0f)
|
||||
verticalLineToRelative(-2.1556f)
|
||||
horizontalLineToRelative(-2.4633f)
|
||||
lineToRelative(-0.9692f, -6.8993f)
|
||||
verticalLineTo(7.0472f)
|
||||
close()
|
||||
}
|
||||
path(
|
||||
fill = SolidColor(Color(0xFF5fb4b1)), stroke = null, strokeLineWidth = 0.0f,
|
||||
fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero
|
||||
) {
|
||||
moveTo(153.0f, 625.6f)
|
||||
lineToRelative(0.3f, 2.3f)
|
||||
lineToRelative(0.7f, 2.6f)
|
||||
curveToRelative(3.8f, 15.1f, 8.9f, 59.5f, -12.0f, 86.3f)
|
||||
curveToRelative(-6.2f, 8.0f, -14.8f, 14.5f, -25.6f, 19.3f)
|
||||
lineTo(53.9f, 836.9f)
|
||||
curveToRelative(36.9f, 0.0f, 69.4f, -5.8f, 96.5f, -17.4f)
|
||||
curveToRelative(25.9f, -11.0f, 47.2f, -27.2f, 63.2f, -48.1f)
|
||||
curveToRelative(22.2f, -28.9f, 33.9f, -66.6f, 33.8f, -109.1f)
|
||||
curveToRelative(0.0f, -24.8f, -4.0f, -44.6f, -5.7f, -52.0f)
|
||||
lineTo(200.8f, 337.0f)
|
||||
horizontalLineToRelative(0.1f)
|
||||
verticalLineToRelative(-90.2f)
|
||||
horizontalLineTo(0.0f)
|
||||
verticalLineTo(337.0f)
|
||||
horizontalLineToRelative(109.8f)
|
||||
lineTo(153.0f, 625.6f)
|
||||
moveTo(5.4324f, 16.9504f)
|
||||
lineToRelative(0.0067f, 0.055f)
|
||||
lineToRelative(0.0157f, 0.0621f)
|
||||
curveToRelative(0.0852f, 0.3609f, 0.1997f, 1.4219f, -0.2692f, 2.0624f)
|
||||
curveToRelative(-0.1391f, 0.1912f, -0.332f, 0.3465f, -0.5743f, 0.4612f)
|
||||
lineTo(3.2092f, 22.0f)
|
||||
curveToRelative(0.8278f, 0.0f, 1.5569f, -0.1386f, 2.1649f, -0.4158f)
|
||||
curveToRelative(0.581f, -0.2629f, 1.0589f, -0.65f, 1.4178f, -1.1495f)
|
||||
curveToRelative(0.498f, -0.6906f, 0.7605f, -1.5916f, 0.7583f, -2.6072f)
|
||||
curveToRelative(0.0f, -0.5927f, -0.0897f, -1.0658f, -0.1279f, -1.2427f)
|
||||
lineToRelative(-0.9176f, -6.5312f)
|
||||
horizontalLineToRelative(0.0022f)
|
||||
verticalLineTo(7.898f)
|
||||
horizontalLineTo(2.0f)
|
||||
verticalLineToRelative(2.1556f)
|
||||
horizontalLineToRelative(2.4633f)
|
||||
lineTo(5.4324f, 16.9504f)
|
||||
close()
|
||||
}
|
||||
}.build()
|
||||
|
@ -156,8 +156,10 @@ enum class Picker {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun localImagePickerMode(picker: Picker = Picker.Single): ImagePickerMode {
|
||||
val modeInt = LocalSettingsState.current.imagePickerModeInt
|
||||
fun localImagePickerMode(
|
||||
picker: Picker = Picker.Single,
|
||||
modeInt: Int = LocalSettingsState.current.imagePickerModeInt
|
||||
): ImagePickerMode {
|
||||
val multiple = picker == Picker.Multiple
|
||||
return remember(modeInt) {
|
||||
derivedStateOf {
|
||||
|
@ -53,6 +53,8 @@ import ru.tech.imageresizershrinker.core.ui.icons.material.ImageEdit
|
||||
import ru.tech.imageresizershrinker.core.ui.icons.material.ImageLimit
|
||||
import ru.tech.imageresizershrinker.core.ui.icons.material.ImageText
|
||||
import ru.tech.imageresizershrinker.core.ui.icons.material.ImageWeight
|
||||
import ru.tech.imageresizershrinker.core.ui.icons.material.Jpg
|
||||
import ru.tech.imageresizershrinker.core.ui.icons.material.Jxl
|
||||
import ru.tech.imageresizershrinker.core.ui.icons.material.MultipleImageEdit
|
||||
import ru.tech.imageresizershrinker.core.ui.icons.material.PaletteSwatch
|
||||
import ru.tech.imageresizershrinker.core.ui.icons.material.Resize
|
||||
@ -428,6 +430,48 @@ sealed class Screen(
|
||||
subtitle = R.string.zip_sub
|
||||
)
|
||||
|
||||
data class JxlTools(
|
||||
val type: Type? = null
|
||||
) : Screen(
|
||||
id = 20,
|
||||
icon = Icons.Filled.Jxl,
|
||||
title = R.string.jxl_tools,
|
||||
subtitle = R.string.jxl_tools_sub
|
||||
) {
|
||||
@Parcelize
|
||||
sealed class Type(
|
||||
@StringRes val title: Int,
|
||||
@StringRes val subtitle: Int,
|
||||
@IgnoredOnParcel val icon: ImageVector? = null
|
||||
) : Parcelable {
|
||||
|
||||
data class JxlToJpeg(
|
||||
val jxlImageUris: List<Uri>? = null
|
||||
) : Type(
|
||||
title = R.string.jxl_type_to_jpeg,
|
||||
subtitle = R.string.jxl_type_to_jpeg_sub,
|
||||
icon = Icons.Outlined.Jpg
|
||||
)
|
||||
|
||||
data class JpegToJxl(
|
||||
val jpegImageUris: List<Uri>? = null
|
||||
) : Type(
|
||||
title = R.string.jpeg_type_to_jxl,
|
||||
subtitle = R.string.jpeg_type_to_jxl_sub,
|
||||
icon = Icons.Filled.Jxl
|
||||
)
|
||||
|
||||
companion object {
|
||||
val entries by lazy {
|
||||
listOf(
|
||||
JpegToJxl(),
|
||||
JxlToJpeg()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val typedEntries by lazy {
|
||||
listOf(
|
||||
@ -465,6 +509,7 @@ sealed class Screen(
|
||||
GifTools(),
|
||||
ImagePreview(),
|
||||
GeneratePalette(),
|
||||
JxlTools(),
|
||||
ApngTools(),
|
||||
LoadNetImage(),
|
||||
) to Triple(
|
||||
@ -490,6 +535,7 @@ sealed class Screen(
|
||||
Watermarking(),
|
||||
GifTools(),
|
||||
ApngTools(),
|
||||
JxlTools(),
|
||||
ImagePreview(),
|
||||
LoadNetImage(),
|
||||
PickColorFromImage(),
|
||||
@ -501,6 +547,6 @@ sealed class Screen(
|
||||
LimitResize()
|
||||
)
|
||||
}
|
||||
const val featuresCount = 31
|
||||
const val featuresCount = 32
|
||||
}
|
||||
}
|
@ -529,10 +529,7 @@ fun ApngToolsScreen(
|
||||
},
|
||||
noDataControls = {
|
||||
val types = remember {
|
||||
listOf(
|
||||
Screen.ApngTools.Type.ImageToApng(),
|
||||
Screen.ApngTools.Type.ApngToImage()
|
||||
)
|
||||
Screen.ApngTools.Type.entries
|
||||
}
|
||||
val preference1 = @Composable {
|
||||
PreferenceItem(
|
||||
|
@ -338,7 +338,7 @@ class ApngToolsViewModel @Inject constructor(
|
||||
val uris = convertedImageUris.filterIndexed { index, _ ->
|
||||
index in positions
|
||||
}
|
||||
shareProvider.shareImageUris(uris)
|
||||
shareProvider.shareUris(uris)
|
||||
onComplete()
|
||||
}
|
||||
|
||||
|
@ -505,10 +505,7 @@ fun GifToolsScreen(
|
||||
},
|
||||
noDataControls = {
|
||||
val types = remember {
|
||||
listOf(
|
||||
Screen.GifTools.Type.ImageToGif(),
|
||||
Screen.GifTools.Type.GifToImage()
|
||||
)
|
||||
Screen.GifTools.Type.entries
|
||||
}
|
||||
val preference1 = @Composable {
|
||||
PreferenceItem(
|
||||
|
@ -347,7 +347,7 @@ class GifToolsViewModel @Inject constructor(
|
||||
val uris = convertedImageUris.filterIndexed { index, _ ->
|
||||
index in positions
|
||||
}
|
||||
shareProvider.shareImageUris(uris)
|
||||
shareProvider.shareUris(uris)
|
||||
onComplete()
|
||||
}
|
||||
|
||||
|
1
feature/jxl-tools/.gitignore
vendored
Normal file
1
feature/jxl-tools/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
25
feature/jxl-tools/build.gradle.kts
Normal file
25
feature/jxl-tools/build.gradle.kts
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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>.
|
||||
*/
|
||||
|
||||
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.jxl_tools"
|
20
feature/jxl-tools/src/main/AndroidManifest.xml
Normal file
20
feature/jxl-tools/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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>.
|
||||
-->
|
||||
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.feature.jxl_tools.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import com.awxkee.jxlcoder.JxlCoder
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import ru.tech.imageresizershrinker.feature.jxl_tools.domain.JxlTranscoder
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
internal class AndroidJxlTranscoder @Inject constructor(
|
||||
@ApplicationContext private val context: Context
|
||||
) : JxlTranscoder {
|
||||
|
||||
override suspend fun jpegToJxl(
|
||||
jpegUris: List<String>,
|
||||
onProgress: suspend (originalUri: String, data: ByteArray) -> Unit
|
||||
) {
|
||||
jpegUris.forEach { uri ->
|
||||
val bytes = context.contentResolver.openInputStream(uri.toUri())?.use {
|
||||
it.readBytes()
|
||||
} ?: return
|
||||
|
||||
onProgress(uri, JxlCoder.construct(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun jxlToJpeg(
|
||||
jxlUris: List<String>,
|
||||
onProgress: suspend (originalUri: String, data: ByteArray) -> Unit
|
||||
) {
|
||||
jxlUris.forEach { uri ->
|
||||
val bytes = context.contentResolver.openInputStream(uri.toUri())?.use {
|
||||
it.readBytes()
|
||||
} ?: return
|
||||
|
||||
onProgress(uri, JxlCoder.reconstructJPEG(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.feature.jxl_tools.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import ru.tech.imageresizershrinker.feature.jxl_tools.data.AndroidJxlTranscoder
|
||||
import ru.tech.imageresizershrinker.feature.jxl_tools.domain.JxlTranscoder
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface JxlToolsModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun provideTranscoder(
|
||||
converter: AndroidJxlTranscoder
|
||||
): JxlTranscoder
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.feature.jxl_tools.domain
|
||||
|
||||
interface JxlTranscoder {
|
||||
|
||||
suspend fun jpegToJxl(
|
||||
jpegUris: List<String>,
|
||||
onProgress: suspend (originalUri: String, data: ByteArray) -> Unit
|
||||
)
|
||||
|
||||
suspend fun jxlToJpeg(
|
||||
jxlUris: List<String>,
|
||||
onProgress: suspend (originalUri: String, data: ByteArray) -> Unit
|
||||
)
|
||||
|
||||
}
|
@ -0,0 +1,521 @@
|
||||
/*
|
||||
* 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.feature.jxl_tools.presentation
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ContextualFlowRow
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.displayCutout
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.InsertDriveFile
|
||||
import androidx.compose.material.icons.outlined.AddPhotoAlternate
|
||||
import androidx.compose.material.icons.outlined.FolderOff
|
||||
import androidx.compose.material.icons.outlined.Share
|
||||
import androidx.compose.material.icons.rounded.RemoveCircleOutline
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||
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.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.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import dev.olshevski.navigation.reimagined.hilt.hiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.tech.imageresizershrinker.core.resources.R
|
||||
import ru.tech.imageresizershrinker.core.settings.presentation.LocalSettingsState
|
||||
import ru.tech.imageresizershrinker.core.ui.icons.material.Jxl
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostState
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.getFilename
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.failedToSaveImages
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen
|
||||
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.EnhancedChip
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.EnhancedIconButton
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.ExitWithoutSavingDialog
|
||||
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.withModifier
|
||||
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.ToastDuration
|
||||
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.text.AutoSizeText
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.text.TopAppBarTitle
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.utils.LocalWindowSizeClass
|
||||
import ru.tech.imageresizershrinker.feature.jxl_tools.presentation.viewModel.JxlToolsViewModel
|
||||
|
||||
@Composable
|
||||
fun JxlToolsScreen(
|
||||
typeState: Screen.JxlTools.Type?,
|
||||
onGoBack: () -> Unit,
|
||||
viewModel: JxlToolsViewModel = hiltViewModel()
|
||||
) {
|
||||
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(typeState) {
|
||||
typeState?.let { viewModel.setType(it) }
|
||||
}
|
||||
|
||||
val settingsState = LocalSettingsState.current
|
||||
|
||||
val pickJpegsLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenMultipleDocuments()
|
||||
) { list ->
|
||||
list.takeIf { it.isNotEmpty() }?.let { uris ->
|
||||
viewModel.setType(
|
||||
Screen.JxlTools.Type.JpegToJxl(uris)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val pickJxlsLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenMultipleDocuments()
|
||||
) { list ->
|
||||
list.takeIf { it.isNotEmpty() }?.filter {
|
||||
it.isJxl(context)
|
||||
}?.let { uris ->
|
||||
if (uris.isEmpty()) {
|
||||
scope.launch {
|
||||
toastHostState.showToast(
|
||||
message = context.getString(R.string.select_jxl_image_to_start),
|
||||
icon = Icons.Filled.Jxl
|
||||
)
|
||||
}
|
||||
} else {
|
||||
viewModel.setType(
|
||||
Screen.JxlTools.Type.JxlToJpeg(uris)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val addJpegsLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenMultipleDocuments()
|
||||
) { list ->
|
||||
list.takeIf { it.isNotEmpty() }?.let { uris ->
|
||||
viewModel.setType(
|
||||
(viewModel.type as? Screen.JxlTools.Type.JpegToJxl)?.let {
|
||||
it.copy(it.jpegImageUris?.plus(uris)?.distinct())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val addJxlsLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenMultipleDocuments()
|
||||
) { list ->
|
||||
list.takeIf { it.isNotEmpty() }?.filter {
|
||||
it.isJxl(context)
|
||||
}?.let { uris ->
|
||||
if (uris.isEmpty()) {
|
||||
scope.launch {
|
||||
toastHostState.showToast(
|
||||
message = context.getString(R.string.select_jxl_image_to_start),
|
||||
icon = Icons.Filled.Jxl
|
||||
)
|
||||
}
|
||||
} else {
|
||||
viewModel.setType(
|
||||
(viewModel.type as? Screen.JxlTools.Type.JxlToJpeg)?.let {
|
||||
it.copy(it.jxlImageUris?.plus(uris)?.distinct())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pickImage(type: Screen.JxlTools.Type? = null) {
|
||||
runCatching {
|
||||
if ((type ?: viewModel.type) is Screen.JxlTools.Type.JpegToJxl) {
|
||||
pickJpegsLauncher.launch(arrayOf("image/jpeg", "image/jpg"))
|
||||
} else pickJxlsLauncher.launch(arrayOf("*/*"))
|
||||
}.onFailure {
|
||||
scope.launch {
|
||||
toastHostState.showToast(
|
||||
message = context.getString(R.string.activate_files),
|
||||
icon = Icons.Outlined.FolderOff,
|
||||
duration = ToastDuration.Long
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val addImages: () -> Unit = {
|
||||
runCatching {
|
||||
if ((viewModel.type) is Screen.JxlTools.Type.JpegToJxl) {
|
||||
addJpegsLauncher.launch(arrayOf("image/jpeg", "image/jpg"))
|
||||
} else addJxlsLauncher.launch(arrayOf("*/*"))
|
||||
}.onFailure {
|
||||
scope.launch {
|
||||
toastHostState.showToast(
|
||||
message = context.getString(R.string.activate_files),
|
||||
icon = Icons.Outlined.FolderOff,
|
||||
duration = ToastDuration.Long
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var showExitDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val onBack = {
|
||||
if (viewModel.type != null) showExitDialog = true
|
||||
else onGoBack()
|
||||
}
|
||||
|
||||
val isPortrait =
|
||||
LocalConfiguration.current.orientation != Configuration.ORIENTATION_LANDSCAPE || LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact
|
||||
|
||||
val uris = when (val type = viewModel.type) {
|
||||
is Screen.JxlTools.Type.JpegToJxl -> type.jpegImageUris
|
||||
is Screen.JxlTools.Type.JxlToJpeg -> type.jxlImageUris
|
||||
null -> null
|
||||
} ?: emptyList()
|
||||
|
||||
AdaptiveLayoutScreen(
|
||||
title = {
|
||||
TopAppBarTitle(
|
||||
title = when (viewModel.type) {
|
||||
is Screen.JxlTools.Type.JpegToJxl -> {
|
||||
stringResource(R.string.jpeg_type_to_jxl)
|
||||
}
|
||||
|
||||
is Screen.JxlTools.Type.JxlToJpeg -> {
|
||||
stringResource(R.string.jxl_type_to_jpeg)
|
||||
}
|
||||
|
||||
null -> stringResource(R.string.jxl_tools)
|
||||
},
|
||||
input = viewModel.type,
|
||||
isLoading = viewModel.isLoading,
|
||||
size = null
|
||||
)
|
||||
},
|
||||
onGoBack = onBack,
|
||||
topAppBarPersistentActions = {
|
||||
if (viewModel.type == null) TopAppBarEmoji()
|
||||
},
|
||||
actions = {
|
||||
EnhancedIconButton(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = LocalContentColor.current,
|
||||
enableAutoShadowAndBorder = false,
|
||||
onClick = {
|
||||
viewModel.performSharing(showConfetti)
|
||||
},
|
||||
enabled = !viewModel.isLoading && viewModel.type != null
|
||||
) {
|
||||
Icon(Icons.Outlined.Share, null)
|
||||
}
|
||||
},
|
||||
imagePreview = {},
|
||||
placeImagePreview = false,
|
||||
showImagePreviewAsStickyHeader = false,
|
||||
autoClearFocus = false,
|
||||
controls = {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
BoxWithConstraints {
|
||||
val size = uris.size + 1f
|
||||
|
||||
val count = if (isPortrait) {
|
||||
size.coerceAtLeast(2f).coerceAtMost(3f)
|
||||
} else {
|
||||
size.coerceAtLeast(2f).coerceAtMost(8f)
|
||||
}
|
||||
|
||||
val width = maxWidth / count - 2.dp * (count - 1)
|
||||
|
||||
ContextualFlowRow(
|
||||
itemCount = uris.size + 1,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) { index ->
|
||||
val uri = uris.getOrNull(index)
|
||||
if (uri != null) {
|
||||
Box(
|
||||
modifier = Modifier.container(
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
resultPadding = 0.dp,
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||
)
|
||||
) {
|
||||
Picture(
|
||||
model = uri,
|
||||
error = {
|
||||
Box {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Outlined.InsertDriveFile,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(width / 3f)
|
||||
.align(Alignment.Center),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.width(width)
|
||||
.aspectRatio(1f)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(Color.Black.copy(0.5f)),
|
||||
) {
|
||||
Text(
|
||||
text = (index + 1).toString(),
|
||||
color = Color.White,
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.align(Alignment.TopStart)
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.RemoveCircleOutline,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.Black.copy(0.2f))
|
||||
.clickable {
|
||||
viewModel.removeUri(uri)
|
||||
}
|
||||
.padding(4.dp)
|
||||
.align(Alignment.TopEnd),
|
||||
tint = Color.White.copy(0.7f),
|
||||
)
|
||||
val filename by remember(uri) {
|
||||
derivedStateOf {
|
||||
context.getFilename(uri)
|
||||
}
|
||||
}
|
||||
filename?.let {
|
||||
AutoSizeText(
|
||||
text = it,
|
||||
style = LocalTextStyle.current.copy(
|
||||
color = Color.White,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 12.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
textAlign = TextAlign.End
|
||||
),
|
||||
maxLines = 3,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.align(Alignment.BottomEnd)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.container(
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
resultPadding = 0.dp,
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh
|
||||
)
|
||||
.width(width)
|
||||
.aspectRatio(1f)
|
||||
.clickable(onClick = addImages),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.AddPhotoAlternate,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(width / 3f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
contentPadding = animateDpAsState(
|
||||
if (viewModel.type == null) 12.dp
|
||||
else 20.dp
|
||||
).value,
|
||||
buttons = {
|
||||
BottomButtonsBlock(
|
||||
targetState = (viewModel.type == null) to isPortrait,
|
||||
onSecondaryButtonClick = { pickImage() },
|
||||
isPrimaryButtonVisible = viewModel.type != null,
|
||||
onPrimaryButtonClick = {
|
||||
viewModel.save { results, path ->
|
||||
context.failedToSaveImages(
|
||||
scope = scope,
|
||||
results = results,
|
||||
toastHostState = toastHostState,
|
||||
savingPathString = path,
|
||||
isOverwritten = settingsState.overwriteFiles,
|
||||
showConfetti = showConfetti
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
EnhancedChip(
|
||||
selected = true,
|
||||
onClick = null,
|
||||
selectedColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Text(uris.size.toString())
|
||||
}
|
||||
},
|
||||
showNullDataButtonAsContainer = true
|
||||
)
|
||||
},
|
||||
noDataControls = {
|
||||
val types = remember {
|
||||
Screen.JxlTools.Type.entries
|
||||
}
|
||||
val preference1 = @Composable {
|
||||
PreferenceItem(
|
||||
title = stringResource(types[0].title),
|
||||
subtitle = stringResource(types[0].subtitle),
|
||||
startIcon = types[0].icon,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),
|
||||
onClick = {
|
||||
pickImage(types[0])
|
||||
}
|
||||
)
|
||||
}
|
||||
val preference2 = @Composable {
|
||||
PreferenceItem(
|
||||
title = stringResource(types[1].title),
|
||||
subtitle = stringResource(types[1].subtitle),
|
||||
startIcon = types[1].icon,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),
|
||||
onClick = {
|
||||
pickImage(types[1])
|
||||
}
|
||||
)
|
||||
}
|
||||
if (isPortrait) {
|
||||
Column {
|
||||
preference1()
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
preference2()
|
||||
}
|
||||
} else {
|
||||
val direction = LocalLayoutDirection.current
|
||||
Row(
|
||||
modifier = Modifier.padding(
|
||||
WindowInsets.displayCutout.asPaddingValues().let {
|
||||
PaddingValues(
|
||||
start = it.calculateStartPadding(direction),
|
||||
end = it.calculateEndPadding(direction)
|
||||
)
|
||||
}
|
||||
)
|
||||
) {
|
||||
preference1.withModifier(modifier = Modifier.weight(1f))
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
preference2.withModifier(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
},
|
||||
isPortrait = isPortrait,
|
||||
canShowScreenData = viewModel.type != null
|
||||
)
|
||||
|
||||
if (viewModel.isSaving) {
|
||||
if (viewModel.left != -1) {
|
||||
LoadingDialog(
|
||||
done = viewModel.done,
|
||||
left = viewModel.left,
|
||||
onCancelLoading = viewModel::cancelSaving
|
||||
)
|
||||
} else {
|
||||
LoadingDialog(
|
||||
onCancelLoading = viewModel::cancelSaving
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ExitWithoutSavingDialog(
|
||||
onExit = viewModel::clearAll,
|
||||
onDismiss = { showExitDialog = false },
|
||||
visible = showExitDialog
|
||||
)
|
||||
}
|
||||
|
||||
private fun Uri.isJxl(context: Context): Boolean {
|
||||
return context.getFilename(this).toString().endsWith(".jxl")
|
||||
.or(context.contentResolver.getType(this)?.contains("jxl") == true)
|
||||
}
|
@ -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.feature.jxl_tools.presentation.viewModel
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.tech.imageresizershrinker.core.domain.image.ShareProvider
|
||||
import ru.tech.imageresizershrinker.core.domain.model.ImageFormat
|
||||
import ru.tech.imageresizershrinker.core.domain.model.ImageInfo
|
||||
import ru.tech.imageresizershrinker.core.domain.saving.FileController
|
||||
import ru.tech.imageresizershrinker.core.domain.saving.SaveResult
|
||||
import ru.tech.imageresizershrinker.core.domain.saving.model.FileSaveTarget
|
||||
import ru.tech.imageresizershrinker.core.domain.saving.model.ImageSaveTarget
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.state.update
|
||||
import ru.tech.imageresizershrinker.feature.jxl_tools.domain.JxlTranscoder
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class JxlToolsViewModel @Inject constructor(
|
||||
private val jxlTranscoder: JxlTranscoder,
|
||||
private val fileController: FileController,
|
||||
private val shareProvider: ShareProvider<Bitmap>
|
||||
) : ViewModel() {
|
||||
|
||||
private val _type: MutableState<Screen.JxlTools.Type?> = mutableStateOf(null)
|
||||
val type by _type
|
||||
|
||||
private val _isLoading: MutableState<Boolean> = mutableStateOf(false)
|
||||
val isLoading by _isLoading
|
||||
|
||||
private val _done: MutableState<Int> = mutableIntStateOf(0)
|
||||
val done by _done
|
||||
|
||||
private val _left: MutableState<Int> = mutableIntStateOf(-1)
|
||||
val left by _left
|
||||
|
||||
private val _isSaving: MutableState<Boolean> = mutableStateOf(false)
|
||||
val isSaving: Boolean by _isSaving
|
||||
|
||||
fun setType(type: Screen.JxlTools.Type?) {
|
||||
_type.update { type }
|
||||
}
|
||||
|
||||
private var savingJob: Job? = null
|
||||
|
||||
fun save(
|
||||
onResult: (List<SaveResult>, String) -> Unit
|
||||
) {
|
||||
_isSaving.value = false
|
||||
savingJob?.cancel()
|
||||
savingJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
_isSaving.value = true
|
||||
_left.value = 1
|
||||
_done.value = 0
|
||||
when (val type = _type.value) {
|
||||
is Screen.JxlTools.Type.JpegToJxl -> {
|
||||
val results = mutableListOf<SaveResult>()
|
||||
val jpegUris = type.jpegImageUris?.map { it.toString() }
|
||||
?: emptyList()
|
||||
_left.value = jpegUris.size
|
||||
jxlTranscoder.jpegToJxl(jpegUris) { uri, jxlBytes ->
|
||||
results.add(
|
||||
fileController.save(
|
||||
saveTarget = FileSaveTarget(
|
||||
originalUri = uri,
|
||||
filename = fileController.constructImageFilename(
|
||||
ImageSaveTarget<ExifInterface>(
|
||||
imageInfo = ImageInfo(
|
||||
imageFormat = ImageFormat.Jxl.Lossless,
|
||||
originalUri = uri
|
||||
),
|
||||
originalUri = uri,
|
||||
sequenceNumber = done + 1,
|
||||
metadata = null,
|
||||
data = jxlBytes
|
||||
),
|
||||
forceNotAddSizeInFilename = true
|
||||
),
|
||||
data = jxlBytes,
|
||||
imageFormat = ImageFormat.Jxl.Lossless
|
||||
),
|
||||
keepOriginalMetadata = true
|
||||
)
|
||||
)
|
||||
_done.update { it + 1 }
|
||||
}
|
||||
|
||||
onResult(results, fileController.savingPath)
|
||||
}
|
||||
|
||||
is Screen.JxlTools.Type.JxlToJpeg -> {
|
||||
val results = mutableListOf<SaveResult>()
|
||||
val jxlUris = type.jxlImageUris?.map { it.toString() }
|
||||
?: emptyList()
|
||||
_left.value = jxlUris.size
|
||||
jxlTranscoder.jxlToJpeg(jxlUris) { uri, jpegBytes ->
|
||||
results.add(
|
||||
fileController.save(
|
||||
saveTarget = FileSaveTarget(
|
||||
originalUri = uri,
|
||||
filename = fileController.constructImageFilename(
|
||||
ImageSaveTarget<ExifInterface>(
|
||||
imageInfo = ImageInfo(
|
||||
imageFormat = ImageFormat.Jpg,
|
||||
originalUri = uri
|
||||
),
|
||||
originalUri = uri,
|
||||
sequenceNumber = done + 1,
|
||||
metadata = null,
|
||||
data = jpegBytes
|
||||
),
|
||||
forceNotAddSizeInFilename = true
|
||||
),
|
||||
data = jpegBytes,
|
||||
imageFormat = ImageFormat.Jpg
|
||||
),
|
||||
keepOriginalMetadata = true
|
||||
)
|
||||
)
|
||||
_done.update { it + 1 }
|
||||
}
|
||||
|
||||
onResult(results, fileController.savingPath)
|
||||
}
|
||||
|
||||
null -> Unit
|
||||
}
|
||||
_isSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelSaving() {
|
||||
savingJob?.cancel()
|
||||
savingJob = null
|
||||
_isSaving.value = false
|
||||
}
|
||||
|
||||
fun performSharing(onComplete: () -> Unit) {
|
||||
_isSaving.value = false
|
||||
savingJob?.cancel()
|
||||
savingJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
_isSaving.value = true
|
||||
_left.value = 1
|
||||
_done.value = 0
|
||||
when (val type = _type.value) {
|
||||
is Screen.JxlTools.Type.JpegToJxl -> {
|
||||
val jpegUris = type.jpegImageUris?.map { it.toString() } ?: emptyList()
|
||||
_left.value = jpegUris.size
|
||||
|
||||
val results = mutableListOf<String?>()
|
||||
jxlTranscoder.jpegToJxl(jpegUris) { uri, jxlBytes ->
|
||||
results.add(
|
||||
shareProvider.cacheByteArray(
|
||||
byteArray = jxlBytes,
|
||||
filename = fileController.constructImageFilename(
|
||||
ImageSaveTarget<ExifInterface>(
|
||||
imageInfo = ImageInfo(
|
||||
imageFormat = ImageFormat.Jxl.Lossless,
|
||||
originalUri = uri
|
||||
),
|
||||
originalUri = uri,
|
||||
sequenceNumber = done + 1,
|
||||
metadata = null,
|
||||
data = jxlBytes
|
||||
),
|
||||
forceNotAddSizeInFilename = true
|
||||
)
|
||||
)
|
||||
)
|
||||
_done.update { it + 1 }
|
||||
}
|
||||
|
||||
shareProvider.shareUris(results.filterNotNull())
|
||||
onComplete()
|
||||
}
|
||||
|
||||
is Screen.JxlTools.Type.JxlToJpeg -> {
|
||||
val jxlUris = type.jxlImageUris?.map { it.toString() } ?: emptyList()
|
||||
_left.value = jxlUris.size
|
||||
|
||||
val results = mutableListOf<String?>()
|
||||
jxlTranscoder.jxlToJpeg(jxlUris) { uri, jpegBytes ->
|
||||
results.add(
|
||||
shareProvider.cacheByteArray(
|
||||
byteArray = jpegBytes,
|
||||
filename = fileController.constructImageFilename(
|
||||
saveTarget = ImageSaveTarget<ExifInterface>(
|
||||
imageInfo = ImageInfo(
|
||||
imageFormat = ImageFormat.Jpg,
|
||||
originalUri = uri
|
||||
),
|
||||
originalUri = uri,
|
||||
sequenceNumber = done + 1,
|
||||
metadata = null,
|
||||
data = jpegBytes
|
||||
),
|
||||
forceNotAddSizeInFilename = true
|
||||
)
|
||||
)
|
||||
)
|
||||
_done.update { it + 1 }
|
||||
}
|
||||
|
||||
shareProvider.shareUris(results.filterNotNull())
|
||||
onComplete()
|
||||
}
|
||||
|
||||
null -> Unit
|
||||
}
|
||||
|
||||
_isSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
fun clearAll() = setType(null)
|
||||
|
||||
fun removeUri(uri: Uri) {
|
||||
_type.update {
|
||||
when (val type = it) {
|
||||
is Screen.JxlTools.Type.JpegToJxl -> type.copy(type.jpegImageUris?.minus(uri))
|
||||
is Screen.JxlTools.Type.JxlToJpeg -> type.copy(type.jxlImageUris?.minus(uri))
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -48,4 +48,5 @@ dependencies {
|
||||
implementation(projects.feature.gifTools)
|
||||
implementation(projects.feature.apngTools)
|
||||
implementation(projects.feature.zip)
|
||||
implementation(projects.feature.jxlTools)
|
||||
}
|
@ -53,6 +53,7 @@ import ru.tech.imageresizershrinker.feature.gif_tools.presentation.GifToolsScree
|
||||
import ru.tech.imageresizershrinker.feature.gradient_maker.presentation.GradientMakerScreen
|
||||
import ru.tech.imageresizershrinker.feature.image_preview.presentation.ImagePreviewScreen
|
||||
import ru.tech.imageresizershrinker.feature.image_stitch.presentation.ImageStitchingScreen
|
||||
import ru.tech.imageresizershrinker.feature.jxl_tools.presentation.JxlToolsScreen
|
||||
import ru.tech.imageresizershrinker.feature.limits_resize.presentation.LimitsResizeScreen
|
||||
import ru.tech.imageresizershrinker.feature.load_net_image.presentation.LoadNetImageScreen
|
||||
import ru.tech.imageresizershrinker.feature.main.presentation.MainScreen
|
||||
@ -290,6 +291,13 @@ fun ScreenSelector(
|
||||
onGoBack = onGoBack
|
||||
)
|
||||
}
|
||||
|
||||
is Screen.JxlTools -> {
|
||||
JxlToolsScreen(
|
||||
typeState = screen.type,
|
||||
onGoBack = onGoBack
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val currentScreen by remember(navController.backstack.entries) {
|
||||
|
@ -333,7 +333,7 @@ class PdfToolsViewModel @Inject constructor(
|
||||
},
|
||||
onComplete = {
|
||||
_isSaving.value = false
|
||||
shareProvider.shareImageUris(uris.filterNotNull())
|
||||
shareProvider.shareUris(uris.filterNotNull())
|
||||
onComplete()
|
||||
}
|
||||
)
|
||||
|
@ -11,9 +11,9 @@ compose-compiler = "1.5.10"
|
||||
|
||||
avifCoderCoil = "1.6.4"
|
||||
avifCoder = "1.6.4"
|
||||
aire = "0.9.84"
|
||||
jxlCoderCoil = "1.11.1"
|
||||
jxlCoder = "1.11.1"
|
||||
aire = "0.9.85"
|
||||
jxlCoderCoil = "2.0.0"
|
||||
jxlCoder = "2.0.0"
|
||||
|
||||
tesseract = "4.3.0"
|
||||
composeVersion = "1.7.0-alpha03"
|
||||
|
@ -65,6 +65,7 @@ include(":feature:gradient-maker")
|
||||
include(":feature:gif-tools")
|
||||
include(":feature:apng-tools")
|
||||
include(":feature:zip")
|
||||
include(":feature:jxl-tools")
|
||||
|
||||
include(":core:settings")
|
||||
include(":core:resources")
|
||||
|
Reference in New Issue
Block a user