diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4fa74e8db0..1ea0ed5889 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreen.kt index 008b1975d5..2507da8d67 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreen.kt @@ -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 diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt deleted file mode 100644 index 2de6239c11..0000000000 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt +++ /dev/null @@ -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) } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreenTest.kt index a8df412f1f..d62a8677f9 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/QrCodeScanScreenTest.kt @@ -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 diff --git a/authenticator/build.gradle.kts b/authenticator/build.gradle.kts index 159b9cceda..04eef1fcf4 100644 --- a/authenticator/build.gradle.kts +++ b/authenticator/build.gradle.kts @@ -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) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt index 812f904f72..c87f417c3d 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt @@ -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 diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzer.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzer.kt deleted file mode 100644 index 2711d03eff..0000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzer.kt +++ /dev/null @@ -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 -} diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/FakeQrCodeAnalyzer.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/FakeQrCodeAnalyzer.kt deleted file mode 100644 index 6b109a87f8..0000000000 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/FakeQrCodeAnalyzer.kt +++ /dev/null @@ -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) } - } -} diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreenTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreenTest.kt index 16166315ea..87020a9f54 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreenTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreenTest.kt @@ -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 diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index fa7a56be3b..16eeffa8d8 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -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) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/util/QrCodeAnalyzer.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/feature/qrcodescan/util/QrCodeAnalyzer.kt similarity index 84% rename from app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/util/QrCodeAnalyzer.kt rename to ui/src/main/kotlin/com/bitwarden/ui/platform/feature/qrcodescan/util/QrCodeAnalyzer.kt index a0ea3552cb..749cb153d7 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/util/QrCodeAnalyzer.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/feature/qrcodescan/util/QrCodeAnalyzer.kt @@ -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 diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt similarity index 87% rename from authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt rename to ui/src/main/kotlin/com/bitwarden/ui/platform/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt index 2decaeeec9..acc73b1a2f 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt @@ -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) } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/util/FakeQrCodeAnalyzer.kt b/ui/src/testFixtures/kotlin/com/bitwarden/ui/platform/feature/qrcodescan/util/FakeQrCodeAnalyzer.kt similarity index 88% rename from app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/util/FakeQrCodeAnalyzer.kt rename to ui/src/testFixtures/kotlin/com/bitwarden/ui/platform/feature/qrcodescan/util/FakeQrCodeAnalyzer.kt index 27c6337f8a..9fbc6f34e6 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/qrcodescan/util/FakeQrCodeAnalyzer.kt +++ b/ui/src/testFixtures/kotlin/com/bitwarden/ui/platform/feature/qrcodescan/util/FakeQrCodeAnalyzer.kt @@ -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