From c66473cc5a7d4c99442beeb7f3cad6950745331b Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:45:58 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[fix]=20#193:=20=EB=AF=B8=EC=A7=80=EC=9B=90?= =?UTF-8?q?=20QR=20=EC=A4=91=EB=B3=B5=20=EB=A1=9C=EA=B9=85=20=EC=B0=A8?= =?UTF-8?q?=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/photo_upload/impl/qrscan/QRScanViewModel.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanViewModel.kt b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanViewModel.kt index 7fd31a1f..c6a873a4 100644 --- a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanViewModel.kt +++ b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanViewModel.kt @@ -21,6 +21,7 @@ internal class QRScanViewModel @Inject constructor( private var webViewEnteredUrl: String? = null private var imageDetected: Boolean = false private var isDownloadRequiredBrand: Boolean = false + private val loggedUnsupportedUrls = mutableSetOf() val store: MviIntentStore = mviIntentStore( @@ -97,8 +98,10 @@ internal class QRScanViewModel @Inject constructor( } } } else { - viewModelScope.launch { - discordWebhookRepository.logUnsupportedBrandQR(scannedUrl) + if (loggedUnsupportedUrls.add(scannedUrl)) { + viewModelScope.launch { + discordWebhookRepository.logUnsupportedBrandQR(scannedUrl) + } } reduce { copy(isUnSupportedBrandDialogShown = true) } } From 2e6987dd80b49878ff6a1f22d0df2541645c5693 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:46:15 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[fix]=20#165:=20=EB=A7=8C=EB=A3=8C=EB=90=9C?= =?UTF-8?q?=20QR=20=EC=8A=A4=EC=BA=94=20=EC=8B=9C=20=ED=9D=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=8C=80=EC=8B=A0=20=EC=97=90=EB=9F=AC=20=EC=95=88?= =?UTF-8?q?=EB=82=B4=20=ED=9B=84=20=EC=8A=A4=EC=BA=90=EB=84=88=20=EB=B3=B5?= =?UTF-8?q?=EA=B7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/qrscan/QRScanContract.kt | 1 + .../photo_upload/impl/qrscan/QRScanScreen.kt | 1 + .../impl/qrscan/QRScanViewModel.kt | 13 ++++++++++ .../qrscan/component/PhotoWebViewContent.kt | 6 ++++- .../impl/qrscan/util/PhotoWebViewClient.kt | 26 +++++++++++++++++++ 5 files changed, 46 insertions(+), 1 deletion(-) diff --git a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanContract.kt b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanContract.kt index 3f5747fb..1e3fd5fa 100644 --- a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanContract.kt +++ b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanContract.kt @@ -34,6 +34,7 @@ sealed interface QRScanIntent { data class ScanQRCode(val scannedUrl: String) : QRScanIntent data class SetViewType(val viewType: QRScanViewType) : QRScanIntent data class DetectImageUrl(val imageUrl: String) : QRScanIntent + data object WebViewError : QRScanIntent data object DismissShouldDownloadDialog : QRScanIntent data object ClickGoDownload : QRScanIntent data object DismissUnSupportedBrandDialog : QRScanIntent diff --git a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanScreen.kt b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanScreen.kt index 77f65c06..dad328b5 100644 --- a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanScreen.kt +++ b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanScreen.kt @@ -103,6 +103,7 @@ internal fun QRScanScreen( PhotoWebViewContent( scannedUrl = uiState.scannedUrl, onDetectImageUrl = { imageUrl -> onIntent(QRScanIntent.DetectImageUrl(imageUrl)) }, + onPageError = { onIntent(QRScanIntent.WebViewError) }, ) else { LaunchedEffect(Unit) { diff --git a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanViewModel.kt b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanViewModel.kt index c6a873a4..f63b712e 100644 --- a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanViewModel.kt +++ b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/QRScanViewModel.kt @@ -117,6 +117,19 @@ internal class QRScanViewModel @Inject constructor( postSideEffect(QRScanSideEffect.SetQRScannedResult(intent.imageUrl)) } + QRScanIntent.WebViewError -> { + webViewEnteredUrl = null + imageDetected = false + isDownloadRequiredBrand = false + reduce { + copy( + viewType = QRScanViewType.QR_SCAN, + scannedUrl = null, + ) + } + postSideEffect(QRScanSideEffect.ShowToast("만료되었거나 유효하지 않은 QR코드입니다.")) + } + QRScanIntent.DismissShouldDownloadDialog -> reduce { copy(isDownloadNeededDialogShown = false) } QRScanIntent.ClickGoDownload -> reduce { copy( diff --git a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/component/PhotoWebViewContent.kt b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/component/PhotoWebViewContent.kt index 2244aca9..8a9aab4d 100644 --- a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/component/PhotoWebViewContent.kt +++ b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/component/PhotoWebViewContent.kt @@ -1,6 +1,5 @@ package com.neki.android.feature.photo_upload.impl.qrscan.component -import android.provider.SyncStateContract.Helpers.update import android.webkit.WebView import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding @@ -21,6 +20,7 @@ import com.neki.android.feature.photo_upload.impl.qrscan.util.PhotoWebViewClient internal fun PhotoWebViewContent( scannedUrl: String, onDetectImageUrl: (String) -> Unit, + onPageError: () -> Unit, ) { val webView = remember { mutableStateOf(null) } var isLoading by remember { mutableStateOf(true) } @@ -42,6 +42,10 @@ internal fun PhotoWebViewContent( webViewClient = PhotoWebViewClient( onPageFinished = { isLoading = false }, + onPageError = { + isLoading = false + onPageError() + }, ) { photoImageUrl -> onDetectImageUrl(photoImageUrl) } diff --git a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/util/PhotoWebViewClient.kt b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/util/PhotoWebViewClient.kt index cbe7838b..a4f47b28 100644 --- a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/util/PhotoWebViewClient.kt +++ b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/util/PhotoWebViewClient.kt @@ -1,5 +1,6 @@ package com.neki.android.feature.photo_upload.impl.qrscan.util +import android.webkit.WebResourceError import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView @@ -9,6 +10,7 @@ import timber.log.Timber class PhotoWebViewClient( private val onPageFinished: () -> Unit, + private val onPageError: () -> Unit, private val onImageUrlDetected: (String) -> Unit, ) : WebViewClient() { @@ -64,4 +66,28 @@ class PhotoWebViewClient( super.onPageFinished(view, url) onPageFinished() } + + override fun onReceivedError( + view: WebView?, + request: WebResourceRequest?, + error: WebResourceError?, + ) { + super.onReceivedError(view, request, error) + if (request?.isForMainFrame == true) { + Timber.e("WebView main frame error: ${error?.description}") + onPageError() + } + } + + override fun onReceivedHttpError( + view: WebView?, + request: WebResourceRequest?, + errorResponse: WebResourceResponse?, + ) { + super.onReceivedHttpError(view, request, errorResponse) + if (request?.isForMainFrame == true) { + Timber.e("WebView main frame HTTP error: ${errorResponse?.statusCode}") + onPageError() + } + } }