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 3f5747fb1..1e3fd5fab 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 77f65c06b..dad328b5f 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 7fd31a1fc..f63b712e7 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) } } @@ -114,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 2244aca9e..8a9aab4dd 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 cbe7838be..a4f47b280 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() + } + } }