Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
build/
.gradle
UserInterfaceState.xcuserstate
.kotlin
.kotlin
/local.properties
12 changes: 10 additions & 2 deletions sample-app/shared/src/commonMain/kotlin/MainView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import org.publicvalue.multiplatform.qrcode.CameraOrientation
import org.publicvalue.multiplatform.qrcode.CameraPosition
import org.publicvalue.multiplatform.qrcode.CodeType
import org.publicvalue.multiplatform.qrcode.ScannerWithPermissions
Expand All @@ -34,11 +35,17 @@ fun MainView() {
Column(modifier = Modifier.padding(it)) {
Text("Scan QR-Code below")
var scannerVisible by remember {mutableStateOf(false)}
var cameraPosition by remember { mutableStateOf( CameraPosition.BACK)}
Button(onClick = {
scannerVisible = !scannerVisible
}) {
Text("Toggle scanner (visible: $scannerVisible)")
}
Button(onClick = {
cameraPosition = if(cameraPosition== CameraPosition.BACK) CameraPosition.FRONT else CameraPosition.BACK
}) {
Text("Toggle camera (position: $cameraPosition)")
}
if (scannerVisible) {
val scope = rememberCoroutineScope()
ScannerWithPermissions(
Expand All @@ -50,9 +57,10 @@ fun MainView() {
false // continue scanning
},
types = listOf(CodeType.QR),
cameraPosition = CameraPosition.BACK
cameraPosition = cameraPosition,
defaultOrientation = CameraOrientation.LANDSCAPE
)
}
}
}
}
}
3 changes: 2 additions & 1 deletion scanner/src/androidMain/kotlin/Scanner.android.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ actual fun Scanner(
onScanned: (String) -> Boolean,
types: List<CodeType>,
cameraPosition: CameraPosition,
defaultOrientation: CameraOrientation?
) {
val analyzer = remember() {
BarcodeAnalyzer(types.toFormat(), onScanned)
}
CameraView(modifier, analyzer, cameraPosition)
CameraView(modifier, analyzer, cameraPosition, defaultOrientation)
}

@OptIn(ExperimentalPermissionsApi::class)
Expand Down
71 changes: 40 additions & 31 deletions scanner/src/androidMain/kotlin/ScannerView.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package org.publicvalue.multiplatform.qrcode

import android.util.Log
import android.widget.LinearLayout
import android.view.Surface.ROTATION_0
import android.view.Surface.ROTATION_180
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
Expand All @@ -20,47 +22,54 @@ import androidx.core.content.ContextCompat
fun CameraView(
modifier: Modifier = Modifier,
analyzer: BarcodeAnalyzer,
cameraPosition: CameraPosition
cameraPosition: CameraPosition,
defaultOrientation: CameraOrientation?
) {
val localContext = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val cameraProviderFuture = remember {
ProcessCameraProvider.getInstance(localContext)
}
AndroidView(
modifier = modifier.fillMaxSize(),
factory = { context ->
val previewView = PreviewView(context)
val preview = Preview.Builder().build()
val selector = CameraSelector.Builder()
.let {
when (cameraPosition) {
CameraPosition.FRONT -> it.requireLensFacing(CameraSelector.LENS_FACING_FRONT)
CameraPosition.BACK -> it.requireLensFacing(CameraSelector.LENS_FACING_BACK)
}
val previewView = remember { PreviewView(localContext) }

LaunchedEffect(cameraPosition) {
val preview = when (defaultOrientation) {
CameraOrientation.LANDSCAPE -> Preview.Builder().setTargetRotation(ROTATION_180).build()
CameraOrientation.PORTRAIT -> Preview.Builder().setTargetRotation(ROTATION_0).build()
null -> Preview.Builder().build()
}
val selector = CameraSelector.Builder()
.let {
when (cameraPosition) {
CameraPosition.FRONT -> it.requireLensFacing(CameraSelector.LENS_FACING_FRONT)
CameraPosition.BACK -> it.requireLensFacing(CameraSelector.LENS_FACING_BACK)
}
.build()
}.build()
preview.setSurfaceProvider(previewView.surfaceProvider)

preview.setSurfaceProvider(previewView.surfaceProvider)
val imageAnalysis = ImageAnalysis.Builder().build()
imageAnalysis.setAnalyzer(
ContextCompat.getMainExecutor(localContext),
analyzer
)

val imageAnalysis = ImageAnalysis.Builder().build()
imageAnalysis.setAnalyzer(
ContextCompat.getMainExecutor(context),
analyzer
runCatching {
cameraProviderFuture.get().unbindAll()
cameraProviderFuture.get().bindToLifecycle(
lifecycleOwner,
selector,
preview,
imageAnalysis
)
}.onFailure {
Log.e("CAMERA", "Camera bind error ${it.localizedMessage}", it)
}
}

runCatching {
cameraProviderFuture.get().unbindAll()
cameraProviderFuture.get().bindToLifecycle(
lifecycleOwner,
selector,
preview,
imageAnalysis
)
}.onFailure {
Log.e("CAMERA", "Camera bind error ${it.localizedMessage}", it)
}
AndroidView(
modifier = modifier.fillMaxSize(),
factory = { context ->
previewView
}
)
}
}
6 changes: 6 additions & 0 deletions scanner/src/commonMain/kotlin/CameraOrientation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.publicvalue.multiplatform.qrcode

enum class CameraOrientation {
LANDSCAPE,
PORTRAIT
}
12 changes: 8 additions & 4 deletions scanner/src/commonMain/kotlin/Scanner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ expect fun Scanner(
modifier: Modifier = Modifier,
onScanned: (String) -> Boolean,
types: List<CodeType>,
cameraPosition: CameraPosition = CameraPosition.BACK
cameraPosition: CameraPosition = CameraPosition.BACK,
defaultOrientation: CameraOrientation? = null
)

/**
Expand All @@ -45,6 +46,7 @@ fun ScannerWithPermissions(
cameraPosition: CameraPosition = CameraPosition.BACK,
permissionText: String = "Camera is required for QR Code scanning",
openSettingsLabel: String = "Open Settings",
defaultOrientation: CameraOrientation?
) {
ScannerWithPermissions(
modifier = modifier.clipToBounds(),
Expand All @@ -61,7 +63,8 @@ fun ScannerWithPermissions(
Text(openSettingsLabel)
}
}
}
},
defaultOrientation
)
}

Expand All @@ -81,6 +84,7 @@ fun ScannerWithPermissions(
types: List<CodeType>,
cameraPosition: CameraPosition,
permissionDeniedContent: @Composable (CameraPermissionState) -> Unit,
defaultOrientation: CameraOrientation?
) {
val permissionState = rememberCameraPermissionState()

Expand All @@ -91,8 +95,8 @@ fun ScannerWithPermissions(
}

if (permissionState.status == CameraPermissionStatus.Granted) {
Scanner(modifier, types = types, onScanned = onScanned, cameraPosition = cameraPosition)
Scanner(modifier, types = types, onScanned = onScanned, cameraPosition = cameraPosition, defaultOrientation = defaultOrientation)
} else {
permissionDeniedContent(permissionState)
}
}
}
8 changes: 5 additions & 3 deletions scanner/src/iosMain/kotlin/Scanner.ios.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ actual fun Scanner(
modifier: Modifier,
onScanned: (String) -> Boolean, // return true to abort scanning
types: List<CodeType>,
cameraPosition: CameraPosition
cameraPosition: CameraPosition,
defaultOrientation: CameraOrientation?
) {
UiScannerView(
modifier = modifier,
onScanned = {
onScanned(it)
},
allowedMetadataTypes = types.toFormat(),
cameraPosition = cameraPosition
cameraPosition = cameraPosition,
defaultOrientation = defaultOrientation
)
}

Expand Down Expand Up @@ -62,4 +64,4 @@ class IosMutableCameraPermissionState: MutableCameraPermissionState() {
fun getCameraPermissionStatus(): CameraPermissionStatus {
val authorizationStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo)
return if (authorizationStatus == AVAuthorizationStatusAuthorized) CameraPermissionStatus.Granted else CameraPermissionStatus.Denied
}
}
Loading