-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20100 from wordpress-mobile/issue/add-support-for…
…-mlkit Finish migration of scanning library from Google Code Scanner to MLKIT
- Loading branch information
Showing
17 changed files
with
949 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
WordPress/src/main/java/org/wordpress/android/modules/CodeScannerModule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package org.wordpress.android.modules | ||
|
||
import com.google.mlkit.vision.barcode.BarcodeScanner | ||
import com.google.mlkit.vision.barcode.BarcodeScanning | ||
import dagger.Module | ||
import dagger.Provides | ||
import dagger.Reusable | ||
import dagger.hilt.InstallIn | ||
import dagger.hilt.components.SingletonComponent | ||
import org.wordpress.android.ui.barcodescanner.CodeScanner | ||
import org.wordpress.android.ui.barcodescanner.GoogleBarcodeFormatMapper | ||
import org.wordpress.android.ui.barcodescanner.GoogleCodeScannerErrorMapper | ||
import org.wordpress.android.ui.barcodescanner.GoogleMLKitCodeScanner | ||
import org.wordpress.android.ui.barcodescanner.MediaImageProvider | ||
|
||
@InstallIn(SingletonComponent::class) | ||
@Module | ||
class CodeScannerModule { | ||
@Provides | ||
@Reusable | ||
fun provideGoogleCodeScanner( | ||
barcodeScanner: BarcodeScanner, | ||
googleCodeScannerErrorMapper: GoogleCodeScannerErrorMapper, | ||
barcodeFormatMapper: GoogleBarcodeFormatMapper, | ||
inputImageProvider: MediaImageProvider, | ||
): CodeScanner { | ||
return GoogleMLKitCodeScanner( | ||
barcodeScanner, | ||
googleCodeScannerErrorMapper, | ||
barcodeFormatMapper, | ||
inputImageProvider, | ||
) | ||
} | ||
|
||
@Provides | ||
@Reusable | ||
fun providesGoogleBarcodeScanner() = BarcodeScanning.getClient() | ||
} |
99 changes: 99 additions & 0 deletions
99
WordPress/src/main/java/org/wordpress/android/ui/barcodescanner/BarcodeScanner.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package org.wordpress.android.ui.barcodescanner | ||
|
||
import android.content.res.Configuration | ||
import android.util.Size | ||
import androidx.camera.core.CameraSelector | ||
import androidx.camera.core.ImageAnalysis | ||
import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST | ||
import androidx.camera.core.ImageProxy | ||
import androidx.camera.lifecycle.ProcessCameraProvider | ||
import androidx.camera.view.PreviewView | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.platform.LocalLifecycleOwner | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.viewinterop.AndroidView | ||
import androidx.core.content.ContextCompat | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.flowOf | ||
import org.wordpress.android.ui.compose.theme.AppTheme | ||
import androidx.camera.core.Preview as CameraPreview | ||
|
||
@Composable | ||
fun BarcodeScanner( | ||
codeScanner: CodeScanner, | ||
onScannedResult: (Flow<CodeScannerStatus>) -> Unit | ||
) { | ||
val context = LocalContext.current | ||
val lifecycleOwner = LocalLifecycleOwner.current | ||
val cameraProviderFuture = remember { | ||
ProcessCameraProvider.getInstance(context) | ||
} | ||
Column( | ||
modifier = Modifier.fillMaxSize() | ||
) { | ||
AndroidView( | ||
factory = { context -> | ||
val previewView = PreviewView(context) | ||
val preview = CameraPreview.Builder().build() | ||
preview.setSurfaceProvider(previewView.surfaceProvider) | ||
val selector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() | ||
val imageAnalysis = ImageAnalysis.Builder().setTargetResolution( | ||
Size( | ||
previewView.width, | ||
previewView.height | ||
) | ||
) | ||
.setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST) | ||
.build() | ||
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(context)) { imageProxy -> | ||
onScannedResult(codeScanner.startScan(imageProxy)) | ||
} | ||
try { | ||
cameraProviderFuture.get().bindToLifecycle(lifecycleOwner, selector, preview, imageAnalysis) | ||
} catch (e: IllegalStateException) { | ||
onScannedResult( | ||
flowOf( | ||
CodeScannerStatus.Failure( | ||
e.message | ||
?: "Illegal state exception while binding camera provider to lifecycle", | ||
CodeScanningErrorType.Other(e) | ||
) | ||
) | ||
) | ||
} catch (e: IllegalArgumentException) { | ||
onScannedResult( | ||
flowOf( | ||
CodeScannerStatus.Failure( | ||
e.message | ||
?: "Illegal argument exception while binding camera provider to lifecycle", | ||
CodeScanningErrorType.Other(e) | ||
) | ||
) | ||
) | ||
} | ||
previewView | ||
}, | ||
modifier = Modifier.fillMaxSize() | ||
) | ||
} | ||
} | ||
|
||
class DummyCodeScanner : CodeScanner { | ||
override fun startScan(imageProxy: ImageProxy): Flow<CodeScannerStatus> { | ||
return flowOf(CodeScannerStatus.Success("", GoogleBarcodeFormatMapper.BarcodeFormat.FormatUPCA)) | ||
} | ||
} | ||
|
||
@Preview(name = "Light mode") | ||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||
@Composable | ||
private fun BarcodeScannerScreenPreview() { | ||
AppTheme { | ||
BarcodeScanner(codeScanner = DummyCodeScanner(), onScannedResult = {}) | ||
} | ||
} |
146 changes: 146 additions & 0 deletions
146
WordPress/src/main/java/org/wordpress/android/ui/barcodescanner/BarcodeScannerScreen.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package org.wordpress.android.ui.barcodescanner | ||
|
||
import android.content.res.Configuration | ||
import androidx.activity.compose.rememberLauncherForActivityResult | ||
import androidx.activity.result.contract.ActivityResultContracts | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.material.AlertDialog | ||
import androidx.compose.material.MaterialTheme | ||
import androidx.compose.material.Text | ||
import androidx.compose.material.TextButton | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.dp | ||
import org.wordpress.android.R | ||
import kotlinx.coroutines.flow.Flow | ||
import org.wordpress.android.ui.compose.theme.AppTheme | ||
|
||
@Composable | ||
fun BarcodeScannerScreen( | ||
codeScanner: CodeScanner, | ||
permissionState: BarcodeScanningViewModel.PermissionState, | ||
onResult: (Boolean) -> Unit, | ||
onScannedResult: (Flow<CodeScannerStatus>) -> Unit, | ||
) { | ||
val cameraPermissionLauncher = rememberLauncherForActivityResult( | ||
contract = ActivityResultContracts.RequestPermission(), | ||
onResult = { granted -> | ||
onResult(granted) | ||
}, | ||
) | ||
LaunchedEffect(key1 = Unit) { | ||
cameraPermissionLauncher.launch(BarcodeScanningFragment.KEY_CAMERA_PERMISSION) | ||
} | ||
when (permissionState) { | ||
BarcodeScanningViewModel.PermissionState.Granted -> { | ||
BarcodeScanner( | ||
codeScanner = codeScanner, | ||
onScannedResult = onScannedResult | ||
) | ||
} | ||
is BarcodeScanningViewModel.PermissionState.ShouldShowRationale -> { | ||
AlertDialog( | ||
title = stringResource(id = permissionState.title), | ||
message = stringResource(id = permissionState.message), | ||
ctaLabel = stringResource(id = permissionState.ctaLabel), | ||
dismissCtaLabel = stringResource(id = permissionState.dismissCtaLabel), | ||
ctaAction = { permissionState.ctaAction.invoke(cameraPermissionLauncher) }, | ||
dismissCtaAction = { permissionState.dismissCtaAction.invoke() } | ||
) | ||
} | ||
is BarcodeScanningViewModel.PermissionState.PermanentlyDenied -> { | ||
AlertDialog( | ||
title = stringResource(id = permissionState.title), | ||
message = stringResource(id = permissionState.message), | ||
ctaLabel = stringResource(id = permissionState.ctaLabel), | ||
dismissCtaLabel = stringResource(id = permissionState.dismissCtaLabel), | ||
ctaAction = { permissionState.ctaAction.invoke(cameraPermissionLauncher) }, | ||
dismissCtaAction = { permissionState.dismissCtaAction.invoke() } | ||
) | ||
} | ||
BarcodeScanningViewModel.PermissionState.Unknown -> { | ||
// no-op | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
private fun AlertDialog( | ||
title: String, | ||
message: String, | ||
ctaLabel: String, | ||
dismissCtaLabel: String, | ||
ctaAction: () -> Unit, | ||
dismissCtaAction: () -> Unit, | ||
) { | ||
AlertDialog( | ||
onDismissRequest = { dismissCtaAction() }, | ||
title = { | ||
Text(title) | ||
}, | ||
text = { | ||
Text(message) | ||
}, | ||
confirmButton = { | ||
TextButton( | ||
onClick = { | ||
ctaAction() | ||
} | ||
) { | ||
Text( | ||
ctaLabel, | ||
color = MaterialTheme.colors.secondary, | ||
modifier = Modifier.padding(8.dp) | ||
) | ||
} | ||
}, | ||
dismissButton = { | ||
TextButton( | ||
onClick = { | ||
dismissCtaAction() | ||
} | ||
) { | ||
Text( | ||
dismissCtaLabel, | ||
color = MaterialTheme.colors.secondary, | ||
modifier = Modifier.padding(8.dp) | ||
) | ||
} | ||
}, | ||
) | ||
} | ||
|
||
@Preview(name = "Light mode") | ||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||
@Composable | ||
fun DeniedOnceAlertDialog() { | ||
AppTheme { | ||
AlertDialog( | ||
title = stringResource(id = R.string.barcode_scanning_alert_dialog_title), | ||
message = stringResource(id = R.string.barcode_scanning_alert_dialog_rationale_message), | ||
ctaLabel = stringResource(id = R.string.barcode_scanning_alert_dialog_rationale_cta_label), | ||
dismissCtaLabel = stringResource(id = R.string.barcode_scanning_alert_dialog_dismiss_label), | ||
ctaAction = {}, | ||
dismissCtaAction = {}, | ||
) | ||
} | ||
} | ||
|
||
@Preview(name = "Light mode") | ||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||
@Composable | ||
fun DeniedPermanentlyAlertDialog() { | ||
AppTheme { | ||
AlertDialog( | ||
title = stringResource(id = R.string.barcode_scanning_alert_dialog_title), | ||
message = stringResource(id = R.string.barcode_scanning_alert_dialog_permanently_denied_message), | ||
ctaLabel = stringResource(id = R.string.barcode_scanning_alert_dialog_permanently_denied_cta_label), | ||
dismissCtaLabel = stringResource(id = R.string.barcode_scanning_alert_dialog_dismiss_label), | ||
ctaAction = {}, | ||
dismissCtaAction = {}, | ||
) | ||
} | ||
} |
Oops, something went wrong.