-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* FEAT: Scan address from QR code * BUG: BTC address prefix "bitcoin:" checking * BUG: Fixed wrong check
- Loading branch information
Showing
8 changed files
with
331 additions
and
28 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
83 changes: 83 additions & 0 deletions
83
app/src/main/java/xyz/tomashrib/zephyruswallet/tools/QRCodes.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,83 @@ | ||
package xyz.tomashrib.zephyruswallet.tools | ||
|
||
import android.graphics.ImageFormat | ||
import android.util.Log | ||
import androidx.camera.core.ImageAnalysis | ||
import androidx.camera.core.ImageProxy | ||
import androidx.compose.ui.graphics.ImageBitmap | ||
import androidx.compose.ui.graphics.asImageBitmap | ||
import androidx.core.graphics.createBitmap | ||
import com.google.zxing.* | ||
import com.google.zxing.common.BitMatrix | ||
import com.google.zxing.common.HybridBinarizer | ||
import com.google.zxing.qrcode.QRCodeWriter | ||
import java.nio.ByteBuffer | ||
|
||
private const val tag = "QrCodes" | ||
|
||
class QRCodeAnalyzer( | ||
private val onQrCodeScanned: (result: String?) -> Unit | ||
) : ImageAnalysis.Analyzer { | ||
|
||
companion object { | ||
private val SUPPORTED_IMAGE_FORMATS = listOf(ImageFormat.YUV_420_888, ImageFormat.YUV_422_888, ImageFormat.YUV_444_888) | ||
} | ||
|
||
override fun analyze(image: ImageProxy) { | ||
if (image.format in SUPPORTED_IMAGE_FORMATS) { | ||
val bytes = image.planes.first().buffer.toByteArray() | ||
val source = PlanarYUVLuminanceSource( | ||
bytes, | ||
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 listOf(BarcodeFormat.QR_CODE) | ||
) | ||
) | ||
}.decode(binaryBitmap) | ||
Log.i(tag, "QR code scanned is ${result.text}") | ||
onQrCodeScanned(result.text) | ||
} catch (e: Exception) { | ||
e.printStackTrace() | ||
} finally { | ||
image.close() | ||
} | ||
} | ||
} | ||
|
||
private fun ByteBuffer.toByteArray(): ByteArray { | ||
rewind() | ||
return ByteArray(remaining()).also { get(it) } | ||
} | ||
} | ||
|
||
fun addressToQR(address: String): ImageBitmap? { | ||
Log.i(tag, "We are generating the QR code for address $address") | ||
try { | ||
val qrCodeWriter = QRCodeWriter() | ||
val bitMatrix: BitMatrix = qrCodeWriter.encode(address, BarcodeFormat.QR_CODE, 1000, 1000) | ||
val bitMap = createBitmap(1000, 1000) | ||
for (x in 0 until 1000) { | ||
for (y in 0 until 1000) { | ||
// uses night1 and md_theme_dark_onPrimary for colors | ||
bitMap.setPixel(x, y, if (bitMatrix[x, y]) 0xff000000.toInt() else 0xfff3f4ff.toInt()) | ||
// bitMap.setPixel(x, y, if (bitMatrix[x, y]) 0xFF3c3836.toInt() else 0xFFebdbb2.toInt()) | ||
} | ||
} | ||
// Log.i(TAG, "QR is ${bitMap.asImageBitmap()}") | ||
return bitMap.asImageBitmap() | ||
} catch (e: Throwable) { | ||
Log.i(tag, "Error with QRCode generation, $e") | ||
} | ||
return null | ||
} |
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
158 changes: 158 additions & 0 deletions
158
app/src/main/java/xyz/tomashrib/zephyruswallet/ui/wallet/QRScanScreen.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,158 @@ | ||
package xyz.tomashrib.zephyruswallet.ui.wallet | ||
|
||
import android.Manifest | ||
import android.content.pm.PackageManager | ||
import androidx.activity.compose.rememberLauncherForActivityResult | ||
import androidx.activity.result.contract.ActivityResultContracts | ||
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.BorderStroke | ||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.layout.* | ||
import androidx.compose.foundation.shape.RoundedCornerShape | ||
import androidx.compose.material.* | ||
import androidx.compose.material3.Button | ||
import androidx.compose.material3.ButtonDefaults | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.* | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.platform.LocalLifecycleOwner | ||
import androidx.compose.ui.unit.dp | ||
import androidx.compose.ui.unit.sp | ||
import androidx.compose.ui.viewinterop.AndroidView | ||
import androidx.constraintlayout.compose.ConstraintLayout | ||
import androidx.core.content.ContextCompat | ||
import androidx.navigation.NavHostController | ||
import xyz.tomashrib.zephyruswallet.tools.QRCodeAnalyzer | ||
import xyz.tomashrib.zephyruswallet.ui.theme.ZephyrusColors | ||
import xyz.tomashrib.zephyruswallet.ui.theme.sourceSans | ||
|
||
@Composable | ||
internal fun QRScanScreen(navController: NavHostController) { | ||
|
||
val context = LocalContext.current | ||
val lifecycleOwner = LocalLifecycleOwner.current | ||
val snackbarHostState = remember { SnackbarHostState() } | ||
val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) } | ||
|
||
var hasCameraPermission by remember { | ||
mutableStateOf( | ||
ContextCompat.checkSelfPermission( | ||
context, | ||
Manifest.permission.CAMERA | ||
) == PackageManager.PERMISSION_GRANTED | ||
) | ||
} | ||
val launcher = rememberLauncherForActivityResult( | ||
contract = ActivityResultContracts.RequestPermission(), | ||
onResult = { granted -> | ||
hasCameraPermission = granted | ||
} | ||
) | ||
|
||
LaunchedEffect(key1 = true) { | ||
launcher.launch(Manifest.permission.CAMERA) | ||
} | ||
|
||
Scaffold( | ||
modifier = Modifier.fillMaxSize(), | ||
backgroundColor = ZephyrusColors.bgColorBlack, | ||
snackbarHost = { SnackbarHost(snackbarHostState) } | ||
) { | ||
ConstraintLayout( | ||
modifier = Modifier.fillMaxSize(), | ||
) { | ||
val (camera, cancelButton) = createRefs() | ||
|
||
Box( | ||
modifier = Modifier | ||
.background(ZephyrusColors.bgColorBlack) | ||
.constrainAs(camera) { | ||
top.linkTo(parent.top) | ||
absoluteLeft.linkTo(parent.absoluteLeft) | ||
absoluteRight.linkTo(parent.absoluteRight) | ||
bottom.linkTo(parent.bottom) | ||
} | ||
) { | ||
Column { | ||
if (hasCameraPermission) { | ||
AndroidView( | ||
factory = { context -> | ||
val previewView = PreviewView(context) | ||
val preview = Preview.Builder().build() | ||
val selector = CameraSelector.Builder() | ||
.requireLensFacing(CameraSelector.LENS_FACING_BACK) | ||
.build() | ||
preview.setSurfaceProvider(previewView.surfaceProvider) | ||
val imageAnalysis = ImageAnalysis.Builder() | ||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) | ||
.build() | ||
imageAnalysis.setAnalyzer( | ||
ContextCompat.getMainExecutor(context), | ||
QRCodeAnalyzer { result -> | ||
result?.let { | ||
navController.previousBackStackEntry | ||
?.savedStateHandle | ||
?.set("BTC_Address", it) | ||
navController.popBackStack() | ||
} | ||
} | ||
) | ||
|
||
try { | ||
cameraProviderFuture.get().bindToLifecycle( | ||
lifecycleOwner, | ||
selector, | ||
preview, | ||
imageAnalysis | ||
) | ||
} catch (e: Exception) { | ||
e.printStackTrace() | ||
} | ||
|
||
return@AndroidView previewView | ||
}, | ||
modifier = Modifier.weight(weight = 1f) | ||
) | ||
} | ||
} | ||
} | ||
|
||
Button( | ||
onClick = { | ||
navController.popBackStack() | ||
}, | ||
colors = ButtonDefaults.buttonColors(ZephyrusColors.lightPurplePrimary), | ||
shape = RoundedCornerShape(20.dp), | ||
// border = BorderStroke(3.dp, ZephyrusColors.fontColorWhite), | ||
modifier = Modifier | ||
.constrainAs(cancelButton) { | ||
start.linkTo(parent.start, margin = 16.dp) | ||
end.linkTo(parent.end, margin = 16.dp) | ||
bottom.linkTo(parent.bottom, margin = 16.dp) | ||
} | ||
.padding(top = 4.dp, start = 4.dp, end = 4.dp, bottom = 4.dp) | ||
.height(70.dp) | ||
.width(200.dp) | ||
) { | ||
Row( | ||
verticalAlignment = Alignment.CenterVertically, | ||
modifier = Modifier.padding(vertical = 4.dp) | ||
) { | ||
Text( | ||
text = "Cancel", | ||
color = ZephyrusColors.darkerPurpleOnPrimary, | ||
fontSize = 18.sp, | ||
fontFamily = sourceSans | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.