-
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 branch 'trunk' into issue/20012-subscribing-tag-update-filter-pill
- Loading branch information
Showing
120 changed files
with
2,690 additions
and
1,770 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
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.