From 88d1c4bdf699d95d5bea93c5a2c7069bd62a99ee Mon Sep 17 00:00:00 2001 From: b1urrrr Date: Fri, 13 Jan 2023 02:59:12 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[ADD/#46]=20text=20recognition=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 9 ++++ .../keyneez/presentation/ocr/OcrActivity.kt | 42 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 149a61c..afb1642 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -115,6 +115,15 @@ dependencies { // shared preference implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha03' + // google ml kit text recognition + implementation 'com.google.mlkit:text-recognition-korean:16.0.0-beta6' + + // text features + implementation 'com.google.android.gms:play-services-mlkit-text-recognition:16.0.0' + + // firebase storage + implementation 'com.google.firebase:firebase-storage' + implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.google.android.material:material:1.7.0' diff --git a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt index 9f012df..77817d3 100644 --- a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt +++ b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt @@ -1,5 +1,6 @@ package com.keyneez.presentation.ocr +import android.graphics.Bitmap import android.graphics.Paint import android.os.Bundle import android.view.View @@ -8,9 +9,14 @@ import androidx.camera.core.CameraSelector import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.content.ContextCompat +import com.google.mlkit.vision.common.InputImage +import com.google.mlkit.vision.text.Text +import com.google.mlkit.vision.text.TextRecognition +import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions import com.keyneez.presentation.ocr.dialog.OcrResultFragment import com.keyneez.util.binding.BindingActivity import com.keyneez.util.extension.setOnSingleClickListener +import com.keyneez.util.extension.showSnackbar import com.lab.keyneez.R import com.lab.keyneez.databinding.ActivityOcrBinding import timber.log.Timber @@ -82,6 +88,42 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { } } + // Text Recognition + + // CameraX -> OnImageCapturedListener & ImageAnalysis.Analyzer 활용해서 rotation 계산 + + private fun runTextRecognition(img: Bitmap) { + // 이미지 유형 : Bitmap, media.Image, ByteBuffer, byte array, device file + val image = InputImage.fromBitmap(img, 0) + val recognizer = TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build()) + recognizer.process(image) + .addOnSuccessListener { visionText -> + processTextRecognitionResult(visionText) + } + .addOnFailureListener { e -> + showSnackbar(binding.root, getString(R.string.msg_error)) + } + } + + private fun processTextRecognitionResult(text: Text) { + if (text.textBlocks.size == 0) { + Timber.e("인식된 글자 없음") + showSnackbar(binding.root, "인식된 글자가 없습니다.") + return + } + + for (block in text.textBlocks) { + Timber.d("block : $block") + + for (line in block.lines) { + Timber.d("line : $line") + + for (element in line.elements) + Timber.d("element : $element") + } + } + } + override fun onDestroy() { super.onDestroy() cameraExecutor.shutdown() From de49094f089f4076883c2292fb3486f0ba269def Mon Sep 17 00:00:00 2001 From: b1urrrr Date: Fri, 13 Jan 2023 04:46:54 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[CHORE/#46]=20take=20photo=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/keyneez/presentation/ocr/OcrActivity.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt index 77817d3..a8758fa 100644 --- a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt +++ b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt @@ -6,6 +6,7 @@ import android.os.Bundle import android.view.View import androidx.activity.viewModels import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageCapture import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.content.ContextCompat @@ -27,6 +28,7 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { private val viewModel by viewModels() private lateinit var cameraExecutor: ExecutorService + private lateinit var cameraProvider: ProcessCameraProvider override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -47,7 +49,7 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { cameraProviderFuture.addListener( { // bind camera lifecycle - val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() + cameraProvider = cameraProviderFuture.get() val preview = Preview.Builder().build().also { it.setSurfaceProvider(binding.previewOcr.surfaceProvider) @@ -83,13 +85,15 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { private fun initCameraBtnClickListener() { binding.btnOcrCamera.setOnSingleClickListener { - val ocrResultBottomSheet = OcrResultFragment() - ocrResultBottomSheet.show(supportFragmentManager, ocrResultBottomSheet.tag) + takePhoto() } } - // Text Recognition + private fun takePhoto() { + val imageCapture = ImageCapture.Builder().build() + } + // Text Recognition // CameraX -> OnImageCapturedListener & ImageAnalysis.Analyzer 활용해서 rotation 계산 private fun runTextRecognition(img: Bitmap) { @@ -106,6 +110,9 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { } private fun processTextRecognitionResult(text: Text) { + val ocrResultBottomSheet = OcrResultFragment() + ocrResultBottomSheet.show(supportFragmentManager, ocrResultBottomSheet.tag) + if (text.textBlocks.size == 0) { Timber.e("인식된 글자 없음") showSnackbar(binding.root, "인식된 글자가 없습니다.") From f7d7d33e082a84cf54c81d9a241be18b4b76aa96 Mon Sep 17 00:00:00 2001 From: b1urrrr Date: Fri, 13 Jan 2023 08:20:12 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[FEAT/#46]=20=EC=82=AC=EC=A7=84=20=EC=B4=AC?= =?UTF-8?q?=EC=98=81=20=EB=B0=8F=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/model/response/ResponseIdDto.kt | 2 +- .../presentation/login/phone/PhoneFragment.kt | 1 + .../login/pin/LoginPinFragment.kt | 24 ++-- .../presentation/main/id/IdFragment.kt | 4 +- .../keyneez/presentation/ocr/OcrActivity.kt | 52 ++++++-- .../signup/danal/guide/DanalGuideFragment.kt | 3 + .../main/res/layout/fragment_danal_guide.xml | 125 ++++++++++-------- app/src/main/res/layout/fragment_id.xml | 1 - .../main/res/layout/fragment_login_pin.xml | 12 +- 9 files changed, 141 insertions(+), 83 deletions(-) diff --git a/app/src/main/java/com/keyneez/data/model/response/ResponseIdDto.kt b/app/src/main/java/com/keyneez/data/model/response/ResponseIdDto.kt index c91a7a3..c1ba252 100644 --- a/app/src/main/java/com/keyneez/data/model/response/ResponseIdDto.kt +++ b/app/src/main/java/com/keyneez/data/model/response/ResponseIdDto.kt @@ -22,6 +22,6 @@ data class ResponseIdDto( @Serializable data class Character( val character: String?, - @SerialName("character_img") val characterImg: String? +// @SerialName("character_img") val characterImg: String? ) } diff --git a/app/src/main/java/com/keyneez/presentation/login/phone/PhoneFragment.kt b/app/src/main/java/com/keyneez/presentation/login/phone/PhoneFragment.kt index a119e42..6675872 100644 --- a/app/src/main/java/com/keyneez/presentation/login/phone/PhoneFragment.kt +++ b/app/src/main/java/com/keyneez/presentation/login/phone/PhoneFragment.kt @@ -34,6 +34,7 @@ class PhoneFragment : BindingFragment(R.layout.fragment_ph private fun initNextBtnClickListener() { binding.btnPhoneNext.setOnSingleClickListener { + requireContext().hideKeyboard(requireView()) (activity as LoginActivity).setPhoneNumber(viewModel.phoneNumber.value.toString()) (activity as LoginActivity).intentToNextPage() } diff --git a/app/src/main/java/com/keyneez/presentation/login/pin/LoginPinFragment.kt b/app/src/main/java/com/keyneez/presentation/login/pin/LoginPinFragment.kt index c6f4fa2..d5545ad 100644 --- a/app/src/main/java/com/keyneez/presentation/login/pin/LoginPinFragment.kt +++ b/app/src/main/java/com/keyneez/presentation/login/pin/LoginPinFragment.kt @@ -64,16 +64,22 @@ class LoginPinFragment : BindingFragment(R.layout.fragm } is UiState.Failure -> { when (it.code) { - UNAUTHORIZED_USER_CODE -> requireContext().showSnackbar( - binding.root, - getString( - R.string.login_pin_unauthorized_msg + UNAUTHORIZED_USER_CODE -> { + viewModel.resetPassword() + requireContext().showSnackbar( + binding.root, + getString( + R.string.signup_pin_confirm_invalid_pwd_msg + ) ) - ) - INVALID_PWD_CODE -> requireContext().showSnackbar( - binding.root, - getString(R.string.signup_pin_confirm_invalid_pwd_msg) - ) + } + INVALID_PWD_CODE -> { + viewModel.resetPassword() + requireContext().showSnackbar( + binding.root, + getString(R.string.signup_pin_confirm_invalid_pwd_msg) + ) + } else -> requireContext().showSnackbar( binding.root, getString(R.string.msg_error) diff --git a/app/src/main/java/com/keyneez/presentation/main/id/IdFragment.kt b/app/src/main/java/com/keyneez/presentation/main/id/IdFragment.kt index 13d3c28..2167c3d 100644 --- a/app/src/main/java/com/keyneez/presentation/main/id/IdFragment.kt +++ b/app/src/main/java/com/keyneez/presentation/main/id/IdFragment.kt @@ -29,6 +29,8 @@ class IdFragment : BindingFragment(R.layout.fragment_id) { binding.vm = viewModel initBottomSheet() initIdPhotoBtnClickListener() + initIdIssueBtnClickListener() + initIdMainBtnClickListener() observeIdStateMessage() } @@ -39,12 +41,10 @@ class IdFragment : BindingFragment(R.layout.fragment_id) { // 발급하기 화면이 뜨게 binding.layoutIdIssue.visibility = View.VISIBLE binding.layoutIdMain.visibility = View.GONE - initIdIssueBtnClickListener() } else { // 메인 아이디 화면이 뜨게 binding.layoutIdIssue.visibility = View.GONE binding.layoutIdMain.visibility = View.VISIBLE - initIdMainBtnClickListener() initIdBackGround() } is UiState.Failure -> requireContext().showSnackbar( diff --git a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt index a8758fa..f3f6122 100644 --- a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt +++ b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt @@ -1,13 +1,12 @@ package com.keyneez.presentation.ocr import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.graphics.Paint import android.os.Bundle import android.view.View import androidx.activity.viewModels -import androidx.camera.core.CameraSelector -import androidx.camera.core.ImageCapture -import androidx.camera.core.Preview +import androidx.camera.core.* // ktlint-disable no-wildcard-imports import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.content.ContextCompat import com.google.mlkit.vision.common.InputImage @@ -21,6 +20,7 @@ import com.keyneez.util.extension.showSnackbar import com.lab.keyneez.R import com.lab.keyneez.databinding.ActivityOcrBinding import timber.log.Timber +import java.nio.ByteBuffer import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -29,6 +29,7 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { private lateinit var cameraExecutor: ExecutorService private lateinit var cameraProvider: ProcessCameraProvider + private var imageCapture: ImageCapture? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -55,12 +56,15 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { it.setSurfaceProvider(binding.previewOcr.surfaceProvider) } + imageCapture = ImageCapture.Builder() + .build() + // select default back camera val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { cameraProvider.unbindAll() - cameraProvider.bindToLifecycle(this, cameraSelector, preview) + cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture) } catch (e: Exception) { Timber.e("$e : Use case binding failed") } @@ -90,7 +94,32 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { } private fun takePhoto() { - val imageCapture = ImageCapture.Builder().build() + imageCapture = imageCapture ?: return + + imageCapture!!.takePicture( + cameraExecutor, + object : ImageCapture.OnImageCapturedCallback() { + override fun onCaptureSuccess(image: ImageProxy) { + val bitmap = imageProxyToBitmap(image) + runTextRecognition(bitmap) + super.onCaptureSuccess(image) + } + + override fun onError(exception: ImageCaptureException) { + Timber.tag(tag).e("exception : $exception") + showSnackbar(binding.root, getString(R.string.msg_error)) + super.onError(exception) + } + } + ) + } + + private fun imageProxyToBitmap(image: ImageProxy): Bitmap { + val planeProxy = image.planes[0] + val buffer: ByteBuffer = planeProxy.buffer + val bytes = ByteArray(buffer.remaining()) + buffer.get(bytes) + return BitmapFactory.decodeByteArray(bytes, 0, bytes.size) } // Text Recognition @@ -105,6 +134,7 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { processTextRecognitionResult(visionText) } .addOnFailureListener { e -> + Timber.tag(tag).e("error : $e") showSnackbar(binding.root, getString(R.string.msg_error)) } } @@ -114,19 +144,19 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { ocrResultBottomSheet.show(supportFragmentManager, ocrResultBottomSheet.tag) if (text.textBlocks.size == 0) { - Timber.e("인식된 글자 없음") + Timber.tag(tag).e("인식된 글자 없음") showSnackbar(binding.root, "인식된 글자가 없습니다.") return } for (block in text.textBlocks) { - Timber.d("block : $block") + Timber.tag(tag).d("block : $block") for (line in block.lines) { - Timber.d("line : $line") + Timber.tag(tag).d("line : $line") for (element in line.elements) - Timber.d("element : $element") + Timber.tag(tag).d("element : $element") } } } @@ -135,4 +165,8 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { super.onDestroy() cameraExecutor.shutdown() } + + companion object { + private const val tag = "OCR_TEST" + } } diff --git a/app/src/main/java/com/keyneez/presentation/signup/danal/guide/DanalGuideFragment.kt b/app/src/main/java/com/keyneez/presentation/signup/danal/guide/DanalGuideFragment.kt index 49f4d12..abada52 100644 --- a/app/src/main/java/com/keyneez/presentation/signup/danal/guide/DanalGuideFragment.kt +++ b/app/src/main/java/com/keyneez/presentation/signup/danal/guide/DanalGuideFragment.kt @@ -33,6 +33,9 @@ class DanalGuideFragment : binding.layoutDanalGuide.setOnSingleClickListener { requireActivity().hideKeyboard(requireView()) } + binding.layoutDanalGuideContent.setOnSingleClickListener { + requireActivity().hideKeyboard(requireView()) + } } private fun initBackBtnClickListener() { diff --git a/app/src/main/res/layout/fragment_danal_guide.xml b/app/src/main/res/layout/fragment_danal_guide.xml index 389bc13..26c30c7 100644 --- a/app/src/main/res/layout/fragment_danal_guide.xml +++ b/app/src/main/res/layout/fragment_danal_guide.xml @@ -27,64 +27,79 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - - - + android:layout_height="0dp" + android:layout_marginVertical="9dp" + app:layout_constraintBottom_toTopOf="@id/btn_danal_guide_verify" + app:layout_constraintTop_toBottomOf="@id/btn_danal_guide_back"> - + - + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_login_pin.xml b/app/src/main/res/layout/fragment_login_pin.xml index 96df5d4..bc34a63 100644 --- a/app/src/main/res/layout/fragment_login_pin.xml +++ b/app/src/main/res/layout/fragment_login_pin.xml @@ -44,7 +44,7 @@ android:id="@+id/view_login_pin_pwd0" isSelected="@{vm.passwordText.length() >= 1}" android:layout_width="20dp" - android:layout_height="0dp" + android:layout_height="20dp" android:background="@drawable/sel_pin_circle" app:layout_constraintDimensionRatio="1:1" /> @@ -52,7 +52,7 @@ android:id="@+id/view_login_pin_pwd1" isSelected="@{vm.passwordText.length() >= 2}" android:layout_width="20dp" - android:layout_height="0dp" + android:layout_height="20dp" android:background="@drawable/sel_pin_circle" app:layout_constraintDimensionRatio="1:1" /> @@ -60,7 +60,7 @@ android:id="@+id/view_login_pin_pwd2" isSelected="@{vm.passwordText.length() >= 3}" android:layout_width="20dp" - android:layout_height="0dp" + android:layout_height="20dp" android:background="@drawable/sel_pin_circle" app:layout_constraintDimensionRatio="1:1" /> @@ -68,7 +68,7 @@ android:id="@+id/view_login_pin_pwd3" isSelected="@{vm.passwordText.length() >= 4}" android:layout_width="20dp" - android:layout_height="0dp" + android:layout_height="20dp" android:background="@drawable/sel_pin_circle" app:layout_constraintDimensionRatio="1:1" /> @@ -76,7 +76,7 @@ android:id="@+id/view_login_pin_pwd4" isSelected="@{vm.passwordText.length() >= 5}" android:layout_width="20dp" - android:layout_height="0dp" + android:layout_height="20dp" android:background="@drawable/sel_pin_circle" app:layout_constraintDimensionRatio="1:1" /> @@ -84,7 +84,7 @@ android:id="@+id/view_login_pin_pwd5" isSelected="@{vm.passwordText.length() >= 6}" android:layout_width="20dp" - android:layout_height="0dp" + android:layout_height="20dp" android:background="@drawable/sel_pin_circle" app:layout_constraintDimensionRatio="1:1" /> From 91846d11f6a95abcbb7dfcaa0e454f9a2ebb1b25 Mon Sep 17 00:00:00 2001 From: b1urrrr Date: Fri, 13 Jan 2023 08:31:12 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[MOD/#46]=20timber=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EC=B6=9C=20=EA=B2=B0=EA=B3=BC=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/keyneez/presentation/ocr/OcrActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt index f3f6122..79ac996 100644 --- a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt +++ b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt @@ -150,13 +150,13 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { } for (block in text.textBlocks) { - Timber.tag(tag).d("block : $block") + Timber.tag(tag).d("block : ${block.text}") for (line in block.lines) { - Timber.tag(tag).d("line : $line") + Timber.tag(tag).d("line : ${line.text}") for (element in line.elements) - Timber.tag(tag).d("element : $element") + Timber.tag(tag).d("element : ${element.text}") } } } From b394c558e3f8a01485ae15e63ac61f1d56f90acd Mon Sep 17 00:00:00 2001 From: b1urrrr Date: Fri, 13 Jan 2023 09:31:20 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[FEAT/#46]=20=EC=B9=B4=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=9D=B8=EC=8B=9D=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../keyneez/presentation/ocr/OcrActivity.kt | 39 ++++++++++++---- .../keyneez/presentation/ocr/OcrViewModel.kt | 46 +++++++++++++++++++ .../ocr/dialog/OcrResultFragment.kt | 5 +- .../main/res/layout/bot_sheet_ocr_result.xml | 8 ++-- 4 files changed, 81 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt index 79ac996..42d388e 100644 --- a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt +++ b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt @@ -25,7 +25,7 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.Executors class OcrActivity : BindingActivity(R.layout.activity_ocr) { - private val viewModel by viewModels() + val viewModel by viewModels() private lateinit var cameraExecutor: ExecutorService private lateinit var cameraProvider: ProcessCameraProvider @@ -140,25 +140,44 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { } private fun processTextRecognitionResult(text: Text) { - val ocrResultBottomSheet = OcrResultFragment() - ocrResultBottomSheet.show(supportFragmentManager, ocrResultBottomSheet.tag) - if (text.textBlocks.size == 0) { Timber.tag(tag).e("인식된 글자 없음") showSnackbar(binding.root, "인식된 글자가 없습니다.") return } - for (block in text.textBlocks) { - Timber.tag(tag).d("block : ${block.text}") + viewModel.resetIdInfo() + var isSuccess = false + for (block in text.textBlocks) { for (line in block.lines) { - Timber.tag(tag).d("line : ${line.text}") - - for (element in line.elements) - Timber.tag(tag).d("element : ${element.text}") + for (element in line.elements) { + val word = element.text + Timber.tag(tag).d("element : $word") + + when (word) { + "학생증" -> { + viewModel.setIsStudent(true) + isSuccess = true + } + "청소년증" -> { + viewModel.setIsStudent(false) + isSuccess = true + } + else -> { + if (isSuccess && viewModel.idName.value == "" && word.length == 3) viewModel.setIdName(word) + if (isSuccess && viewModel.isStudentId.value == true && viewModel.idSubEntry.value == "" && word.endsWith("학교")) viewModel.setIdSchool(word) + if (isSuccess && viewModel.isStudentId.value == false && viewModel.idSubEntry.value == "" && word.length == 14 && word.contains('-')) viewModel.setBirthDate(word) + } + } + } } } + + if (isSuccess) { + val ocrResultBottomSheet = OcrResultFragment() + ocrResultBottomSheet.show(supportFragmentManager, ocrResultBottomSheet.tag) + } } override fun onDestroy() { diff --git a/app/src/main/java/com/keyneez/presentation/ocr/OcrViewModel.kt b/app/src/main/java/com/keyneez/presentation/ocr/OcrViewModel.kt index b115ef4..2655b02 100644 --- a/app/src/main/java/com/keyneez/presentation/ocr/OcrViewModel.kt +++ b/app/src/main/java/com/keyneez/presentation/ocr/OcrViewModel.kt @@ -16,9 +16,22 @@ class OcrViewModel @Inject constructor() : ViewModel() { val isPassive: LiveData get() = _isPassive + private val _idName = MutableLiveData() + val idName: MutableLiveData + get() = _idName + + private val _idSubEntry = MutableLiveData() + val idSubEntry: MutableLiveData + get() = _idSubEntry + + private val _isStudentId = MutableLiveData() + val isStudentId: LiveData + get() = _isStudentId + init { _isVertical.value = false _isPassive.value = false + _isStudentId.value = true } /** 카메라 프레임 회전 */ @@ -30,4 +43,37 @@ class OcrViewModel @Inject constructor() : ViewModel() { fun updateRecognitionType() { _isPassive.value = !requireNotNull(_isPassive.value) } + + /** 신분증 이름 설정 */ + fun setIdName(name: String) { + _idName.value = name + } + + /** 신분증 학교 설정 */ + fun setIdSchool(school: String) { + _idSubEntry.value = school + } + + /** 생년월일 설정 */ + fun setBirthDate(date: String) { + // date 형식 : 000000-0000000 + _idSubEntry.value = date.substring(0, 6) + } + + /** 학생증 여부 판단 */ + fun setIsStudent(isStudent: Boolean) { + _isStudentId.value = isStudent + } + + /** ID 카드 유형 변경 */ + fun updateIdType(isStudent: Boolean) { + _isStudentId.value = isStudent + _idSubEntry.value = "" + } + + /** 텍스트 추출 초기화 */ + fun resetIdInfo() { + _idName.value = "" + _idSubEntry.value = "" + } } diff --git a/app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultFragment.kt b/app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultFragment.kt index 0d71dfb..15bef9b 100644 --- a/app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultFragment.kt +++ b/app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultFragment.kt @@ -4,7 +4,7 @@ import android.app.Activity.RESULT_OK import android.content.Intent import android.os.Bundle import android.view.View -import androidx.fragment.app.viewModels +import com.keyneez.presentation.ocr.OcrActivity import com.keyneez.presentation.ocr.guide.OcrGuideActivity import com.keyneez.util.binding.BindingBottomSheetDialog import com.keyneez.util.extension.hideKeyboard @@ -14,11 +14,10 @@ import com.lab.keyneez.databinding.BotSheetOcrResultBinding class OcrResultFragment : BindingBottomSheetDialog(R.layout.bot_sheet_ocr_result) { - private val viewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.vm = viewModel + binding.vm = (activity as OcrActivity).viewModel initHideKeyboard() initReshootBtnClickListener() diff --git a/app/src/main/res/layout/bot_sheet_ocr_result.xml b/app/src/main/res/layout/bot_sheet_ocr_result.xml index db5ce0c..360474e 100644 --- a/app/src/main/res/layout/bot_sheet_ocr_result.xml +++ b/app/src/main/res/layout/bot_sheet_ocr_result.xml @@ -7,7 +7,7 @@ + type="com.keyneez.presentation.ocr.OcrViewModel" /> @@ -97,7 +97,7 @@ android:background="@drawable/shape_gray400_bottomline_rect" android:hint="@string/ocr_result_name_hint" android:paddingVertical="17dp" - android:text="@={vm.nameText}" + android:text="@={vm.idName}" app:layout_constraintTop_toBottomOf="@id/tv_ocr_result_name" /> @@ -170,7 +170,7 @@ android:layout_marginTop="111dp" android:layout_marginEnd="24dp" android:background="@drawable/sel_ocr_result_confirm_btn" - android:enabled="@{!vm.nameText.isEmpty() && !vm.subEntryText.isEmpty()}" + android:enabled="@{!vm.idName.isEmpty() && !vm.idSubEntry.isEmpty()}" android:gravity="center" android:paddingVertical="15dp" android:text="@string/ocr_result_confirm" From 867aabc98c7dd7203994be9ef920c4a6485767e9 Mon Sep 17 00:00:00 2001 From: b1urrrr Date: Fri, 13 Jan 2023 10:41:18 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[MOD/#46]=20=EC=B2=AD=EC=86=8C=EB=85=84?= =?UTF-8?q?=EC=A6=9D=20=EC=9D=B4=EB=A6=84=20=EC=9D=B8=EC=8B=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=B4=EC=99=84=20=EB=B0=8F=20firebase=20storage?= =?UTF-8?q?=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 13 ++++------ .../keyneez/presentation/ocr/OcrActivity.kt | 4 +-- .../ocr/dialog/OcrResultFragment.kt | 1 - .../ocr/dialog/OcrResultViewModel.kt | 25 ------------------- .../keyneez/util/binding/BindingAdapter.kt | 2 -- app/src/main/res/layout/item_like_content.xml | 3 +-- build.gradle | 6 ++++- 7 files changed, 12 insertions(+), 42 deletions(-) delete mode 100644 app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultViewModel.kt diff --git a/app/build.gradle b/app/build.gradle index 6d6e425..175646d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,6 +5,7 @@ plugins { id 'dagger.hilt.android.plugin' id 'org.jetbrains.kotlin.plugin.serialization' version '1.7.10' id "org.jlleitschuh.gradle.ktlint" version "10.3.0" + id 'com.google.gms.google-services' } apply plugin: 'kotlin-kapt' @@ -107,6 +108,7 @@ dependencies { // google ml kit implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2' + implementation 'com.google.mlkit:text-recognition-korean:16.0.0-beta6' // viewpager2 implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' @@ -115,19 +117,14 @@ dependencies { implementation "com.tbuonomo:dotsindicator:4.3" // shared preference - implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha03' - - // google ml kit text recognition - implementation 'com.google.mlkit:text-recognition-korean:16.0.0-beta6' + implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha04' - // text features - implementation 'com.google.android.gms:play-services-mlkit-text-recognition:16.0.0' // firebase storage - // implementation 'com.google.firebase:firebase-storage' + implementation 'com.google.firebase:firebase-storage:20.1.0' implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'androidx.appcompat:appcompat:1.6.0' implementation 'com.google.android.material:material:1.7.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'junit:junit:4.13.2' diff --git a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt index 42d388e..89de4f0 100644 --- a/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt +++ b/app/src/main/java/com/keyneez/presentation/ocr/OcrActivity.kt @@ -122,9 +122,6 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { return BitmapFactory.decodeByteArray(bytes, 0, bytes.size) } - // Text Recognition - // CameraX -> OnImageCapturedListener & ImageAnalysis.Analyzer 활용해서 rotation 계산 - private fun runTextRecognition(img: Bitmap) { // 이미지 유형 : Bitmap, media.Image, ByteBuffer, byte array, device file val image = InputImage.fromBitmap(img, 0) @@ -166,6 +163,7 @@ class OcrActivity : BindingActivity(R.layout.activity_ocr) { } else -> { if (isSuccess && viewModel.idName.value == "" && word.length == 3) viewModel.setIdName(word) + if (isSuccess && viewModel.idName.value == "" && word.startsWith("명:")) viewModel.setIdName(word.substring(2, 5)) if (isSuccess && viewModel.isStudentId.value == true && viewModel.idSubEntry.value == "" && word.endsWith("학교")) viewModel.setIdSchool(word) if (isSuccess && viewModel.isStudentId.value == false && viewModel.idSubEntry.value == "" && word.length == 14 && word.contains('-')) viewModel.setBirthDate(word) } diff --git a/app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultFragment.kt b/app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultFragment.kt index 15bef9b..91c2c90 100644 --- a/app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultFragment.kt +++ b/app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultFragment.kt @@ -14,7 +14,6 @@ import com.lab.keyneez.databinding.BotSheetOcrResultBinding class OcrResultFragment : BindingBottomSheetDialog(R.layout.bot_sheet_ocr_result) { - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.vm = (activity as OcrActivity).viewModel diff --git a/app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultViewModel.kt b/app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultViewModel.kt deleted file mode 100644 index 44f2dcc..0000000 --- a/app/src/main/java/com/keyneez/presentation/ocr/dialog/OcrResultViewModel.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.keyneez.presentation.ocr.dialog - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import javax.inject.Inject - -class OcrResultViewModel @Inject constructor() : ViewModel() { - private val _isStudentId = MutableLiveData() - val isStudentId: LiveData - get() = _isStudentId - - init { - _isStudentId.value = true - } - - val nameText = MutableLiveData("") - val subEntryText = MutableLiveData("") - - /** ID 카드 유형 변경 */ - fun updateIdType(isStudent: Boolean) { - _isStudentId.value = isStudent - subEntryText.value = "" - } -} diff --git a/app/src/main/java/com/keyneez/util/binding/BindingAdapter.kt b/app/src/main/java/com/keyneez/util/binding/BindingAdapter.kt index d2dbef4..e808401 100644 --- a/app/src/main/java/com/keyneez/util/binding/BindingAdapter.kt +++ b/app/src/main/java/com/keyneez/util/binding/BindingAdapter.kt @@ -15,8 +15,6 @@ object BindingAdapter { @BindingAdapter("setRoundedImage") fun ImageView.setRoundedImage(url: String?) { this.load(url) { - fallback(R.drawable.img_like_background) - placeholder(R.drawable.img_like_background) transformations(RoundedCornersTransformation(14f)) } } diff --git a/app/src/main/res/layout/item_like_content.xml b/app/src/main/res/layout/item_like_content.xml index 5c7a29f..155bcb1 100644 --- a/app/src/main/res/layout/item_like_content.xml +++ b/app/src/main/res/layout/item_like_content.xml @@ -38,8 +38,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" putStartDate="@{@string/like_date(data.start)" - putEndDate="@{@string/like_date(data.end)" - android:text="@{@string/like_date(data.start,data.end)}" /> + putEndDate="@{@string/like_date(data.end)" />