mirror of
https://github.com/T8RIN/ImageToolbox.git
synced 2025-05-17 21:45:59 +08:00
Start working on base64 encode/decode to image by #1545
This commit is contained in:
@ -27,7 +27,7 @@ import coil3.fetch.Fetcher
|
||||
import coil3.fetch.SourceFetchResult
|
||||
import coil3.request.Options
|
||||
import okio.Buffer
|
||||
import java.util.regex.Pattern
|
||||
import ru.tech.imageresizershrinker.core.domain.utils.isBase64
|
||||
|
||||
internal class Base64Fetcher(
|
||||
private val options: Options,
|
||||
@ -56,20 +56,12 @@ internal class Base64Fetcher(
|
||||
imageLoader: ImageLoader,
|
||||
): Fetcher? {
|
||||
val stripped = data.toString().substringAfter(",")
|
||||
return if (data.toString().startsWith("data:image") || isBase64(stripped)) {
|
||||
return if (isBase64(stripped)) {
|
||||
Base64Fetcher(
|
||||
options = options,
|
||||
base64 = stripped
|
||||
)
|
||||
} else null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun isBase64(data: String) = BASE64_PATTERN.matcher(data).matches()
|
||||
|
||||
private val BASE64_PATTERN = Pattern.compile(
|
||||
"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,17 @@
|
||||
|
||||
package ru.tech.imageresizershrinker.core.domain.utils
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
inline fun <reified T> T?.notNullAnd(
|
||||
predicate: (T) -> Boolean
|
||||
): Boolean = if (this != null) predicate(this)
|
||||
else false
|
||||
else false
|
||||
|
||||
fun isBase64(data: String) = data.trim { it.isWhitespace() }
|
||||
.isNotEmpty()
|
||||
.and(BASE64_PATTERN.matcher(data).matches() || data.startsWith("data:image"))
|
||||
|
||||
private val BASE64_PATTERN = Pattern.compile(
|
||||
"^(?=(.{4})*\$)[A-Za-z0-9+/]*={0,2}\$"
|
||||
)
|
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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.core.resources.icons
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val Icons.Rounded.Base64: ImageVector by lazy {
|
||||
ImageVector.Builder(
|
||||
name = "Base64Rounded",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f
|
||||
).apply {
|
||||
path(fill = SolidColor(Color(0xFF000000))) {
|
||||
moveTo(6.566f, 16.554f)
|
||||
horizontalLineToRelative(0.909f)
|
||||
verticalLineToRelative(0.909f)
|
||||
horizontalLineToRelative(-0.909f)
|
||||
close()
|
||||
}
|
||||
path(fill = SolidColor(Color(0xFF000000))) {
|
||||
moveTo(14.526f, 8.969f)
|
||||
lineToRelative(1.756f, 0f)
|
||||
lineToRelative(0.307f, -1.756f)
|
||||
lineToRelative(-1.756f, 0f)
|
||||
close()
|
||||
}
|
||||
path(fill = SolidColor(Color(0xFF000000))) {
|
||||
moveTo(18.307f, 3f)
|
||||
horizontalLineToRelative(-5.259f)
|
||||
horizontalLineTo(5.694f)
|
||||
horizontalLineTo(5.693f)
|
||||
curveTo(4.206f, 3f, 3f, 4.206f, 3f, 5.693f)
|
||||
verticalLineToRelative(12.613f)
|
||||
curveToRelative(0f, 1.302f, 0.923f, 2.388f, 2.151f, 2.639f)
|
||||
curveToRelative(0.105f, 0.021f, 0.215f, 0.024f, 0.324f, 0.033f)
|
||||
curveTo(5.548f, 20.984f, 5.618f, 21f, 5.693f, 21f)
|
||||
horizontalLineToRelative(0f)
|
||||
horizontalLineToRelative(8.294f)
|
||||
horizontalLineToRelative(4.319f)
|
||||
curveTo(19.794f, 21f, 21f, 19.794f, 21f, 18.307f)
|
||||
verticalLineToRelative(-5.259f)
|
||||
curveToRelative(0f, 0f, 0f, 0f, 0f, 0f)
|
||||
verticalLineToRelative(-7.354f)
|
||||
curveTo(21f, 4.206f, 19.794f, 3f, 18.307f, 3f)
|
||||
close()
|
||||
moveTo(8.383f, 14.737f)
|
||||
horizontalLineTo(6.566f)
|
||||
verticalLineToRelative(0.909f)
|
||||
horizontalLineToRelative(0.909f)
|
||||
curveToRelative(0.502f, 0f, 0.908f, 0.407f, 0.908f, 0.909f)
|
||||
verticalLineToRelative(0.909f)
|
||||
curveToRelative(0f, 0.502f, -0.407f, 0.908f, -0.908f, 0.908f)
|
||||
horizontalLineTo(6.566f)
|
||||
curveToRelative(-0.502f, 0f, -0.909f, -0.407f, -0.909f, -0.908f)
|
||||
verticalLineToRelative(-2.726f)
|
||||
curveToRelative(0f, -0.502f, 0.407f, -0.908f, 0.909f, -0.908f)
|
||||
horizontalLineToRelative(1.817f)
|
||||
verticalLineTo(14.737f)
|
||||
close()
|
||||
moveTo(11.942f, 18.371f)
|
||||
horizontalLineToRelative(-0.909f)
|
||||
verticalLineToRelative(-1.817f)
|
||||
horizontalLineTo(9.217f)
|
||||
verticalLineToRelative(-2.725f)
|
||||
horizontalLineToRelative(0.909f)
|
||||
verticalLineToRelative(1.817f)
|
||||
horizontalLineToRelative(0.908f)
|
||||
verticalLineToRelative(-1.817f)
|
||||
horizontalLineToRelative(0.909f)
|
||||
verticalLineTo(18.371f)
|
||||
close()
|
||||
moveTo(18.344f, 7.213f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineTo(17.159f, 8.969f)
|
||||
horizontalLineToRelative(0.878f)
|
||||
lineToRelative(-0.158f, 0.878f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineToRelative(-0.154f, 0.878f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineToRelative(0.154f, -0.878f)
|
||||
horizontalLineToRelative(-1.756f)
|
||||
lineToRelative(-0.154f, 0.878f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineToRelative(0.154f, -0.878f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineToRelative(0.158f, -0.878f)
|
||||
horizontalLineToRelative(0.878f)
|
||||
lineToRelative(0.307f, -1.756f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineToRelative(0.154f, -0.878f)
|
||||
horizontalLineToRelative(0.878f)
|
||||
lineToRelative(0.158f, -0.878f)
|
||||
horizontalLineToRelative(0.878f)
|
||||
lineToRelative(-0.158f, 0.878f)
|
||||
horizontalLineToRelative(1.756f)
|
||||
lineToRelative(0.158f, -0.878f)
|
||||
horizontalLineToRelative(0.878f)
|
||||
lineToRelative(-0.158f, 0.878f)
|
||||
horizontalLineToRelative(0.878f)
|
||||
lineTo(18.344f, 7.213f)
|
||||
close()
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
|
||||
val Icons.Outlined.Base64: ImageVector by lazy {
|
||||
ImageVector.Builder(
|
||||
name = "Base64Outlined",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f
|
||||
).apply {
|
||||
path(fill = SolidColor(Color(0xFF000000))) {
|
||||
moveTo(6.566f, 13.829f)
|
||||
curveToRelative(-0.502f, 0f, -0.908f, 0.407f, -0.908f, 0.908f)
|
||||
verticalLineToRelative(2.725f)
|
||||
curveToRelative(0f, 0.502f, 0.407f, 0.908f, 0.908f, 0.908f)
|
||||
horizontalLineToRelative(0.908f)
|
||||
curveToRelative(0.502f, 0f, 0.908f, -0.407f, 0.908f, -0.908f)
|
||||
verticalLineToRelative(-0.908f)
|
||||
curveToRelative(0f, -0.502f, -0.407f, -0.908f, -0.908f, -0.908f)
|
||||
horizontalLineToRelative(-0.908f)
|
||||
verticalLineToRelative(-0.908f)
|
||||
horizontalLineToRelative(1.817f)
|
||||
verticalLineToRelative(-0.908f)
|
||||
horizontalLineTo(6.566f)
|
||||
moveTo(6.566f, 16.554f)
|
||||
horizontalLineToRelative(0.908f)
|
||||
verticalLineToRelative(0.908f)
|
||||
horizontalLineToRelative(-0.908f)
|
||||
verticalLineTo(16.554f)
|
||||
close()
|
||||
}
|
||||
path(fill = SolidColor(Color(0xFF000000))) {
|
||||
moveTo(9.217f, 13.829f)
|
||||
verticalLineToRelative(2.725f)
|
||||
horizontalLineToRelative(1.817f)
|
||||
verticalLineToRelative(1.817f)
|
||||
horizontalLineToRelative(0.908f)
|
||||
verticalLineToRelative(-4.542f)
|
||||
horizontalLineToRelative(-0.908f)
|
||||
verticalLineToRelative(1.817f)
|
||||
horizontalLineToRelative(-0.908f)
|
||||
verticalLineToRelative(-1.817f)
|
||||
horizontalLineTo(9.217f)
|
||||
close()
|
||||
}
|
||||
path(fill = SolidColor(Color(0xFF000000))) {
|
||||
moveTo(18.344f, 7.213f)
|
||||
lineToRelative(0.154f, -0.878f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineToRelative(0.158f, -0.878f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineToRelative(-0.158f, 0.878f)
|
||||
horizontalLineToRelative(-1.756f)
|
||||
lineToRelative(0.158f, -0.878f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineToRelative(-0.158f, 0.878f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineToRelative(-0.154f, 0.878f)
|
||||
horizontalLineToRelative(0.878f)
|
||||
lineToRelative(-0.307f, 1.756f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineTo(12.612f, 9.847f)
|
||||
horizontalLineToRelative(0.878f)
|
||||
lineToRelative(-0.154f, 0.878f)
|
||||
horizontalLineToRelative(0.878f)
|
||||
lineToRelative(0.154f, -0.878f)
|
||||
horizontalLineToRelative(1.756f)
|
||||
lineToRelative(-0.154f, 0.878f)
|
||||
horizontalLineToRelative(0.878f)
|
||||
lineToRelative(0.154f, -0.878f)
|
||||
horizontalLineToRelative(0.878f)
|
||||
lineToRelative(0.158f, -0.878f)
|
||||
horizontalLineToRelative(-0.878f)
|
||||
lineToRelative(0.307f, -1.756f)
|
||||
horizontalLineTo(18.344f)
|
||||
close()
|
||||
moveTo(16.281f, 8.969f)
|
||||
horizontalLineToRelative(-1.756f)
|
||||
lineToRelative(0.307f, -1.756f)
|
||||
horizontalLineToRelative(1.756f)
|
||||
lineTo(16.281f, 8.969f)
|
||||
close()
|
||||
}
|
||||
path(fill = SolidColor(Color(0xFF000000))) {
|
||||
moveTo(21f, 5.693f)
|
||||
curveTo(21f, 4.206f, 19.794f, 3f, 18.307f, 3f)
|
||||
horizontalLineToRelative(-5.259f)
|
||||
horizontalLineTo(5.694f)
|
||||
horizontalLineTo(5.693f)
|
||||
curveTo(4.206f, 3f, 3f, 4.206f, 3f, 5.693f)
|
||||
verticalLineToRelative(12.613f)
|
||||
curveToRelative(0f, 1.302f, 0.923f, 2.388f, 2.151f, 2.639f)
|
||||
curveToRelative(0.105f, 0.021f, 0.215f, 0.024f, 0.324f, 0.033f)
|
||||
curveTo(5.548f, 20.984f, 5.618f, 21f, 5.693f, 21f)
|
||||
horizontalLineToRelative(0f)
|
||||
horizontalLineToRelative(8.294f)
|
||||
horizontalLineToRelative(4.319f)
|
||||
curveTo(19.794f, 21f, 21f, 19.794f, 21f, 18.307f)
|
||||
verticalLineToRelative(-5.259f)
|
||||
curveToRelative(0f, 0f, 0f, 0f, 0f, 0f)
|
||||
verticalLineTo(5.693f)
|
||||
close()
|
||||
moveTo(19f, 17.715f)
|
||||
curveTo(19f, 18.424f, 18.424f, 19f, 17.715f, 19f)
|
||||
horizontalLineToRelative(-3.737f)
|
||||
horizontalLineTo(6.286f)
|
||||
curveToRelative(-0.71f, 0f, -1.285f, -0.576f, -1.285f, -1.285f)
|
||||
verticalLineTo(6.285f)
|
||||
curveTo(5f, 5.576f, 5.576f, 5f, 6.286f, 5f)
|
||||
horizontalLineToRelative(6.778f)
|
||||
horizontalLineToRelative(4.651f)
|
||||
curveToRelative(0.71f, 0f, 1.285f, 0.576f, 1.285f, 1.285f)
|
||||
verticalLineToRelative(3.737f)
|
||||
curveToRelative(-0f, 0f, -0f, 0f, -0f, 0f)
|
||||
verticalLineTo(17.715f)
|
||||
close()
|
||||
}
|
||||
}.build()
|
||||
}
|
@ -1489,7 +1489,7 @@
|
||||
<string name="material_you_slider_sub">Modern Material You slider, futuristic, fresh, acessible</string>
|
||||
<string name="apply">Apply</string>
|
||||
<string name="center_align_dialog_buttons">Center Dialog Buttons</string>
|
||||
<string name="center_align_dialog_buttons_sub">Buttons of dialogs will be postioned at center instead of left side if possible</string>
|
||||
<string name="center_align_dialog_buttons_sub">Buttons of dialogs will be positioned at center instead of left side if possible</string>
|
||||
<string name="open_source_licenses">Open Source Licenses</string>
|
||||
<string name="open_source_licenses_sub">View licenses of open source libraries used in this app</string>
|
||||
<string name="area">Area</string>
|
||||
@ -1510,4 +1510,15 @@
|
||||
<string name="clear_selection">Clear Selection</string>
|
||||
<string name="settings_group_visibility_hidden">Setting group \"%s\" will be collapsed by default</string>
|
||||
<string name="settings_group_visibility_visible">Setting group \"%s\" will be expanded by default</string>
|
||||
<string name="base_64_conversion">Base64 conversion</string>
|
||||
<string name="base_64_conversion_sub">Decode base64 string to image, or encode image to base64 format</string>
|
||||
<string name="base_64">Base64</string>
|
||||
<string name="not_a_valid_base_64">Pasted string is not a valid Base64 string</string>
|
||||
<string name="copy_not_a_valid_base_64">Cannot copy empty or invalid Base64 string</string>
|
||||
<string name="paste_base_64">Paste Base64</string>
|
||||
<string name="copy_base_64">Copy Base64</string>
|
||||
<string name="base_64_tips">Load image to copy or save Base64 string, if you have any, you can paste it above to obtain image</string>
|
||||
<string name="save_base_64">Save Base64</string>
|
||||
<string name="share_base_64">Share Base64</string>
|
||||
<string name="options">Options</string>
|
||||
</resources>
|
@ -20,7 +20,7 @@ package ru.tech.imageresizershrinker.core.ui.utils.helper
|
||||
import androidx.compose.foundation.Indication
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.RippleDefaults
|
||||
import androidx.compose.material.ripple
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
@ -55,6 +55,7 @@ import kotlinx.serialization.encoding.Encoder
|
||||
import ru.tech.imageresizershrinker.core.resources.R
|
||||
import ru.tech.imageresizershrinker.core.resources.icons.Apng
|
||||
import ru.tech.imageresizershrinker.core.resources.icons.ApngBox
|
||||
import ru.tech.imageresizershrinker.core.resources.icons.Base64
|
||||
import ru.tech.imageresizershrinker.core.resources.icons.CropSmall
|
||||
import ru.tech.imageresizershrinker.core.resources.icons.Draw
|
||||
import ru.tech.imageresizershrinker.core.resources.icons.Encrypted
|
||||
@ -133,6 +134,7 @@ sealed class Screen(
|
||||
is CollageMaker -> "Collage_Maker"
|
||||
is LibrariesInfo -> "Libraries_Info"
|
||||
is MarkupLayers -> "Markup_Layers"
|
||||
is Base64Conversion -> "Base64_Conversion"
|
||||
}
|
||||
|
||||
val icon: ImageVector?
|
||||
@ -177,6 +179,7 @@ sealed class Screen(
|
||||
NoiseGeneration -> Icons.Outlined.Grain
|
||||
is CollageMaker -> Icons.Outlined.AutoAwesomeMosaic
|
||||
is MarkupLayers -> Icons.Outlined.Stack
|
||||
is Base64Conversion -> Icons.Outlined.Base64
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@ -804,6 +807,15 @@ sealed class Screen(
|
||||
subtitle = R.string.markup_layers_sub
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Base64Conversion(
|
||||
val uri: KUri? = null
|
||||
) : Screen(
|
||||
id = 35,
|
||||
title = R.string.base_64_conversion,
|
||||
subtitle = R.string.base_64_conversion_sub
|
||||
)
|
||||
|
||||
companion object {
|
||||
val typedEntries by lazy {
|
||||
listOf(
|
||||
@ -854,6 +866,7 @@ sealed class Screen(
|
||||
DocumentScanner,
|
||||
ScanQrCode(),
|
||||
ColorTools,
|
||||
Base64Conversion(),
|
||||
GifTools(),
|
||||
JxlTools(),
|
||||
ApngTools(),
|
||||
@ -872,7 +885,7 @@ sealed class Screen(
|
||||
typedEntries.flatMap { it.first }.sortedBy { it.id }
|
||||
}
|
||||
|
||||
const val FEATURES_COUNT = 55
|
||||
const val FEATURES_COUNT = 56
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,13 +25,21 @@ import android.os.Build
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.View
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import androidx.compose.foundation.Indication
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedback
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.core.content.getSystemService
|
||||
|
||||
private fun View.vibrate() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
@ -127,3 +135,42 @@ fun rememberCustomHapticFeedback(hapticsStrength: Int): HapticFeedback {
|
||||
|
||||
return haptics
|
||||
}
|
||||
|
||||
fun Modifier.hapticsClickable(
|
||||
interactionSource: MutableInteractionSource?,
|
||||
indication: Indication?,
|
||||
enabled: Boolean = true,
|
||||
onClickLabel: String? = null,
|
||||
role: Role? = null,
|
||||
onClick: () -> Unit
|
||||
): Modifier = this.composed {
|
||||
val haptics = LocalHapticFeedback.current
|
||||
|
||||
Modifier.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = indication,
|
||||
enabled = enabled,
|
||||
onClickLabel = onClickLabel,
|
||||
role = role,
|
||||
onClick = {
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onClick()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun Modifier.hapticsClickable(
|
||||
enabled: Boolean = true,
|
||||
onClickLabel: String? = null,
|
||||
role: Role? = null,
|
||||
onClick: () -> Unit
|
||||
): Modifier = this.composed {
|
||||
hapticsClickable(
|
||||
interactionSource = null,
|
||||
indication = LocalIndication.current,
|
||||
enabled = enabled,
|
||||
onClickLabel = onClickLabel,
|
||||
role = role,
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
@ -273,7 +273,7 @@ fun RoundedTextFieldColors(
|
||||
isError: Boolean,
|
||||
containerColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
focusedIndicatorColor: Color = MaterialTheme.colorScheme.primary,
|
||||
unfocusedIndicatorColor: Color = MaterialTheme.colorScheme.surfaceVariant.inverse({ 0.2f })
|
||||
unfocusedIndicatorColor: Color = MaterialTheme.colorScheme.surfaceVariant.inverse({ 0.4f })
|
||||
): TextFieldColors =
|
||||
MaterialTheme.colorScheme.run {
|
||||
val containerColorNew = if (isError) {
|
||||
|
@ -22,9 +22,32 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.isSpecified
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.toBitmap
|
||||
import com.t8rin.dynamic.theme.LocalDynamicThemeState
|
||||
import ru.tech.imageresizershrinker.core.settings.presentation.provider.LocalSettingsState
|
||||
import ru.tech.imageresizershrinker.core.settings.presentation.provider.rememberAppColorTuple
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.provider.LocalImageLoader
|
||||
|
||||
@Composable
|
||||
fun <T : Any> AutoContentBasedColors(
|
||||
model: T?,
|
||||
allowChangeColor: Boolean = true
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val imageLoader = LocalImageLoader.current
|
||||
|
||||
AutoContentBasedColors(
|
||||
model = model,
|
||||
selector = {
|
||||
imageLoader.execute(
|
||||
ImageRequest.Builder(context).data(model).build()
|
||||
).image?.toBitmap()
|
||||
},
|
||||
allowChangeColor = allowChangeColor
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T : Any> AutoContentBasedColors(
|
||||
|
1
feature/base64-conversion/.gitignore
vendored
Normal file
1
feature/base64-conversion/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
25
feature/base64-conversion/build.gradle.kts
Normal file
25
feature/base64-conversion/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.base64_conversion"
|
4
feature/base64-conversion/src/main/AndroidManifest.xml
Normal file
4
feature/base64-conversion/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.base64_conversion.data
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Base64
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import kotlinx.coroutines.withContext
|
||||
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.Quality
|
||||
import ru.tech.imageresizershrinker.core.domain.saving.FileController
|
||||
import ru.tech.imageresizershrinker.feature.base64_conversion.domain.Base64Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AndroidBase64Converter @Inject constructor(
|
||||
private val imageGetter: ImageGetter<Bitmap, ExifInterface>,
|
||||
private val fileController: FileController,
|
||||
private val shareProvider: ShareProvider<Bitmap>,
|
||||
private val imageCompressor: ImageCompressor<Bitmap>,
|
||||
dispatchersHolder: DispatchersHolder
|
||||
) : Base64Converter, DispatchersHolder by dispatchersHolder {
|
||||
|
||||
override suspend fun decode(
|
||||
base64: String
|
||||
): String? = withContext(ioDispatcher) {
|
||||
val decoded = runCatching {
|
||||
Base64.decode(base64, Base64.DEFAULT or Base64.NO_WRAP)
|
||||
}.getOrNull() ?: return@withContext null
|
||||
|
||||
imageGetter.getImage(decoded)?.let { bitmap ->
|
||||
shareProvider.cacheData(
|
||||
writeData = {
|
||||
it.writeBytes(
|
||||
imageCompressor.compress(
|
||||
image = bitmap,
|
||||
imageFormat = ImageFormat.Png.Lossless,
|
||||
quality = Quality.Base()
|
||||
)
|
||||
)
|
||||
},
|
||||
filename = "Base64_decoded_${System.currentTimeMillis()}.png"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun encode(
|
||||
uri: String
|
||||
): String = withContext(ioDispatcher) {
|
||||
Base64.encodeToString(fileController.readBytes(uri), Base64.DEFAULT or Base64.NO_WRAP)
|
||||
}
|
||||
|
||||
}
|
@ -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.base64_conversion.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import ru.tech.imageresizershrinker.feature.base64_conversion.data.AndroidBase64Converter
|
||||
import ru.tech.imageresizershrinker.feature.base64_conversion.domain.Base64Converter
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface Base64ConversionModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun provideConverter(
|
||||
impl: AndroidBase64Converter
|
||||
): Base64Converter
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.base64_conversion.domain
|
||||
|
||||
interface Base64Converter {
|
||||
|
||||
/**
|
||||
* @param base64 - string to decode to uri
|
||||
* @return uri
|
||||
**/
|
||||
suspend fun decode(base64: String): String?
|
||||
|
||||
/**
|
||||
* @param uri - uri to decode
|
||||
* @return base64
|
||||
**/
|
||||
suspend fun encode(uri: String): String
|
||||
|
||||
}
|
@ -0,0 +1,435 @@
|
||||
/*
|
||||
* 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.base64_conversion.presentation
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.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.outlined.Info
|
||||
import androidx.compose.material.icons.outlined.Save
|
||||
import androidx.compose.material.icons.outlined.Share
|
||||
import androidx.compose.material.icons.rounded.ContentPaste
|
||||
import androidx.compose.material.icons.rounded.CopyAll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
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 coil3.toBitmap
|
||||
import ru.tech.imageresizershrinker.core.domain.utils.isBase64
|
||||
import ru.tech.imageresizershrinker.core.resources.R
|
||||
import ru.tech.imageresizershrinker.core.resources.icons.Base64
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.content_pickers.Picker
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.content_pickers.rememberImagePicker
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.ImageUtils.safeAspectRatio
|
||||
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.provider.LocalComponentActivity
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.provider.rememberLocalEssentials
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.AdaptiveLayoutScreen
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.BottomButtonsBlock
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.ShareButton
|
||||
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.LoadingDialog
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.OneTimeImagePickingDialog
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.haptics.hapticsClickable
|
||||
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.other.TopAppBarEmoji
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.sheets.ProcessImagesPreferenceSheet
|
||||
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.AutoContentBasedColors
|
||||
import ru.tech.imageresizershrinker.feature.base64_conversion.presentation.components.Base64ConversionComponent
|
||||
|
||||
@Composable
|
||||
fun Base64ConversionContent(
|
||||
component: Base64ConversionComponent
|
||||
) {
|
||||
AutoContentBasedColors(component.uri)
|
||||
|
||||
val isPortrait by isPortraitOrientationAsState()
|
||||
|
||||
val context = LocalComponentActivity.current
|
||||
|
||||
val essentials = rememberLocalEssentials()
|
||||
val showConfetti: () -> Unit = essentials::showConfetti
|
||||
|
||||
val imagePicker = rememberImagePicker(onSuccess = component::setUri)
|
||||
val pickImage = imagePicker::pickImage
|
||||
|
||||
val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = {
|
||||
component.saveBitmap(
|
||||
oneTimeSaveLocationUri = it,
|
||||
onComplete = essentials::parseSaveResult
|
||||
)
|
||||
}
|
||||
|
||||
AdaptiveLayoutScreen(
|
||||
shouldDisableBackHandler = true,
|
||||
title = {
|
||||
TopAppBarTitle(
|
||||
title = stringResource(R.string.base_64_conversion),
|
||||
input = component.uri,
|
||||
isLoading = component.isImageLoading,
|
||||
size = null
|
||||
)
|
||||
},
|
||||
onGoBack = component.onGoBack,
|
||||
topAppBarPersistentActions = {
|
||||
if (component.uri == null) {
|
||||
TopAppBarEmoji()
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
var editSheetData by remember {
|
||||
mutableStateOf(listOf<Uri>())
|
||||
}
|
||||
ShareButton(
|
||||
enabled = component.uri != null,
|
||||
onShare = {
|
||||
component.shareBitmap(showConfetti)
|
||||
},
|
||||
onCopy = { manager ->
|
||||
component.cacheCurrentImage { uri ->
|
||||
manager.setClip(uri.asClip(context))
|
||||
showConfetti()
|
||||
}
|
||||
},
|
||||
onEdit = {
|
||||
component.cacheCurrentImage { uri ->
|
||||
editSheetData = listOf(uri)
|
||||
}
|
||||
}
|
||||
)
|
||||
ProcessImagesPreferenceSheet(
|
||||
uris = editSheetData,
|
||||
visible = editSheetData.isNotEmpty(),
|
||||
onDismiss = { editSheetData = emptyList() },
|
||||
onNavigate = component.onNavigate
|
||||
)
|
||||
},
|
||||
imagePreview = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.container()
|
||||
.padding(4.dp)
|
||||
.animateContentSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
var aspectRatio by remember {
|
||||
mutableFloatStateOf(1f)
|
||||
}
|
||||
Picture(
|
||||
model = component.base64String,
|
||||
contentScale = ContentScale.FillBounds,
|
||||
modifier = Modifier.aspectRatio(aspectRatio),
|
||||
onSuccess = {
|
||||
aspectRatio = it.result.image.toBitmap().safeAspectRatio
|
||||
},
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
isLoadingFromDifferentPlace = component.isImageLoading
|
||||
)
|
||||
}
|
||||
},
|
||||
controls = {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Base64ConversionTiles(component)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
if (component.uri != null) {
|
||||
if (component.imageFormat.canChangeCompressionValue) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
}
|
||||
QualitySelector(
|
||||
imageFormat = component.imageFormat,
|
||||
quality = component.quality,
|
||||
onQualityChange = component::setQuality
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
ImageFormatSelector(
|
||||
value = component.imageFormat,
|
||||
onValueChange = component::setImageFormat
|
||||
)
|
||||
} else {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.container(
|
||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(0.2f),
|
||||
resultPadding = 0.dp,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Info,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.5f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.base_64_tips),
|
||||
fontSize = 12.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.5f)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
buttons = {
|
||||
var showFolderSelectionDialog by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
var showOneTimeImagePickingDialog by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
BottomButtonsBlock(
|
||||
targetState = (component.uri == null) to isPortrait,
|
||||
onSecondaryButtonClick = pickImage,
|
||||
onSecondaryButtonLongClick = {
|
||||
showOneTimeImagePickingDialog = true
|
||||
},
|
||||
onPrimaryButtonClick = {
|
||||
saveBitmap(null)
|
||||
},
|
||||
onPrimaryButtonLongClick = {
|
||||
showFolderSelectionDialog = true
|
||||
},
|
||||
actions = {
|
||||
if (isPortrait) it()
|
||||
}
|
||||
)
|
||||
OneTimeSaveLocationSelectionDialog(
|
||||
visible = showFolderSelectionDialog,
|
||||
onDismiss = { showFolderSelectionDialog = false },
|
||||
onSaveRequest = saveBitmap,
|
||||
formatForFilenameSelection = component.getFormatForFilenameSelection()
|
||||
)
|
||||
OneTimeImagePickingDialog(
|
||||
onDismiss = { showOneTimeImagePickingDialog = false },
|
||||
picker = Picker.Single,
|
||||
imagePicker = imagePicker,
|
||||
visible = showOneTimeImagePickingDialog
|
||||
)
|
||||
},
|
||||
canShowScreenData = true,
|
||||
isPortrait = isPortrait,
|
||||
)
|
||||
|
||||
LoadingDialog(
|
||||
visible = component.isSaving,
|
||||
onCancelLoading = component::cancelSaving
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun Base64ConversionTiles(component: Base64ConversionComponent) {
|
||||
val essentials = rememberLocalEssentials()
|
||||
val context = LocalContext.current
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
val pasteTile: @Composable RowScope.(shape: Shape) -> Unit = { shape ->
|
||||
Tile(
|
||||
onClick = {
|
||||
val text = clipboardManager.getText()?.text?.substringAfter(",") ?: ""
|
||||
if (isBase64(text)) {
|
||||
component.setBase64(text)
|
||||
} else {
|
||||
essentials.showToast(
|
||||
message = context.getString(R.string.not_a_valid_base_64),
|
||||
icon = Icons.Rounded.Base64
|
||||
)
|
||||
}
|
||||
},
|
||||
shape = shape,
|
||||
icon = Icons.Rounded.ContentPaste,
|
||||
textRes = R.string.paste_base_64
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedContent(component.uri == null) { isNoUri ->
|
||||
if (isNoUri) {
|
||||
Row {
|
||||
pasteTile(CircleShape)
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.container(
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
resultPadding = 8.dp
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.options),
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
pasteTile(
|
||||
RoundedCornerShape(
|
||||
topStart = 16.dp,
|
||||
topEnd = 4.dp,
|
||||
bottomStart = 4.dp,
|
||||
bottomEnd = 4.dp
|
||||
)
|
||||
)
|
||||
|
||||
Tile(
|
||||
onClick = {
|
||||
val text = buildAnnotatedString {
|
||||
append(component.base64String)
|
||||
}
|
||||
if (isBase64(component.base64String)) {
|
||||
clipboardManager.setText(text)
|
||||
} else {
|
||||
essentials.showToast(
|
||||
message = context.getString(R.string.copy_not_a_valid_base_64),
|
||||
icon = Icons.Rounded.Base64
|
||||
)
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(
|
||||
topStart = 4.dp,
|
||||
topEnd = 16.dp,
|
||||
bottomStart = 4.dp,
|
||||
bottomEnd = 4.dp
|
||||
),
|
||||
icon = Icons.Rounded.CopyAll,
|
||||
textRes = R.string.copy_base_64
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Tile(
|
||||
onClick = {
|
||||
|
||||
},
|
||||
shape = RoundedCornerShape(
|
||||
topStart = 4.dp,
|
||||
topEnd = 4.dp,
|
||||
bottomStart = 16.dp,
|
||||
bottomEnd = 4.dp
|
||||
),
|
||||
icon = Icons.Outlined.Share,
|
||||
textRes = R.string.share_base_64
|
||||
)
|
||||
|
||||
Tile(
|
||||
onClick = {
|
||||
|
||||
},
|
||||
shape = RoundedCornerShape(
|
||||
topStart = 4.dp,
|
||||
topEnd = 4.dp,
|
||||
bottomStart = 4.dp,
|
||||
bottomEnd = 16.dp
|
||||
),
|
||||
icon = Icons.Outlined.Save,
|
||||
textRes = R.string.save_base_64
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.Tile(
|
||||
onClick: () -> Unit,
|
||||
shape: Shape,
|
||||
icon: ImageVector,
|
||||
textRes: Int
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(56.dp)
|
||||
.container(
|
||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f),
|
||||
shape = shape,
|
||||
resultPadding = 0.dp
|
||||
)
|
||||
.hapticsClickable(onClick = onClick)
|
||||
.padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = 8.dp,
|
||||
alignment = Alignment.CenterHorizontally
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
AutoSizeText(
|
||||
text = stringResource(id = textRes),
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,225 @@
|
||||
/*
|
||||
* 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.base64_conversion.presentation.components
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.core.net.toUri
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
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.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.BaseComponent
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.state.update
|
||||
import ru.tech.imageresizershrinker.feature.base64_conversion.domain.Base64Converter
|
||||
import kotlin.toString
|
||||
|
||||
class Base64ConversionComponent @AssistedInject internal constructor(
|
||||
@Assisted componentContext: ComponentContext,
|
||||
@Assisted initialUri: Uri?,
|
||||
@Assisted val onGoBack: () -> Unit,
|
||||
@Assisted val onNavigate: (Screen) -> Unit,
|
||||
private val imageGetter: ImageGetter<Bitmap, ExifInterface>,
|
||||
private val shareProvider: ShareProvider<Bitmap>,
|
||||
private val fileController: FileController,
|
||||
private val converter: Base64Converter,
|
||||
private val imageCompressor: ImageCompressor<Bitmap>,
|
||||
dispatchersHolder: DispatchersHolder,
|
||||
) : BaseComponent(dispatchersHolder, componentContext) {
|
||||
|
||||
private val _imageFormat = mutableStateOf(ImageFormat.Default)
|
||||
val imageFormat by _imageFormat
|
||||
|
||||
private val _quality = mutableStateOf<Quality>(Quality.Base())
|
||||
val quality by _quality
|
||||
|
||||
private val _uri = mutableStateOf<Uri?>(null)
|
||||
val uri by _uri
|
||||
|
||||
private val _base64String = mutableStateOf("")
|
||||
val base64String by _base64String
|
||||
|
||||
private val _isSaving: MutableState<Boolean> = mutableStateOf(false)
|
||||
val isSaving by _isSaving
|
||||
|
||||
private var savingJob: Job? by smartJob {
|
||||
_isSaving.update { false }
|
||||
}
|
||||
|
||||
init {
|
||||
debounce {
|
||||
initialUri?.let(::setUri)
|
||||
}
|
||||
}
|
||||
|
||||
fun setUri(uri: Uri) {
|
||||
_uri.value = uri
|
||||
|
||||
updateBase64()
|
||||
}
|
||||
|
||||
private fun updateBase64() {
|
||||
debouncedImageCalculation {
|
||||
uri?.let { imageGetter.getImage(it) }?.let { image ->
|
||||
shareProvider.cacheImage(
|
||||
image = image,
|
||||
imageInfo = ImageInfo(
|
||||
width = image.width,
|
||||
height = image.height,
|
||||
imageFormat = imageFormat,
|
||||
quality = quality,
|
||||
originalUri = uri.toString()
|
||||
)
|
||||
)?.let {
|
||||
_base64String.value = converter.encode(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setBase64(base64: String) {
|
||||
_base64String.value = base64
|
||||
debouncedImageCalculation {
|
||||
_uri.value = converter.decode(base64)?.toUri()
|
||||
}
|
||||
}
|
||||
|
||||
fun setImageFormat(imageFormat: ImageFormat) {
|
||||
_imageFormat.update { imageFormat }
|
||||
updateBase64()
|
||||
}
|
||||
|
||||
fun setQuality(quality: Quality) {
|
||||
_quality.update { quality }
|
||||
updateBase64()
|
||||
}
|
||||
|
||||
fun getFormatForFilenameSelection(): ImageFormat = imageFormat
|
||||
|
||||
fun shareBitmap(onComplete: () -> Unit) {
|
||||
savingJob = componentScope.launch {
|
||||
_isSaving.update { true }
|
||||
uri?.let { imageGetter.getImage(it) }?.let { image ->
|
||||
shareProvider.shareImage(
|
||||
image = image,
|
||||
imageInfo = ImageInfo(
|
||||
width = image.width,
|
||||
height = image.height,
|
||||
imageFormat = imageFormat,
|
||||
quality = quality,
|
||||
originalUri = uri.toString()
|
||||
),
|
||||
onComplete = onComplete
|
||||
)
|
||||
}
|
||||
_isSaving.update { false }
|
||||
}
|
||||
}
|
||||
|
||||
fun cacheCurrentImage(onComplete: (Uri) -> Unit) {
|
||||
savingJob = componentScope.launch {
|
||||
_isSaving.update { true }
|
||||
uri?.let { imageGetter.getImage(it) }?.let { image ->
|
||||
shareProvider.cacheImage(
|
||||
image = image,
|
||||
imageInfo = ImageInfo(
|
||||
width = image.width,
|
||||
height = image.height,
|
||||
imageFormat = imageFormat,
|
||||
quality = quality,
|
||||
originalUri = uri.toString()
|
||||
)
|
||||
)?.let { uri ->
|
||||
onComplete(uri.toUri())
|
||||
}
|
||||
}
|
||||
_isSaving.update { false }
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelSaving() {
|
||||
savingJob?.cancel()
|
||||
savingJob = null
|
||||
_isSaving.update { false }
|
||||
}
|
||||
|
||||
fun saveBitmap(
|
||||
oneTimeSaveLocationUri: String?,
|
||||
onComplete: (result: SaveResult) -> Unit,
|
||||
) {
|
||||
savingJob = componentScope.launch(defaultDispatcher) {
|
||||
_isSaving.update { true }
|
||||
uri?.let { imageGetter.getImage(it) }?.let { image ->
|
||||
val imageInfo = ImageInfo(
|
||||
width = image.width,
|
||||
height = image.height,
|
||||
imageFormat = imageFormat,
|
||||
quality = quality,
|
||||
originalUri = uri.toString()
|
||||
)
|
||||
onComplete(
|
||||
fileController.save(
|
||||
saveTarget = ImageSaveTarget(
|
||||
imageInfo = imageInfo,
|
||||
metadata = null,
|
||||
originalUri = uri.toString(),
|
||||
sequenceNumber = null,
|
||||
data = imageCompressor.compressAndTransform(
|
||||
image = image,
|
||||
imageInfo = imageInfo.copy(
|
||||
originalUri = uri.toString()
|
||||
)
|
||||
)
|
||||
),
|
||||
keepOriginalMetadata = true,
|
||||
oneTimeSaveLocationUri = oneTimeSaveLocationUri
|
||||
).onSuccess(::registerSave)
|
||||
)
|
||||
}
|
||||
_isSaving.update { false }
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
fun interface Factory {
|
||||
operator fun invoke(
|
||||
componentContext: ComponentContext,
|
||||
initialUri: Uri?,
|
||||
onGoBack: () -> Unit,
|
||||
onNavigate: (Screen) -> Unit,
|
||||
): Base64ConversionComponent
|
||||
}
|
||||
|
||||
}
|
@ -85,7 +85,6 @@ import ru.tech.imageresizershrinker.core.ui.theme.toColor
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.content_pickers.Picker
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.content_pickers.rememberImagePicker
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.provider.LocalComponentActivity
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.provider.rememberLocalEssentials
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.AdaptiveBottomScaffoldLayoutScreen
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.buttons.BottomButtonsBlock
|
||||
@ -114,7 +113,7 @@ import ru.tech.imageresizershrinker.feature.markup_layers.presentation.screenLog
|
||||
fun MarkupLayersContent(
|
||||
component: MarkupLayersComponent
|
||||
) {
|
||||
LocalComponentActivity.current
|
||||
AutoContentBasedColors(component.bitmap)
|
||||
|
||||
val themeState = LocalDynamicThemeState.current
|
||||
|
||||
|
@ -64,4 +64,5 @@ dependencies {
|
||||
implementation(projects.feature.colllageMaker)
|
||||
implementation(projects.feature.librariesInfo)
|
||||
implementation(projects.feature.markupLayers)
|
||||
implementation(projects.feature.base64Conversion)
|
||||
}
|
@ -24,7 +24,6 @@ import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.height
|
||||
@ -48,6 +47,7 @@ import ru.tech.imageresizershrinker.core.settings.domain.model.FastSettingsSide
|
||||
import ru.tech.imageresizershrinker.core.settings.presentation.provider.LocalSettingsState
|
||||
import ru.tech.imageresizershrinker.core.ui.theme.blend
|
||||
import ru.tech.imageresizershrinker.core.ui.theme.takeColorFromScheme
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.haptics.hapticsClickable
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.modifier.container
|
||||
|
||||
@Composable
|
||||
@ -91,7 +91,7 @@ internal fun BoxScope.SettingsOpenButton(
|
||||
else 24.dp
|
||||
).value
|
||||
)
|
||||
.clickable(
|
||||
.hapticsClickable(
|
||||
enabled = canExpandSettings,
|
||||
indication = null,
|
||||
interactionSource = null
|
||||
|
@ -22,6 +22,7 @@ import ru.tech.imageresizershrinker.colllage_maker.presentation.screenLogic.Coll
|
||||
import ru.tech.imageresizershrinker.color_tools.presentation.screenLogic.ColorToolsComponent
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen
|
||||
import ru.tech.imageresizershrinker.feature.apng_tools.presentation.screenLogic.ApngToolsComponent
|
||||
import ru.tech.imageresizershrinker.feature.base64_conversion.presentation.components.Base64ConversionComponent
|
||||
import ru.tech.imageresizershrinker.feature.cipher.presentation.screenLogic.CipherComponent
|
||||
import ru.tech.imageresizershrinker.feature.compare.presentation.screenLogic.CompareComponent
|
||||
import ru.tech.imageresizershrinker.feature.crop.presentation.screenLogic.CropComponent
|
||||
@ -48,6 +49,7 @@ import ru.tech.imageresizershrinker.feature.pdf_tools.presentation.screenLogic.P
|
||||
import ru.tech.imageresizershrinker.feature.pick_color.presentation.screenLogic.PickColorFromImageComponent
|
||||
import ru.tech.imageresizershrinker.feature.recognize.text.presentation.screenLogic.RecognizeTextComponent
|
||||
import ru.tech.imageresizershrinker.feature.resize_convert.presentation.screenLogic.ResizeAndConvertComponent
|
||||
import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.*
|
||||
import ru.tech.imageresizershrinker.feature.root.presentation.screenLogic.RootComponent
|
||||
import ru.tech.imageresizershrinker.feature.scan_qr_code.presentation.screenLogic.ScanQrCodeComponent
|
||||
import ru.tech.imageresizershrinker.feature.settings.presentation.screenLogic.SettingsComponent
|
||||
@ -100,27 +102,28 @@ internal class ChildProvider @Inject constructor(
|
||||
private val colorToolsComponentFactory: ColorToolsComponent.Factory,
|
||||
private val librariesInfoComponentFactory: LibrariesInfoComponent.Factory,
|
||||
private val mainComponentFactory: MainComponent.Factory,
|
||||
private val markupLayersComponentFactory: MarkupLayersComponent.Factory
|
||||
private val markupLayersComponentFactory: MarkupLayersComponent.Factory,
|
||||
private val base64ConversionComponentFactory: Base64ConversionComponent.Factory
|
||||
) {
|
||||
fun RootComponent.createChild(
|
||||
config: Screen,
|
||||
componentContext: ComponentContext
|
||||
): NavigationChild = when (config) {
|
||||
Screen.ColorTools -> NavigationChild.ColorTools(
|
||||
Screen.ColorTools -> ColorTools(
|
||||
colorToolsComponentFactory(
|
||||
componentContext = componentContext,
|
||||
onGoBack = ::navigateBack
|
||||
)
|
||||
)
|
||||
|
||||
Screen.EasterEgg -> NavigationChild.EasterEgg(
|
||||
Screen.EasterEgg -> EasterEgg(
|
||||
easterEggComponentFactory(
|
||||
componentContext = componentContext,
|
||||
onGoBack = ::navigateBack
|
||||
)
|
||||
)
|
||||
|
||||
Screen.Main -> NavigationChild.Main(
|
||||
Screen.Main -> Main(
|
||||
mainComponentFactory(
|
||||
componentContext = componentContext,
|
||||
onTryGetUpdate = ::tryGetUpdate,
|
||||
@ -130,7 +133,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.ApngTools -> NavigationChild.ApngTools(
|
||||
is Screen.ApngTools -> ApngTools(
|
||||
apngToolsComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialType = config.type,
|
||||
@ -139,7 +142,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.Cipher -> NavigationChild.Cipher(
|
||||
is Screen.Cipher -> Cipher(
|
||||
cipherComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUri = config.uri,
|
||||
@ -147,7 +150,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.CollageMaker -> NavigationChild.CollageMaker(
|
||||
is Screen.CollageMaker -> CollageMaker(
|
||||
collageMakerComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -156,7 +159,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.Compare -> NavigationChild.Compare(
|
||||
is Screen.Compare -> Compare(
|
||||
compareComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialComparableUris = config.uris
|
||||
@ -166,7 +169,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.Crop -> NavigationChild.Crop(
|
||||
is Screen.Crop -> Crop(
|
||||
cropComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUri = config.uri,
|
||||
@ -175,7 +178,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.DeleteExif -> NavigationChild.DeleteExif(
|
||||
is Screen.DeleteExif -> DeleteExif(
|
||||
deleteExifComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -184,7 +187,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
Screen.DocumentScanner -> NavigationChild.DocumentScanner(
|
||||
Screen.DocumentScanner -> DocumentScanner(
|
||||
documentScannerComponentFactory(
|
||||
componentContext = componentContext,
|
||||
onGoBack = ::navigateBack,
|
||||
@ -192,7 +195,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.Draw -> NavigationChild.Draw(
|
||||
is Screen.Draw -> Draw(
|
||||
drawComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUri = config.uri,
|
||||
@ -201,7 +204,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.EraseBackground -> NavigationChild.EraseBackground(
|
||||
is Screen.EraseBackground -> EraseBackground(
|
||||
eraseBackgroundComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUri = config.uri,
|
||||
@ -210,7 +213,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.Filter -> NavigationChild.Filter(
|
||||
is Screen.Filter -> Filter(
|
||||
filtersComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialType = config.type,
|
||||
@ -219,7 +222,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.FormatConversion -> NavigationChild.FormatConversion(
|
||||
is Screen.FormatConversion -> FormatConversion(
|
||||
formatConversionComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -228,7 +231,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.GeneratePalette -> NavigationChild.GeneratePalette(
|
||||
is Screen.GeneratePalette -> GeneratePalette(
|
||||
generatePaletteComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUri = config.uri,
|
||||
@ -236,7 +239,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.GifTools -> NavigationChild.GifTools(
|
||||
is Screen.GifTools -> GifTools(
|
||||
gifToolsComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialType = config.type,
|
||||
@ -244,7 +247,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.GradientMaker -> NavigationChild.GradientMaker(
|
||||
is Screen.GradientMaker -> GradientMaker(
|
||||
gradientMakerComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -253,7 +256,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.ImagePreview -> NavigationChild.ImagePreview(
|
||||
is Screen.ImagePreview -> ImagePreview(
|
||||
imagePreviewComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -262,7 +265,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.ImageSplitting -> NavigationChild.ImageSplitting(
|
||||
is Screen.ImageSplitting -> ImageSplitting(
|
||||
imageSplittingComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uri,
|
||||
@ -271,7 +274,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.ImageStacking -> NavigationChild.ImageStacking(
|
||||
is Screen.ImageStacking -> ImageStacking(
|
||||
imageStackingComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -280,7 +283,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.ImageStitching -> NavigationChild.ImageStitching(
|
||||
is Screen.ImageStitching -> ImageStitching(
|
||||
imageStitchingComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -289,7 +292,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.JxlTools -> NavigationChild.JxlTools(
|
||||
is Screen.JxlTools -> JxlTools(
|
||||
jxlToolsComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialType = config.type,
|
||||
@ -297,7 +300,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.LimitResize -> NavigationChild.LimitResize(
|
||||
is Screen.LimitResize -> LimitResize(
|
||||
limitResizeComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -306,7 +309,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.LoadNetImage -> NavigationChild.LoadNetImage(
|
||||
is Screen.LoadNetImage -> LoadNetImage(
|
||||
loadNetImageComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUrl = config.url,
|
||||
@ -316,7 +319,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
|
||||
|
||||
Screen.NoiseGeneration -> NavigationChild.NoiseGeneration(
|
||||
Screen.NoiseGeneration -> NoiseGeneration(
|
||||
noiseGenerationComponentFactory(
|
||||
componentContext = componentContext,
|
||||
onGoBack = ::navigateBack,
|
||||
@ -324,7 +327,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.PdfTools -> NavigationChild.PdfTools(
|
||||
is Screen.PdfTools -> PdfTools(
|
||||
pdfToolsComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialType = config.type,
|
||||
@ -332,7 +335,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.PickColorFromImage -> NavigationChild.PickColorFromImage(
|
||||
is Screen.PickColorFromImage -> PickColorFromImage(
|
||||
pickColorFromImageComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUri = config.uri,
|
||||
@ -340,7 +343,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.RecognizeText -> NavigationChild.RecognizeText(
|
||||
is Screen.RecognizeText -> RecognizeText(
|
||||
recognizeTextComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUri = config.uri,
|
||||
@ -348,7 +351,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.ResizeAndConvert -> NavigationChild.ResizeAndConvert(
|
||||
is Screen.ResizeAndConvert -> ResizeAndConvert(
|
||||
resizeAndConvertComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -357,7 +360,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.ScanQrCode -> NavigationChild.ScanQrCode(
|
||||
is Screen.ScanQrCode -> ScanQrCode(
|
||||
scanQrCodeComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialQrCodeContent = config.qrCodeContent,
|
||||
@ -365,7 +368,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
Screen.Settings -> NavigationChild.Settings(
|
||||
Screen.Settings -> Settings(
|
||||
settingsComponentFactory(
|
||||
componentContext = componentContext,
|
||||
onTryGetUpdate = ::tryGetUpdate,
|
||||
@ -375,7 +378,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.SingleEdit -> NavigationChild.SingleEdit(
|
||||
is Screen.SingleEdit -> SingleEdit(
|
||||
singleEditComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUri = config.uri,
|
||||
@ -384,7 +387,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.SvgMaker -> NavigationChild.SvgMaker(
|
||||
is Screen.SvgMaker -> SvgMaker(
|
||||
svgMakerComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -392,7 +395,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.Watermarking -> NavigationChild.Watermarking(
|
||||
is Screen.Watermarking -> Watermarking(
|
||||
watermarkingComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -401,7 +404,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.WebpTools -> NavigationChild.WebpTools(
|
||||
is Screen.WebpTools -> WebpTools(
|
||||
webpToolsComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialType = config.type,
|
||||
@ -410,7 +413,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.WeightResize -> NavigationChild.WeightResize(
|
||||
is Screen.WeightResize -> WeightResize(
|
||||
weightResizeComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -419,7 +422,7 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.Zip -> NavigationChild.Zip(
|
||||
is Screen.Zip -> Zip(
|
||||
zipComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUris = config.uris,
|
||||
@ -427,14 +430,14 @@ internal class ChildProvider @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
Screen.LibrariesInfo -> NavigationChild.LibrariesInfo(
|
||||
Screen.LibrariesInfo -> LibrariesInfo(
|
||||
librariesInfoComponentFactory(
|
||||
componentContext = componentContext,
|
||||
onGoBack = ::navigateBack
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.MarkupLayers -> NavigationChild.MarkupLayers(
|
||||
is Screen.MarkupLayers -> MarkupLayers(
|
||||
markupLayersComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUri = config.uri,
|
||||
@ -442,5 +445,14 @@ internal class ChildProvider @Inject constructor(
|
||||
onNavigate = ::navigateTo
|
||||
)
|
||||
)
|
||||
|
||||
is Screen.Base64Conversion -> Base64Conversion(
|
||||
base64ConversionComponentFactory(
|
||||
componentContext = componentContext,
|
||||
initialUri = config.uri,
|
||||
onGoBack = ::navigateBack,
|
||||
onNavigate = ::navigateTo
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -24,6 +24,8 @@ import ru.tech.imageresizershrinker.color_tools.presentation.ColorToolsContent
|
||||
import ru.tech.imageresizershrinker.color_tools.presentation.screenLogic.ColorToolsComponent
|
||||
import ru.tech.imageresizershrinker.feature.apng_tools.presentation.ApngToolsContent
|
||||
import ru.tech.imageresizershrinker.feature.apng_tools.presentation.screenLogic.ApngToolsComponent
|
||||
import ru.tech.imageresizershrinker.feature.base64_conversion.presentation.Base64ConversionContent
|
||||
import ru.tech.imageresizershrinker.feature.base64_conversion.presentation.components.Base64ConversionComponent
|
||||
import ru.tech.imageresizershrinker.feature.cipher.presentation.CipherContent
|
||||
import ru.tech.imageresizershrinker.feature.cipher.presentation.screenLogic.CipherComponent
|
||||
import ru.tech.imageresizershrinker.feature.compare.presentation.CompareContent
|
||||
@ -299,4 +301,9 @@ internal sealed class NavigationChild {
|
||||
override fun Content() = MarkupLayersContent(component)
|
||||
}
|
||||
|
||||
class Base64Conversion(val component: Base64ConversionComponent) : NavigationChild() {
|
||||
@Composable
|
||||
override fun Content() = Base64ConversionContent(component)
|
||||
}
|
||||
|
||||
}
|
@ -34,7 +34,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.t8rin.logger.makeLog
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.tech.imageresizershrinker.core.resources.R
|
||||
import ru.tech.imageresizershrinker.core.settings.presentation.provider.LocalSettingsState
|
||||
@ -63,7 +62,7 @@ fun SettingGroupItem(
|
||||
val settingsState = LocalSettingsState.current
|
||||
|
||||
val initialState =
|
||||
settingsState.settingGroupsInitialVisibility[groupKey].makeLog("COCK") ?: initialState
|
||||
settingsState.settingGroupsInitialVisibility[groupKey] ?: initialState
|
||||
|
||||
val simpleSettingsInteractor = LocalSimpleSettingsInteractor.current
|
||||
val interactionSource = remember {
|
||||
|
@ -17,12 +17,6 @@
|
||||
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
include(":feature:markup-layers")
|
||||
|
||||
|
||||
include(":feature:libraries-info")
|
||||
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
includeBuild("build-logic")
|
||||
@ -108,6 +102,9 @@ include(":feature:color-tools")
|
||||
include(":feature:webp-tools")
|
||||
include(":feature:noise-generation")
|
||||
include(":feature:colllage-maker")
|
||||
include(":feature:libraries-info")
|
||||
include(":feature:markup-layers")
|
||||
include(":feature:base64-conversion")
|
||||
|
||||
include(":feature:root")
|
||||
|
||||
|
Reference in New Issue
Block a user