mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
android: colored and clickable qr code with logo (#1884)
* android: colored and clickable qr code with logo * save qr code as jpg for better quality * bigger logo * bigger logo (0.16f), low error correction (QR scans ok with up to 0.26f circle size) --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
c8fae0ec43
commit
5082f5b4a4
@@ -1,5 +1,6 @@
|
||||
package chat.simplex.app.views.helpers
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.*
|
||||
@@ -374,6 +375,24 @@ fun saveAnimImage(context: Context, uri: Uri): String? {
|
||||
}
|
||||
}
|
||||
|
||||
fun saveTempImageUncompressed(image: Bitmap, asPng: Boolean): File? {
|
||||
return try {
|
||||
val ext = if (asPng) "png" else "jpg"
|
||||
val tmpDir = SimplexApp.context.getDir("temp", Application.MODE_PRIVATE)
|
||||
return File(tmpDir.absolutePath + File.separator + generateNewFileName(SimplexApp.context, "IMG", ext)).apply {
|
||||
outputStream().use { out ->
|
||||
image.compress(if (asPng) Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG, 85, out)
|
||||
out.flush()
|
||||
}
|
||||
deleteOnExit()
|
||||
SimplexApp.context.chatModel.filesToDelete.add(this)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Util.kt saveTempImageUncompressed error: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun saveFileFromUri(context: Context, uri: Uri): String? {
|
||||
return try {
|
||||
val inputStream = context.contentResolver.openInputStream(uri)
|
||||
|
||||
@@ -1,34 +1,98 @@
|
||||
package chat.simplex.app.views.newchat
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.layout.boundsInRoot
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import boofcv.alg.fiducial.qrcode.QrCodeEncoder
|
||||
import boofcv.alg.fiducial.qrcode.QrCodeGeneratorImage
|
||||
import androidx.core.graphics.applyCanvas
|
||||
import boofcv.alg.fiducial.qrcode.*
|
||||
import boofcv.android.ConvertBitmap
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun QRCode(connReq: String, modifier: Modifier = Modifier) {
|
||||
Image(
|
||||
bitmap = qrCodeBitmap(connReq, 1024).asImageBitmap(),
|
||||
contentDescription = stringResource(R.string.image_descr_qr_code),
|
||||
modifier = modifier
|
||||
)
|
||||
fun QRCode(connReq: String, modifier: Modifier = Modifier, withLogo: Boolean = true, tintColor: Color = Color(0xff062d56)) {
|
||||
val view = LocalView.current
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var rect by remember { mutableStateOf<Rect?>(null) }
|
||||
BoxWithConstraints(Modifier
|
||||
.onGloballyPositioned {
|
||||
rect = it.boundsInRoot()
|
||||
}
|
||||
.clickable {
|
||||
scope.launch {
|
||||
val r = rect
|
||||
if (r != null) {
|
||||
val image = Bitmap.createBitmap(r.width.toInt(), r.height.toInt(), Bitmap.Config.ARGB_8888).applyCanvas {
|
||||
translate(-r.left, -r.top)
|
||||
view.draw(this)
|
||||
}
|
||||
val file = saveTempImageUncompressed(image, false)
|
||||
if (file != null) {
|
||||
shareFile(context, "", file.absolutePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
bitmap = qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()).asImageBitmap(),
|
||||
contentDescription = stringResource(R.string.image_descr_qr_code),
|
||||
modifier = modifier
|
||||
)
|
||||
if (withLogo) {
|
||||
Box(
|
||||
Modifier
|
||||
.size(maxWidth * 0.16f)
|
||||
.background(Color.White, RoundedCornerShape(100))
|
||||
)
|
||||
Image(
|
||||
painterResource(R.mipmap.icon_foreground),
|
||||
null,
|
||||
Modifier
|
||||
.size(maxWidth * 0.24f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun qrCodeBitmap(content: String, size: Int): Bitmap {
|
||||
val qrCode = QrCodeEncoder().addAutomatic(content).fixate()
|
||||
val qrCode = QrCodeEncoder().addAutomatic(content).setError(QrCode.ErrorLevel.L).fixate()
|
||||
val renderer = QrCodeGeneratorImage(5)
|
||||
renderer.render(qrCode)
|
||||
return ConvertBitmap.grayToBitmap(renderer.gray, Bitmap.Config.RGB_565)
|
||||
}
|
||||
|
||||
fun Bitmap.replaceColor(from: Int, to: Int): Bitmap {
|
||||
val pixels = IntArray(width * height)
|
||||
getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
var i = 0
|
||||
while (i < pixels.size) {
|
||||
if (pixels[i] == from) {
|
||||
pixels[i] = to
|
||||
}
|
||||
i++
|
||||
}
|
||||
setPixels(pixels, 0, width, 0, 0, width, height)
|
||||
return this
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewQRCode() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<paths>
|
||||
<files-path name="my_files" path="/"/>
|
||||
<files-path name="app_temp" path="../app_temp"/>
|
||||
</paths>
|
||||
|
||||
Reference in New Issue
Block a user