Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.ninecraft.booket.core.common.utils.MultipleEventsCutter
import com.ninecraft.booket.core.common.utils.get
Expand Down Expand Up @@ -93,6 +94,8 @@ fun ReedButton(

Text(
text = text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = sizeStyle.textStyle.copy(
color = if (enabled) colorStyle.contentColor() else colorStyle.disabledContentColor(),
),
Expand Down
9 changes: 9 additions & 0 deletions core/designsystem/src/main/res/drawable/ic_gallery.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19,2.25C19.729,2.25 20.429,2.54 20.944,3.056C21.46,3.571 21.75,4.271 21.75,5V19C21.75,19.729 21.46,20.429 20.944,20.944C20.429,21.46 19.729,21.75 19,21.75H5C4.271,21.75 3.571,21.46 3.056,20.944C2.54,20.429 2.25,19.729 2.25,19V5C2.25,4.271 2.54,3.571 3.056,3.056C3.571,2.54 4.271,2.25 5,2.25H19ZM8.395,13.096C8.302,13.022 8.17,13.023 8.079,13.099L3.75,16.706V19C3.75,19.331 3.882,19.649 4.116,19.884C4.351,20.118 4.668,20.25 5,20.25H19C19.331,20.25 19.649,20.118 19.884,19.884C20.118,19.649 20.25,19.331 20.25,19V18.713L16.799,15.492C16.705,15.405 16.559,15.403 16.463,15.488L15.161,16.646C14.524,17.212 13.571,17.237 12.905,16.704L8.395,13.096ZM5,3.75C4.668,3.75 4.351,3.882 4.116,4.116C3.882,4.351 3.75,4.668 3.75,5V14.753L7.118,11.946C7.757,11.414 8.683,11.405 9.332,11.925L13.842,15.532C13.937,15.608 14.073,15.605 14.164,15.524L15.466,14.367C16.141,13.767 17.163,13.779 17.823,14.396L20.25,16.66V5C20.25,4.668 20.118,4.351 19.884,4.116C19.649,3.882 19.331,3.75 19,3.75H5ZM16,7C17.105,7 18,7.895 18,9C18,10.105 17.105,11 16,11C14.895,11 14,10.105 14,9C14,7.895 14.895,7 16,7Z"
android:fillColor="#ffffff"/>
</vector>
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.ninecraft.booket.core.ocr.recognizer

import android.content.Context
import android.net.Uri
import android.util.Base64
import com.ninecraft.booket.core.common.utils.runSuspendCatching
import com.ninecraft.booket.core.di.ApplicationContext
import com.ninecraft.booket.core.ocr.BuildConfig
import com.ninecraft.booket.core.ocr.model.AnnotateImageRequest
import com.ninecraft.booket.core.ocr.model.CloudVisionRequest
Expand All @@ -21,13 +23,22 @@ import com.ninecraft.booket.core.di.DataScope
@SingleIn(DataScope::class)
@Inject
class CloudOcrRecognizer(
@ApplicationContext private val context: Context,
private val service: CloudVisionService,
) {
suspend fun recognizeText(imageUri: Uri): Result<CloudVisionResponse> = runSuspendCatching {
withContext(Dispatchers.IO) {
val filePath = imageUri.path ?: throw IllegalArgumentException("URI does not have a valid path.")
val file = File(filePath)
val byte = file.readBytes()
val byte = when (imageUri.scheme) {
null, "file" -> {
val filePath = imageUri.path ?: throw IllegalArgumentException("URI does not have a valid path.")
val file = File(filePath)
file.readBytes()
}
else -> {
context.contentResolver.openInputStream(imageUri)?.use { it.readBytes() }
?: throw IllegalArgumentException("Unable to open image input stream.")
}
}
Comment on lines +31 to +41
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

대용량 이미지 방어 로직이 필요합니다.

갤러리 이미지 도입으로 매우 큰 파일이 들어올 수 있어 readBytes()가 OOM 또는 API 제한 초과를 유발할 수 있습니다. 파일/콘텐츠 길이 사전 체크(또는 제한 스트리밍)로 상한을 두는 처리가 필요합니다.

🛠️ 제안 수정안 (사전 용량 체크 예시)
+import android.content.res.AssetFileDescriptor
+
+private companion object {
+    private const val MAX_IMAGE_BYTES = 4 * 1024 * 1024 // 예: 4MB
+}
...
-            val byte = when (imageUri.scheme) {
+            val byte = when (imageUri.scheme) {
                 null, "file" -> {
                     val filePath = imageUri.path ?: throw IllegalArgumentException("URI does not have a valid path.")
                     val file = File(filePath)
+                    if (file.length() > MAX_IMAGE_BYTES) {
+                        throw IllegalArgumentException("Image is too large.")
+                    }
                     file.readBytes()
                 }
                 else -> {
+                    context.contentResolver.openAssetFileDescriptor(imageUri, "r")?.use { afd ->
+                        if (afd.length != AssetFileDescriptor.UNKNOWN_LENGTH && afd.length > MAX_IMAGE_BYTES) {
+                            throw IllegalArgumentException("Image is too large.")
+                        }
+                    } ?: throw IllegalArgumentException("Unable to open image descriptor.")
                     context.contentResolver.openInputStream(imageUri)?.use { it.readBytes() }
                         ?: throw IllegalArgumentException("Unable to open image input stream.")
                 }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val byte = when (imageUri.scheme) {
null, "file" -> {
val filePath = imageUri.path ?: throw IllegalArgumentException("URI does not have a valid path.")
val file = File(filePath)
file.readBytes()
}
else -> {
context.contentResolver.openInputStream(imageUri)?.use { it.readBytes() }
?: throw IllegalArgumentException("Unable to open image input stream.")
}
}
val byte = when (imageUri.scheme) {
null, "file" -> {
val filePath = imageUri.path ?: throw IllegalArgumentException("URI does not have a valid path.")
val file = File(filePath)
if (file.length() > MAX_IMAGE_BYTES) {
throw IllegalArgumentException("Image is too large.")
}
file.readBytes()
}
else -> {
context.contentResolver.openAssetFileDescriptor(imageUri, "r")?.use { afd ->
if (afd.length != AssetFileDescriptor.UNKNOWN_LENGTH && afd.length > MAX_IMAGE_BYTES) {
throw IllegalArgumentException("Image is too large.")
}
} ?: throw IllegalArgumentException("Unable to open image descriptor.")
context.contentResolver.openInputStream(imageUri)?.use { it.readBytes() }
?: throw IllegalArgumentException("Unable to open image input stream.")
}
}
🤖 Prompt for AI Agents
In
`@core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/recognizer/CloudOcrRecognizer.kt`
around lines 31 - 41, 현재 CloudOcrRecognizer의 이미지 로드(변수 byte 생성)에서 readBytes()를
바로 호출해 대용량 이미지로 인해 OOM/제한 초과가 발생할 수 있으므로, 최대 허용 바이트(MAX_IMAGE_BYTES) 상수를 도입해
파일/콘텐트 길이를 사전 확인하거나 제한 스트리밍으로 읽도록 변경하세요: 파일 스킴일 때는 File.length()로 크기를 확인하고 초과 시
IllegalArgumentException을 던지며, content 스킴일 때는
ContentResolver.query(Images.Media.SIZE) 또는 openAssetFileDescriptor?.length를 시도해
크기를 확인하되 길이를 얻을 수 없으면 InputStream을 읽을 때는 고정 버퍼로 누적 읽기하며 누적 크기가 MAX_IMAGE_BYTES를
넘으면 즉시 중단해 에러를 던지도록 구현(관련 식별자: CloudOcrRecognizer, imageUri,
context.contentResolver, readBytes()).

val base64Image = Base64.encodeToString(byte, Base64.NO_WRAP)

val request = CloudVisionRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,17 @@ fun ReedDialog(
description: String? = null,
dismissButtonText: String? = null,
onDismissRequest: () -> Unit = {},
dismissOnClickOutside: Boolean = true,
dismissOnBackPress: Boolean = true,
headerContent: @Composable (() -> Unit)? = null,
) {
Dialog(
onDismissRequest = {
onDismissRequest()
},
properties = DialogProperties(
dismissOnClickOutside = dismissOnClickOutside,
dismissOnBackPress = dismissOnBackPress,
usePlatformDefaultWidth = false,
),
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal fun HandleOcrSideEffects(
RememberedEffect(state.sideEffect) {
when (state.sideEffect) {
is OcrSideEffect.ShowToast -> {
Toast.makeText(context, state.sideEffect.message, Toast.LENGTH_SHORT).show()
Toast.makeText(context, state.sideEffect.message.asString(context), Toast.LENGTH_SHORT).show()
}

null -> {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ package com.ninecraft.booket.feature.record.ocr

import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.core.net.toUri
import com.ninecraft.booket.core.common.analytics.AnalyticsHelper
import com.ninecraft.booket.core.common.utils.UiText
import com.ninecraft.booket.core.common.utils.handleException
import com.ninecraft.booket.core.ocr.recognizer.CloudOcrRecognizer
import com.ninecraft.booket.feature.record.R
import com.ninecraft.booket.feature.record.ocr.OcrSideEffect.ShowToast
import com.ninecraft.booket.feature.screens.OcrScreen
import com.ninecraft.booket.feature.screens.OcrScreen.OcrResult
import com.orhanobut.logger.Logger
import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.retained.rememberRetained
Expand All @@ -24,6 +31,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.collections.immutable.toPersistentSet
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@AssistedInject
Expand All @@ -41,22 +49,65 @@ class OcrPresenter(

companion object {
private const val RECORD_OCR_SENTENCE = "record_OCR_sentence"
private const val CAMERA_MAX_FAILURES = 2
}

@Composable
override fun present(): OcrUiState {
val scope = rememberCoroutineScope()
var isLoading by rememberRetained { mutableStateOf(false) }
var currentUi by rememberRetained { mutableStateOf(OcrUi.CAMERA) }
var isPermissionDialogVisible by rememberRetained { mutableStateOf(false) }
var selectedImage by rememberRetained { mutableStateOf("") }
var sentenceList by rememberRetained { mutableStateOf(persistentListOf<String>()) }
var selectedIndices by rememberRetained { mutableStateOf(persistentSetOf<Int>()) }
var mergedSentence by rememberRetained { mutableStateOf("") }
var isTextDetectionFailed by rememberRetained { mutableStateOf(false) }
var isCameraRecognitionFailedDialogVisible by rememberRetained { mutableStateOf(false) }
var isGalleryRecognitionFailedDialogVisible by rememberRetained { mutableStateOf(false) }
var isRecaptureDialogVisible by rememberRetained { mutableStateOf(false) }
var isLoading by rememberRetained { mutableStateOf(false) }
var cameraFailureCount by rememberRetained { mutableIntStateOf(0) }
var sideEffect by rememberRetained { mutableStateOf<OcrSideEffect?>(null) }

fun recognizeText(imageUri: Uri) {
LaunchedEffect(isTextDetectionFailed) {
if (isTextDetectionFailed) {
delay(2000)
isTextDetectionFailed = false
}
}

fun handleRecognitionSuccess(text: String) {
isTextDetectionFailed = false
cameraFailureCount = 0

val sentences = text
.split("\n")
.map { it.trim() }
.filter { it.isNotEmpty() }

sentenceList = sentences.toPersistentList()
currentUi = OcrUi.RESULT
analyticsHelper.logScreenView(RECORD_OCR_SENTENCE)
}

fun handleRecognitionFailure(source: RecognizeSource) {
when (source) {
RecognizeSource.CAMERA -> {
isTextDetectionFailed = true
cameraFailureCount += 1

if (cameraFailureCount > CAMERA_MAX_FAILURES) {
isCameraRecognitionFailedDialogVisible = true
}
}

RecognizeSource.GALLERY -> {
isGalleryRecognitionFailedDialogVisible = true
}
}
Comment on lines +93 to +107
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "OcrPresenter.kt" | head -5

Repository: YAPP-Github/Reed-Android

Length of output: 156


🏁 Script executed:

wc -l feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt

Repository: YAPP-Github/Reed-Android

Length of output: 158


🏁 Script executed:

cat -n feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt | sed -n '85,110p'

Repository: YAPP-Github/Reed-Android

Length of output: 1099


🏁 Script executed:

cat -n feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt | sed -n '210,225p'

Repository: YAPP-Github/Reed-Android

Length of output: 657


🏁 Script executed:

rg "cameraFailureCount" feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt -n

Repository: YAPP-Github/Reed-Android

Length of output: 300


🏁 Script executed:

cat -n feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt | sed -n '70,85p'

Repository: YAPP-Github/Reed-Android

Length of output: 640


🏁 Script executed:

rg "OnImageSelected" feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt -n -B 2 -A 5

Repository: YAPP-Github/Reed-Android

Length of output: 383


🏁 Script executed:

rg "CAMERA_MAX_FAILURES" feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/ -n

Repository: YAPP-Github/Reed-Android

Length of output: 366


🏁 Script executed:

cat -n feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt | sed -n '174,180p'

Repository: YAPP-Github/Reed-Android

Length of output: 437


🏁 Script executed:

rg "OcrUiEvent\." feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt | head -20

Repository: YAPP-Github/Reed-Android

Length of output: 924


카메라 인식 실패 횟수 리셋 누락으로 인한 논리 오류

cameraFailureCount가 다이얼로그 닫기(라인 216-218)와 갤러리 모드로 전환(라인 174-178) 시에 리셋되지 않아 의도된 "연속 2회 실패" 정책이 깨집니다. 현재 로직에서는:

  1. 카메라에서 2회 연속 실패 → 다이얼로그 표시 (count=2)
  2. 다이얼로그 닫음 → count는 여전히 2
  3. 1회 더 실패 → 다이얼로그 즉시 표시 (count=3 ≥ 2)

이는 사용자가 다이얼로그를 닫은 후 새로운 시도를 하려 할 때 예상치 못한 조기 실패 다이얼로그 노출로 이어집니다.

또한 갤러리 모드로 전환 후 다시 카메라로 돌아올 때도 카운트가 유지되어 새 세션에서 1회 실패만으로 다이얼로그가 나타날 수 있습니다.

수정 제안
 OcrUiEvent.OnCameraRecognitionFailedDialogDismissed -> {
     isCameraRecognitionFailedDialogVisible = false
+    cameraFailureCount = 0
 }

 is OcrUiEvent.OnImageSelected -> {
     currentUi = OcrUi.IMAGE
     selectedImage = event.imageUri
     isTextDetectionFailed = false
     isGalleryRecognitionFailedDialogVisible = false
+    cameraFailureCount = 0
 }
🤖 Prompt for AI Agents
In
`@feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt`
around lines 91 - 105, The bug is that cameraFailureCount is never reset, so the
"2 consecutive failures" policy is broken; update the logic to reset
cameraFailureCount to 0 whenever the camera-failure dialog is dismissed (the
handler that sets isCameraRecognitionFailedDialogVisible to false) and whenever
the app switches away from camera to gallery (the code that sets
isGalleryRecognitionFailedDialogVisible / toggles camera/gallery mode), and also
reset cameraFailureCount when switching back to camera to start a fresh session;
locate references to cameraFailureCount, handleRecognitionFailure,
isCameraRecognitionFailedDialogVisible and
isGalleryRecognitionFailedDialogVisible and add cameraFailureCount = 0 in those
transition/close handlers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정책 내용이 아래와 같은데, 연속 2회의 경우에 띄우는건지 2회 이상 실패에 띄우는건지 확인해보고 작업할게

OCR 인식 오류 <다이얼로그>
OCR 인식 지연 메시지 2번 띄운 후에도 인식 안되는 경우 → 직접 입력 유도

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seoyoon513, 정책 확인하시는 것 좋습니다!

현재 코드는 cameraFailureCount >= CAMERA_MAX_FAILURES (Line 98)를 사용하고 있어서, 2회차 실패부터 다이얼로그가 표시됩니다. 만약 정책이 "2번 띄운 후" (즉, 3회차에 다이얼로그)라면 > 조건으로 변경하시면 됩니다.

다만 제가 지적한 핵심 이슈는 카운트 리셋 누락입니다:

  • 다이얼로그를 닫아도 cameraFailureCount가 유지되어, 다음 실패 시 즉시 다이얼로그가 재노출됨
  • 갤러리 모드로 전환했다가 카메라로 돌아와도 이전 카운트가 남아있음

정책 확인 후 임계값(>= vs >)과 함께 카운트 리셋 로직도 추가해 주시면 좋을 것 같습니다. 확인 후 작업 진행하시면 됩니다! 👍


🧠 Learnings used
Learnt from: seoyoon513
Repo: YAPP-Github/Reed-Android PR: 72
File: feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt:70-70
Timestamp: 2025-07-28T18:22:00.618Z
Learning: LiveTextAnalyzer는 ML Kit 에러를 addOnFailureListener로 처리하지만 로그만 남기고 presenter에 실패를 알리지 않아서, UI에서 텍스트 인식 실패 상태를 표시할 수 없는 문제가 있음. StillTextAnalyzer처럼 실패 콜백을 추가하는 것이 더 나은 해결책임

Learnt from: seoyoon513
Repo: YAPP-Github/Reed-Android PR: 46
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/InfiniteLazyColumn.kt:83-95
Timestamp: 2025-07-14T00:46:03.843Z
Learning: seoyoon513과 팀은 한국어 주석을 선호하며, 한국어 주석을 영어로 번역하라는 제안을 하지 않아야 함

}

fun recognizeText(imageUri: Uri, source: RecognizeSource) {
scope.launch {
try {
isLoading = true
Expand All @@ -65,25 +116,17 @@ class OcrPresenter(
val text = it.responses.firstOrNull()?.fullTextAnnotation?.text.orEmpty()

if (text.isNotBlank()) {
isTextDetectionFailed = false
val sentences = text
.split("\n")
.map { it.trim() }
.filter { it.isNotEmpty() }

sentenceList = sentences.toPersistentList()
currentUi = OcrUi.RESULT
analyticsHelper.logScreenView(RECORD_OCR_SENTENCE)
handleRecognitionSuccess(text)
} else {
isTextDetectionFailed = true
handleRecognitionFailure(source)
}
}
.onFailure { exception ->
isTextDetectionFailed = true
handleRecognitionFailure(source)

val handleErrorMessage = { message: String ->
Logger.e("Cloud Vision API Error: ${exception.message}")
sideEffect = OcrSideEffect.ShowToast(message)
sideEffect = ShowToast(UiText.DirectString(message))
}

handleException(
Expand Down Expand Up @@ -118,14 +161,24 @@ class OcrPresenter(

is OcrUiEvent.OnCaptureFailed -> {
isLoading = false
sideEffect = OcrSideEffect.ShowToast("이미지 캡처에 실패했어요")
sideEffect = ShowToast(UiText.StringResource(R.string.ocr_capture_failed))
Logger.e("ImageCaptureException: ${event.exception.message}")
}

is OcrUiEvent.OnImageCaptured -> {
isTextDetectionFailed = false

recognizeText(event.imageUri)
recognizeText(event.imageUri, RecognizeSource.CAMERA)
}

is OcrUiEvent.OnImageSelected -> {
currentUi = OcrUi.IMAGE
selectedImage = event.imageUri
isTextDetectionFailed = false
cameraFailureCount = 0

val pareUri = selectedImage.toUri()
recognizeText(pareUri, RecognizeSource.GALLERY)
}

is OcrUiEvent.OnReCaptureButtonClick -> {
Expand All @@ -135,7 +188,7 @@ class OcrPresenter(
is OcrUiEvent.OnSelectionConfirmed -> {
mergedSentence = selectedIndices
.sorted().joinToString("") { sentenceList[it] }
navigator.pop(result = OcrScreen.OcrResult(mergedSentence))
navigator.pop(result = OcrResult(mergedSentence))
}

is OcrUiEvent.OnSentenceSelected -> {
Expand All @@ -155,6 +208,19 @@ class OcrPresenter(
is OcrUiEvent.OnRecaptureDialogDismissed -> {
isRecaptureDialogVisible = false
}

OcrUiEvent.OnImageContentClosed -> {
currentUi = OcrUi.CAMERA
}

OcrUiEvent.OnCameraRecognitionFailedDialogDismissed -> {
isCameraRecognitionFailedDialogVisible = false
cameraFailureCount = 0
}

OcrUiEvent.OnImageRecognitionFailedDialogDismissed -> {
isGalleryRecognitionFailedDialogVisible = false
}
}
}

Expand All @@ -165,9 +231,12 @@ class OcrPresenter(
return OcrUiState(
currentUi = currentUi,
isPermissionDialogVisible = isPermissionDialogVisible,
selectedImage = selectedImage,
sentenceList = sentenceList,
selectedIndices = selectedIndices,
isTextDetectionFailed = isTextDetectionFailed,
isCameraRecognitionFailedDialogVisible = isCameraRecognitionFailedDialogVisible,
isGalleryRecognitionFailedDialogVisible = isGalleryRecognitionFailedDialogVisible,
isRecaptureDialogVisible = isRecaptureDialogVisible,
isLoading = isLoading,
sideEffect = sideEffect,
Expand Down
Loading
Loading