diff --git a/core/resources/src/main/res/values/strings.xml b/core/resources/src/main/res/values/strings.xml index adad2367b..3caa5026c 100644 --- a/core/resources/src/main/res/values/strings.xml +++ b/core/resources/src/main/res/values/strings.xml @@ -1600,4 +1600,5 @@ Enforce B/W Barcode Image will be fully black and white and not colored by app\'s theme Scan any Barcode (QR, EAN, AZTEC, …) and get it\'s content or paste your text to generate new one + No Barcode Found \ No newline at end of file diff --git a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt index 6ba9ab7df..67524ecaf 100644 --- a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt +++ b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/navigation/Screen.kt @@ -578,7 +578,8 @@ sealed class Screen( @Serializable data class ScanQrCode( - val qrCodeContent: String? = null + val qrCodeContent: String? = null, + val uriToAnalyze: Uri? = null ) : Screen( id = 27, title = R.string.qr_code, diff --git a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/buttons/BottomButtonsBlock.kt b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/buttons/BottomButtonsBlock.kt index c5d66efaf..a6b68f9da 100644 --- a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/buttons/BottomButtonsBlock.kt +++ b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/buttons/BottomButtonsBlock.kt @@ -87,7 +87,8 @@ fun BottomButtonsBlock( showNullDataButtonAsContainer: Boolean = false, columnarFab: (@Composable ColumnScope.() -> Unit)? = null, actions: @Composable RowScope.() -> Unit, - isPrimaryButtonEnabled: Boolean = true + isPrimaryButtonEnabled: Boolean = true, + showColumnarFabInRow: Boolean = false, ) { AnimatedContent( targetState = targetState, @@ -97,9 +98,7 @@ fun BottomButtonsBlock( ) { (isNull, inside) -> if (isNull) { val button = @Composable { - EnhancedFloatingActionButton( - onClick = onSecondaryButtonClick, - onLongClick = onSecondaryButtonLongClick, + Row( modifier = Modifier .windowInsetsPadding( WindowInsets.navigationBars.union( @@ -109,14 +108,24 @@ fun BottomButtonsBlock( ) ) .padding(16.dp), - content = { - Spacer(Modifier.width(16.dp)) - Icon(secondaryButtonIcon, null) - Spacer(Modifier.width(16.dp)) - Text(secondaryButtonText) - Spacer(Modifier.width(16.dp)) + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + EnhancedFloatingActionButton( + onClick = onSecondaryButtonClick, + onLongClick = onSecondaryButtonLongClick, + content = { + Spacer(Modifier.width(16.dp)) + Icon(secondaryButtonIcon, null) + Spacer(Modifier.width(16.dp)) + Text(secondaryButtonText) + Spacer(Modifier.width(16.dp)) + } + ) + if (showColumnarFabInRow && columnarFab != null) { + Column { columnarFab() } } - ) + } } if (showNullDataButtonAsContainer) { Row( @@ -136,7 +145,9 @@ fun BottomButtonsBlock( modifier = Modifier.drawHorizontalStroke(true), actions = actions, floatingActionButton = { - Row { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { AnimatedVisibility(visible = isSecondaryButtonVisible) { EnhancedFloatingActionButton( onClick = onSecondaryButtonClick, @@ -152,43 +163,45 @@ fun BottomButtonsBlock( ) } } + AnimatedVisibility(visible = showColumnarFabInRow) { + columnarFab?.let { + Column { it() } + } + } AnimatedVisibility(visible = isPrimaryButtonVisible) { - Row { - Spacer(Modifier.width(8.dp)) - EnhancedFloatingActionButton( - onClick = if (isPrimaryButtonEnabled) onPrimaryButtonClick - else null, - onLongClick = if (isPrimaryButtonEnabled) onPrimaryButtonLongClick - else null, - containerColor = takeColorFromScheme { - if (isPrimaryButtonEnabled) primaryContainer - else surfaceContainerHighest - }, - contentColor = takeColorFromScheme { - if (isPrimaryButtonEnabled) onPrimaryContainer - else outline - } - ) { - AnimatedContent( - targetState = primaryButtonIcon to primaryButtonText, - transitionSpec = { fadeIn() + scaleIn() togetherWith fadeOut() + scaleOut() } - ) { (icon, text) -> - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - if (text.isNotEmpty()) { - Spacer(Modifier.width(16.dp)) - } - Icon( - imageVector = icon, - contentDescription = null - ) - if (text.isNotEmpty()) { - Spacer(Modifier.width(16.dp)) - Text(text) - Spacer(Modifier.width(16.dp)) - } + EnhancedFloatingActionButton( + onClick = if (isPrimaryButtonEnabled) onPrimaryButtonClick + else null, + onLongClick = if (isPrimaryButtonEnabled) onPrimaryButtonLongClick + else null, + containerColor = takeColorFromScheme { + if (isPrimaryButtonEnabled) primaryContainer + else surfaceContainerHighest + }, + contentColor = takeColorFromScheme { + if (isPrimaryButtonEnabled) onPrimaryContainer + else outline + } + ) { + AnimatedContent( + targetState = primaryButtonIcon to primaryButtonText, + transitionSpec = { fadeIn() + scaleIn() togetherWith fadeOut() + scaleOut() } + ) { (icon, text) -> + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + if (text.isNotEmpty()) { + Spacer(Modifier.width(16.dp)) + } + Icon( + imageVector = icon, + contentDescription = null + ) + if (text.isNotEmpty()) { + Spacer(Modifier.width(16.dp)) + Text(text) + Spacer(Modifier.width(16.dp)) } } } @@ -223,7 +236,10 @@ fun BottomButtonsBlock( EnhancedFloatingActionButton( onClick = onSecondaryButtonClick, onLongClick = onSecondaryButtonLongClick, - containerColor = MaterialTheme.colorScheme.tertiaryContainer + containerColor = takeColorFromScheme { + if (isPrimaryButtonVisible) tertiaryContainer + else primaryContainer + } ) { Icon( imageVector = secondaryButtonIcon, diff --git a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/utils/ScreenList.kt b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/utils/ScreenList.kt index 2e66d45a7..549ff35ea 100644 --- a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/utils/ScreenList.kt +++ b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/widget/utils/ScreenList.kt @@ -121,6 +121,7 @@ internal fun List.screenList( Screen.ImageStacking(uris), Screen.ImageSplitting(uris.firstOrNull()), Screen.ImageCutter(uris), + Screen.ScanQrCode(uriToAnalyze = uris.firstOrNull()), Screen.GradientMaker(uris), Screen.PdfTools( Screen.PdfTools.Type.ImagesToPdf(uris) diff --git a/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/ChildProvider.kt b/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/ChildProvider.kt index 5212001e1..8a7e7b5ab 100644 --- a/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/ChildProvider.kt +++ b/feature/root/src/main/java/ru/tech/imageresizershrinker/feature/root/presentation/components/navigation/ChildProvider.kt @@ -52,7 +52,50 @@ 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.components.navigation.NavigationChild.ApngTools +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Base64Tools +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.ChecksumTools +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Cipher +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.CollageMaker +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.ColorTools +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Compare +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Crop +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.DeleteExif +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.DocumentScanner +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Draw +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.EasterEgg +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.EditExif +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.EraseBackground +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Filter +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.FormatConversion +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.GeneratePalette +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.GifTools +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.GradientMaker +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.ImageCutter +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.ImagePreview +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.ImageSplitting +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.ImageStacking +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.ImageStitching +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.JxlTools +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.LibrariesInfo +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.LimitResize +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.LoadNetImage +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Main +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.MarkupLayers +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.MeshGradients +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.NoiseGeneration +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.PdfTools +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.PickColorFromImage +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.RecognizeText +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.ResizeAndConvert +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.ScanQrCode +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Settings +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.SingleEdit +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.SvgMaker +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Watermarking +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.WebpTools +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.WeightResize +import ru.tech.imageresizershrinker.feature.root.presentation.components.navigation.NavigationChild.Zip 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 @@ -372,6 +415,7 @@ internal class ChildProvider @Inject constructor( scanQrCodeComponentFactory( componentContext = componentContext, initialQrCodeContent = config.qrCodeContent, + uriToAnalyze = config.uriToAnalyze, onGoBack = ::navigateBack ) ) diff --git a/feature/scan-qr-code/build.gradle.kts b/feature/scan-qr-code/build.gradle.kts index 30a570106..03fe4e533 100644 --- a/feature/scan-qr-code/build.gradle.kts +++ b/feature/scan-qr-code/build.gradle.kts @@ -26,4 +26,6 @@ android.namespace = "ru.tech.imageresizershrinker.feature.scan_qr_code" dependencies { implementation(projects.core.filters) + "marketImplementation"(libs.quickie.bundled) + "fossImplementation"(libs.quickie.foss) } \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/data/AndroidImageBarcodeReader.kt b/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/data/AndroidImageBarcodeReader.kt new file mode 100644 index 000000000..8fb41a971 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/data/AndroidImageBarcodeReader.kt @@ -0,0 +1,64 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.feature.scan_qr_code.data + +import android.graphics.Bitmap +import androidx.exifinterface.media.ExifInterface +import io.github.g00fy2.quickie.extensions.readQrCode +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext +import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder +import ru.tech.imageresizershrinker.core.domain.image.ImageGetter +import ru.tech.imageresizershrinker.core.domain.resource.ResourceManager +import ru.tech.imageresizershrinker.core.resources.R +import ru.tech.imageresizershrinker.feature.scan_qr_code.domain.ImageBarcodeReader +import javax.inject.Inject +import kotlin.coroutines.resume + +internal class AndroidImageBarcodeReader @Inject constructor( + private val imageGetter: ImageGetter, + resourceManager: ResourceManager, + dispatchersHolder: DispatchersHolder +) : ImageBarcodeReader, DispatchersHolder by dispatchersHolder, ResourceManager by resourceManager { + + override suspend fun readBarcode( + image: Any + ): Result = withContext(defaultDispatcher) { + val bitmap = imageGetter.getImage( + data = image, + originalSize = false + ) + + if (bitmap == null) { + return@withContext Result.failure(NullPointerException(getString(R.string.something_went_wrong))) + } + + suspendCancellableCoroutine { continuation -> + bitmap.readQrCode( + barcodeFormats = IntArray(0), + onSuccess = { + continuation.resume(Result.success(it)) + }, + onFailure = { + continuation.resume(Result.failure(it)) + } + ) + } + } + +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/di/ScanQrCodeModule.kt b/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/di/ScanQrCodeModule.kt new file mode 100644 index 000000000..a56d4add3 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/di/ScanQrCodeModule.kt @@ -0,0 +1,40 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.feature.scan_qr_code.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import ru.tech.imageresizershrinker.feature.scan_qr_code.data.AndroidImageBarcodeReader +import ru.tech.imageresizershrinker.feature.scan_qr_code.domain.ImageBarcodeReader +import javax.inject.Singleton + + +@Module +@InstallIn(SingletonComponent::class) +internal interface ScanQrCodeModule { + + @Binds + @Singleton + fun reader( + impl: AndroidImageBarcodeReader + ): ImageBarcodeReader + + +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/domain/ImageBarcodeReader.kt b/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/domain/ImageBarcodeReader.kt new file mode 100644 index 000000000..bc9dd5e82 --- /dev/null +++ b/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/domain/ImageBarcodeReader.kt @@ -0,0 +1,26 @@ +/* + * ImageToolbox is an image editor for android + * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License + * along with this program. If not, see . + */ + +package ru.tech.imageresizershrinker.feature.scan_qr_code.domain + +interface ImageBarcodeReader { + + suspend fun readBarcode( + image: Any + ): Result + +} \ No newline at end of file diff --git a/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/presentation/ScanQrCodeContent.kt b/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/presentation/ScanQrCodeContent.kt index 2cbe74836..d00998831 100644 --- a/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/presentation/ScanQrCodeContent.kt +++ b/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/presentation/ScanQrCodeContent.kt @@ -19,6 +19,7 @@ package ru.tech.imageresizershrinker.feature.scan_qr_code.presentation import android.annotation.SuppressLint import android.graphics.Bitmap +import android.net.Uri import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height @@ -26,7 +27,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AutoFixHigh import androidx.compose.material.icons.outlined.QrCodeScanner +import androidx.compose.material.icons.rounded.ImageSearch import androidx.compose.material3.Badge +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -43,6 +46,9 @@ import androidx.compose.ui.unit.dp import dev.shreyaspatil.capturable.controller.rememberCaptureController import kotlinx.coroutines.launch import ru.tech.imageresizershrinker.core.resources.R +import ru.tech.imageresizershrinker.core.ui.theme.takeColorFromScheme +import ru.tech.imageresizershrinker.core.ui.utils.content_pickers.Picker +import ru.tech.imageresizershrinker.core.ui.utils.content_pickers.rememberImagePicker import ru.tech.imageresizershrinker.core.ui.utils.helper.asClip import ru.tech.imageresizershrinker.core.ui.utils.helper.isLandscapeOrientationAsState import ru.tech.imageresizershrinker.core.ui.utils.helper.rememberBarcodeScanner @@ -52,7 +58,9 @@ 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.dialogs.LoadingDialog +import ru.tech.imageresizershrinker.core.ui.widget.dialogs.OneTimeImagePickingDialog import ru.tech.imageresizershrinker.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog +import ru.tech.imageresizershrinker.core.ui.widget.enhanced.EnhancedFloatingActionButton import ru.tech.imageresizershrinker.core.ui.widget.modifier.scaleOnTap import ru.tech.imageresizershrinker.core.ui.widget.other.BarcodeType import ru.tech.imageresizershrinker.core.ui.widget.other.TopAppBarEmoji @@ -82,6 +90,17 @@ fun ScanQrCodeContent( ) } + val analyzerImagePicker = rememberImagePicker { uri: Uri -> + component.readBarcodeFromImage( + imageUri = uri, + onFailure = { + essentials.showFailureToast( + Throwable(context.getString(R.string.no_barcode_found), it) + ) + } + ) + } + LaunchedEffect(params.content) { component.processFilterTemplateFromQrContent( onSuccess = { filterName, filtersCount -> @@ -192,8 +211,11 @@ fun ScanQrCodeContent( var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } + var showOneTimeImagePickingDialog by rememberSaveable { + mutableStateOf(false) + } BottomButtonsBlock( - targetState = (params.content.isEmpty()) to !isLandscape, + targetState = (params.content.isEmpty() && !isLandscape) to !isLandscape, secondaryButtonIcon = Icons.Outlined.QrCodeScanner, secondaryButtonText = stringResource(R.string.start_scanning), onSecondaryButtonClick = scanner::scan, @@ -208,6 +230,25 @@ fun ScanQrCodeContent( }, actions = { if (!isLandscape) actions() + }, + showColumnarFabInRow = true, + isPrimaryButtonVisible = !isLandscape || params.content.isNotEmpty(), + columnarFab = { + EnhancedFloatingActionButton( + onClick = analyzerImagePicker::pickImage, + onLongClick = { + showOneTimeImagePickingDialog = true + }, + containerColor = takeColorFromScheme { + if (params.content.isEmpty()) tertiaryContainer + else secondaryContainer + } + ) { + Icon( + imageVector = Icons.Rounded.ImageSearch, + contentDescription = null + ) + } } ) OneTimeSaveLocationSelectionDialog( @@ -221,6 +262,12 @@ fun ScanQrCodeContent( }, formatForFilenameSelection = component.getFormatForFilenameSelection() ) + OneTimeImagePickingDialog( + onDismiss = { showOneTimeImagePickingDialog = false }, + picker = Picker.Single, + imagePicker = analyzerImagePicker, + visible = showOneTimeImagePickingDialog + ) }, canShowScreenData = true, isPortrait = !isLandscape diff --git a/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/presentation/screenLogic/ScanQrCodeComponent.kt b/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/presentation/screenLogic/ScanQrCodeComponent.kt index a4d560a75..d9a10f0d3 100644 --- a/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/presentation/screenLogic/ScanQrCodeComponent.kt +++ b/feature/scan-qr-code/src/main/java/ru/tech/imageresizershrinker/feature/scan_qr_code/presentation/screenLogic/ScanQrCodeComponent.kt @@ -48,16 +48,19 @@ import ru.tech.imageresizershrinker.core.settings.domain.model.SettingsState import ru.tech.imageresizershrinker.core.settings.presentation.model.toUiFont import ru.tech.imageresizershrinker.core.ui.utils.BaseComponent import ru.tech.imageresizershrinker.core.ui.utils.state.update +import ru.tech.imageresizershrinker.feature.scan_qr_code.domain.ImageBarcodeReader import ru.tech.imageresizershrinker.feature.scan_qr_code.presentation.components.QrPreviewParams class ScanQrCodeComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted initialQrCodeContent: String?, + @Assisted uriToAnalyze: Uri?, @Assisted val onGoBack: () -> Unit, private val fileController: FileController, private val shareProvider: ShareProvider, private val imageCompressor: ImageCompressor, private val favoriteFiltersInteractor: FavoriteFiltersInteractor, + private val imageBarcodeReader: ImageBarcodeReader, settingsProvider: SettingsProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { @@ -79,14 +82,16 @@ class ScanQrCodeComponent @AssistedInject internal constructor( private var settingsState: SettingsState = SettingsState.Default init { - settingsProvider.getSettingsStateFlow().onEach { - settingsState = it + settingsProvider.getSettingsStateFlow().onEach { state -> + settingsState = state _params.update { it.copy( descriptionFont = settingsState.font.toUiFont() ) } }.launchIn(componentScope) + + uriToAnalyze?.let(::readBarcodeFromImage) } fun saveBitmap( @@ -194,11 +199,30 @@ class ScanQrCodeComponent @AssistedInject internal constructor( _params.update { params } } + fun readBarcodeFromImage( + imageUri: Uri, + onFailure: (Throwable) -> Unit = {} + ) { + componentScope.launch { + imageBarcodeReader + .readBarcode(imageUri) + .onSuccess { + updateParams( + params.copy( + content = it + ) + ) + } + .onFailure(onFailure) + } + } + @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialQrCodeContent: String?, + uriToAnalyze: Uri?, onGoBack: () -> Unit, ): ScanQrCodeComponent }