mirror of
https://github.com/bitwarden/android.git
synced 2025-12-11 13:57:03 -06:00
PM-26594: Move the QrCodeAnalyzer to the UI module (#5980)
This commit is contained in:
parent
cd9c7f98e7
commit
7849bbbb0a
@ -271,7 +271,6 @@ dependencies {
|
|||||||
implementation(platform(libs.square.retrofit.bom))
|
implementation(platform(libs.square.retrofit.bom))
|
||||||
implementation(libs.square.retrofit)
|
implementation(libs.square.retrofit)
|
||||||
implementation(libs.timber)
|
implementation(libs.timber)
|
||||||
implementation(libs.zxing.zxing.core)
|
|
||||||
|
|
||||||
// For now we are restricted to running Compose tests for debug builds only
|
// For now we are restricted to running Compose tests for debug builds only
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
|||||||
@ -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.appbar.BitwardenTopAppBar
|
||||||
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
|
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.model.WindowSize
|
||||||
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
||||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
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.LocalBitwardenColorScheme
|
||||||
import com.bitwarden.ui.platform.theme.color.darkBitwardenColorScheme
|
import com.bitwarden.ui.platform.theme.color.darkBitwardenColorScheme
|
||||||
import com.bitwarden.ui.platform.util.rememberWindowSize
|
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 java.util.concurrent.Executors
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|||||||
@ -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) }
|
|
||||||
@ -3,9 +3,9 @@ package com.x8bit.bitwarden.ui.vault.feature.qrcodescan
|
|||||||
import androidx.camera.core.ImageProxy
|
import androidx.camera.core.ImageProxy
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
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.bitwarden.ui.util.performCustomAccessibilityAction
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
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.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
|
|||||||
@ -224,7 +224,6 @@ dependencies {
|
|||||||
implementation(libs.kotlinx.collections.immutable)
|
implementation(libs.kotlinx.collections.immutable)
|
||||||
implementation(libs.kotlinx.coroutines.android)
|
implementation(libs.kotlinx.coroutines.android)
|
||||||
implementation(libs.kotlinx.serialization)
|
implementation(libs.kotlinx.serialization)
|
||||||
implementation(libs.zxing.zxing.core)
|
|
||||||
|
|
||||||
// For now we are restricted to running Compose tests for debug builds only
|
// For now we are restricted to running Compose tests for debug builds only
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
|||||||
@ -50,8 +50,6 @@ import androidx.compose.ui.viewinterop.AndroidView
|
|||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
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.authenticator.ui.platform.util.isPortrait
|
||||||
import com.bitwarden.ui.platform.base.util.EventsEffect
|
import com.bitwarden.ui.platform.base.util.EventsEffect
|
||||||
import com.bitwarden.ui.platform.base.util.annotatedStringResource
|
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.appbar.BitwardenTopAppBar
|
||||||
import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
||||||
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
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.BitwardenDrawable
|
||||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -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) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -8,6 +8,7 @@ import androidx.compose.ui.test.onNodeWithText
|
|||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import com.bitwarden.authenticator.ui.platform.base.AuthenticatorComposeTest
|
import com.bitwarden.authenticator.ui.platform.base.AuthenticatorComposeTest
|
||||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||||
|
import com.bitwarden.ui.platform.feature.qrcodescan.util.FakeQrCodeAnalyzer
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
|||||||
@ -57,6 +57,7 @@ dependencies {
|
|||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.browser)
|
implementation(libs.androidx.browser)
|
||||||
|
implementation(libs.androidx.camera.camera2)
|
||||||
implementation(libs.androidx.compose.animation)
|
implementation(libs.androidx.compose.animation)
|
||||||
implementation(libs.androidx.compose.material3)
|
implementation(libs.androidx.compose.material3)
|
||||||
implementation(libs.androidx.compose.material3.adaptive)
|
implementation(libs.androidx.compose.material3.adaptive)
|
||||||
@ -72,6 +73,7 @@ dependencies {
|
|||||||
implementation(libs.kotlinx.serialization)
|
implementation(libs.kotlinx.serialization)
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
implementation(libs.kotlinx.collections.immutable)
|
implementation(libs.kotlinx.collections.immutable)
|
||||||
|
implementation(libs.zxing.zxing.core)
|
||||||
|
|
||||||
// For now we are restricted to running Compose tests for debug builds only
|
// For now we are restricted to running Compose tests for debug builds only
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
@ -86,6 +88,7 @@ dependencies {
|
|||||||
testImplementation(libs.androidx.compose.ui.test)
|
testImplementation(libs.androidx.compose.ui.test)
|
||||||
|
|
||||||
testFixturesImplementation(libs.androidx.activity.compose)
|
testFixturesImplementation(libs.androidx.activity.compose)
|
||||||
|
testFixturesImplementation(libs.androidx.camera.camera2)
|
||||||
testFixturesImplementation(libs.androidx.compose.ui.test)
|
testFixturesImplementation(libs.androidx.compose.ui.test)
|
||||||
testFixturesImplementation(libs.androidx.navigation.compose)
|
testFixturesImplementation(libs.androidx.navigation.compose)
|
||||||
testFixturesImplementation(libs.google.hilt.android.testing)
|
testFixturesImplementation(libs.google.hilt.android.testing)
|
||||||
|
|||||||
@ -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.camera.core.ImageAnalysis
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
@ -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 androidx.camera.core.ImageProxy
|
||||||
|
import com.bitwarden.annotation.OmitFromCoverage
|
||||||
import com.google.zxing.BarcodeFormat
|
import com.google.zxing.BarcodeFormat
|
||||||
import com.google.zxing.BinaryBitmap
|
import com.google.zxing.BinaryBitmap
|
||||||
import com.google.zxing.DecodeHintType
|
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
|
* 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.
|
* to scan QR codes and convert them to a string.
|
||||||
*/
|
*/
|
||||||
|
@OmitFromCoverage
|
||||||
class QrCodeAnalyzerImpl : QrCodeAnalyzer {
|
class QrCodeAnalyzerImpl : QrCodeAnalyzer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,7 +33,7 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer {
|
|||||||
|
|
||||||
val source = PlanarYUVLuminanceSource(
|
val source = PlanarYUVLuminanceSource(
|
||||||
image.planes[0].buffer.toByteArray(),
|
image.planes[0].buffer.toByteArray(),
|
||||||
image.planes[0].rowStride,
|
image.width,
|
||||||
image.height,
|
image.height,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@ -39,12 +41,10 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer {
|
|||||||
image.height,
|
image.height,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
val binaryBitmap = BinaryBitmap(HybridBinarizer(source))
|
val binaryBitmap = BinaryBitmap(HybridBinarizer(source))
|
||||||
try {
|
try {
|
||||||
val result = MultiFormatReader().decode(
|
val result = MultiFormatReader().decode(
|
||||||
/* image = */ binaryBitmap,
|
binaryBitmap,
|
||||||
/* hints = */
|
|
||||||
mapOf(
|
mapOf(
|
||||||
DecodeHintType.POSSIBLE_FORMATS to arrayListOf(BarcodeFormat.QR_CODE),
|
DecodeHintType.POSSIBLE_FORMATS to arrayListOf(BarcodeFormat.QR_CODE),
|
||||||
DecodeHintType.ALSO_INVERTED to true,
|
DecodeHintType.ALSO_INVERTED to true,
|
||||||
@ -53,7 +53,8 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer {
|
|||||||
|
|
||||||
qrCodeRead = true
|
qrCodeRead = true
|
||||||
onQrCodeScanned(result.text)
|
onQrCodeScanned(result.text)
|
||||||
} catch (ignored: NotFoundException) {
|
} catch (_: NotFoundException) {
|
||||||
|
return
|
||||||
} finally {
|
} finally {
|
||||||
image.close()
|
image.close()
|
||||||
}
|
}
|
||||||
@ -63,5 +64,6 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer {
|
|||||||
/**
|
/**
|
||||||
* This function helps us prepare the byte buffer to be read.
|
* This function helps us prepare the byte buffer to be read.
|
||||||
*/
|
*/
|
||||||
|
@OmitFromCoverage
|
||||||
private fun ByteBuffer.toByteArray(): ByteArray =
|
private fun ByteBuffer.toByteArray(): ByteArray =
|
||||||
ByteArray(rewind().remaining()).also { get(it) }
|
ByteArray(rewind().remaining()).also { get(it) }
|
||||||
@ -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
|
import androidx.camera.core.ImageProxy
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user