PM-26594: Move the QrCodeAnalyzer to the UI module (#5980)

This commit is contained in:
David Perez 2025-10-07 12:04:16 -05:00 committed by GitHub
parent cd9c7f98e7
commit 7849bbbb0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 19 additions and 126 deletions

View File

@ -271,7 +271,6 @@ dependencies {
implementation(platform(libs.square.retrofit.bom))
implementation(libs.square.retrofit)
implementation(libs.timber)
implementation(libs.zxing.zxing.core)
// For now we are restricted to running Compose tests for debug builds only
debugImplementation(libs.androidx.compose.ui.test.manifest)

View File

@ -55,6 +55,8 @@ import com.bitwarden.ui.platform.base.util.annotatedStringResource
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.bitwarden.ui.platform.feature.qrcodescan.util.QrCodeAnalyzer
import com.bitwarden.ui.platform.feature.qrcodescan.util.QrCodeAnalyzerImpl
import com.bitwarden.ui.platform.model.WindowSize
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
@ -62,8 +64,6 @@ import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.platform.theme.LocalBitwardenColorScheme
import com.bitwarden.ui.platform.theme.color.darkBitwardenColorScheme
import com.bitwarden.ui.platform.util.rememberWindowSize
import com.x8bit.bitwarden.ui.vault.feature.qrcodescan.util.QrCodeAnalyzer
import com.x8bit.bitwarden.ui.vault.feature.qrcodescan.util.QrCodeAnalyzerImpl
import java.util.concurrent.Executors
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

View File

@ -1,73 +0,0 @@
package com.x8bit.bitwarden.ui.vault.feature.qrcodescan.util
import androidx.camera.core.ImageProxy
import com.bitwarden.annotation.OmitFromCoverage
import com.google.zxing.BarcodeFormat
import com.google.zxing.BinaryBitmap
import com.google.zxing.DecodeHintType
import com.google.zxing.MultiFormatReader
import com.google.zxing.NotFoundException
import com.google.zxing.PlanarYUVLuminanceSource
import com.google.zxing.common.HybridBinarizer
import java.nio.ByteBuffer
/**
* A class setup to handle image analysis so that we can use the Zxing library
* to scan QR codes and convert them to a string.
*/
@OmitFromCoverage
class QrCodeAnalyzerImpl : QrCodeAnalyzer {
/**
* This will ensure the result is only sent once as multiple images with a valid
* QR code can be sent for analysis.
*/
private var qrCodeRead = false
override lateinit var onQrCodeScanned: (String) -> Unit
override fun analyze(image: ImageProxy) {
if (qrCodeRead) {
return
}
val source = PlanarYUVLuminanceSource(
image.planes[0].buffer.toByteArray(),
image.width,
image.height,
0,
0,
image.width,
image.height,
false,
)
val binaryBitmap = BinaryBitmap(HybridBinarizer(source))
try {
val result = MultiFormatReader()
.apply {
setHints(
mapOf(
DecodeHintType.POSSIBLE_FORMATS to arrayListOf(
BarcodeFormat.QR_CODE,
),
),
)
}
.decode(binaryBitmap)
qrCodeRead = true
onQrCodeScanned(result.text)
} catch (e: NotFoundException) {
return
} finally {
image.close()
}
}
}
/**
* This function helps us prepare the byte buffer to be read.
*/
@OmitFromCoverage
private fun ByteBuffer.toByteArray(): ByteArray =
ByteArray(rewind().remaining()).also { get(it) }

View File

@ -3,9 +3,9 @@ package com.x8bit.bitwarden.ui.vault.feature.qrcodescan
import androidx.camera.core.ImageProxy
import androidx.compose.ui.test.onNodeWithText
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.ui.platform.feature.qrcodescan.util.FakeQrCodeAnalyzer
import com.bitwarden.ui.util.performCustomAccessibilityAction
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
import com.x8bit.bitwarden.ui.vault.feature.qrcodescan.util.FakeQrCodeAnalyzer
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify

View File

@ -224,7 +224,6 @@ dependencies {
implementation(libs.kotlinx.collections.immutable)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.serialization)
implementation(libs.zxing.zxing.core)
// For now we are restricted to running Compose tests for debug builds only
debugImplementation(libs.androidx.compose.ui.test.manifest)

View File

@ -50,8 +50,6 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.util.QrCodeAnalyzer
import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.util.QrCodeAnalyzerImpl
import com.bitwarden.authenticator.ui.platform.util.isPortrait
import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.base.util.annotatedStringResource
@ -60,6 +58,8 @@ import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.bitwarden.ui.platform.feature.qrcodescan.util.QrCodeAnalyzer
import com.bitwarden.ui.platform.feature.qrcodescan.util.QrCodeAnalyzerImpl
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme

View File

@ -1,16 +0,0 @@
package com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.util
import androidx.camera.core.ImageAnalysis
import androidx.compose.runtime.Stable
/**
* An interface that is used to help scan QR codes.
*/
@Stable
interface QrCodeAnalyzer : ImageAnalysis.Analyzer {
/**
* The method that is called once the code is scanned.
*/
var onQrCodeScanned: (String) -> Unit
}

View File

@ -1,22 +0,0 @@
package com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan
import androidx.camera.core.ImageProxy
import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.util.QrCodeAnalyzer
/**
* A helper class that helps test scan outcomes.
*/
class FakeQrCodeAnalyzer : QrCodeAnalyzer {
override lateinit var onQrCodeScanned: (String) -> Unit
/**
* The result of the scan that will be sent to the ViewModel (or `null` to indicate a
* scanning error.
*/
var scanResult: String? = null
override fun analyze(image: ImageProxy) {
scanResult?.let { onQrCodeScanned.invoke(it) }
}
}

View File

@ -8,6 +8,7 @@ import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import com.bitwarden.authenticator.ui.platform.base.AuthenticatorComposeTest
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.ui.platform.feature.qrcodescan.util.FakeQrCodeAnalyzer
import io.mockk.every
import io.mockk.just
import io.mockk.mockk

View File

@ -57,6 +57,7 @@ dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.browser)
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.compose.animation)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material3.adaptive)
@ -72,6 +73,7 @@ dependencies {
implementation(libs.kotlinx.serialization)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.zxing.zxing.core)
// For now we are restricted to running Compose tests for debug builds only
debugImplementation(libs.androidx.compose.ui.test.manifest)
@ -86,6 +88,7 @@ dependencies {
testImplementation(libs.androidx.compose.ui.test)
testFixturesImplementation(libs.androidx.activity.compose)
testFixturesImplementation(libs.androidx.camera.camera2)
testFixturesImplementation(libs.androidx.compose.ui.test)
testFixturesImplementation(libs.androidx.navigation.compose)
testFixturesImplementation(libs.google.hilt.android.testing)

View File

@ -1,4 +1,4 @@
package com.x8bit.bitwarden.ui.vault.feature.qrcodescan.util
package com.bitwarden.ui.platform.feature.qrcodescan.util
import androidx.camera.core.ImageAnalysis
import androidx.compose.runtime.Stable

View File

@ -1,6 +1,7 @@
package com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.util
package com.bitwarden.ui.platform.feature.qrcodescan.util
import androidx.camera.core.ImageProxy
import com.bitwarden.annotation.OmitFromCoverage
import com.google.zxing.BarcodeFormat
import com.google.zxing.BinaryBitmap
import com.google.zxing.DecodeHintType
@ -14,6 +15,7 @@ import java.nio.ByteBuffer
* A class setup to handle image analysis so that we can use the Zxing library
* to scan QR codes and convert them to a string.
*/
@OmitFromCoverage
class QrCodeAnalyzerImpl : QrCodeAnalyzer {
/**
@ -31,7 +33,7 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer {
val source = PlanarYUVLuminanceSource(
image.planes[0].buffer.toByteArray(),
image.planes[0].rowStride,
image.width,
image.height,
0,
0,
@ -39,12 +41,10 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer {
image.height,
false,
)
val binaryBitmap = BinaryBitmap(HybridBinarizer(source))
try {
val result = MultiFormatReader().decode(
/* image = */ binaryBitmap,
/* hints = */
binaryBitmap,
mapOf(
DecodeHintType.POSSIBLE_FORMATS to arrayListOf(BarcodeFormat.QR_CODE),
DecodeHintType.ALSO_INVERTED to true,
@ -53,7 +53,8 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer {
qrCodeRead = true
onQrCodeScanned(result.text)
} catch (ignored: NotFoundException) {
} catch (_: NotFoundException) {
return
} finally {
image.close()
}
@ -63,5 +64,6 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer {
/**
* This function helps us prepare the byte buffer to be read.
*/
@OmitFromCoverage
private fun ByteBuffer.toByteArray(): ByteArray =
ByteArray(rewind().remaining()).also { get(it) }

View File

@ -1,4 +1,4 @@
package com.x8bit.bitwarden.ui.vault.feature.qrcodescan.util
package com.bitwarden.ui.platform.feature.qrcodescan.util
import androidx.camera.core.ImageProxy