diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3668339..d3348c7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -32,6 +32,7 @@
+
diff --git a/app/src/main/java/org/fnives/android/qrcodetransfer/MainActivity.kt b/app/src/main/java/org/fnives/android/qrcodetransfer/MainActivity.kt
index b65677b..24e90f6 100644
--- a/app/src/main/java/org/fnives/android/qrcodetransfer/MainActivity.kt
+++ b/app/src/main/java/org/fnives/android/qrcodetransfer/MainActivity.kt
@@ -1,8 +1,11 @@
package org.fnives.android.qrcodetransfer
+import android.app.Activity
import android.os.Bundle
+import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -26,8 +29,10 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import org.fnives.android.qrcodetransfer.create.CreateQRCode
-import org.fnives.android.qrcodetransfer.intent.LocalIntentTextProvider
+import org.fnives.android.qrcodetransfer.intent.LocalIntentImageUri
+import org.fnives.android.qrcodetransfer.intent.LocalIntentProvider
import org.fnives.android.qrcodetransfer.read.ReadQRCode
+import org.fnives.android.qrcodetransfer.read.image.ImageReadQRCode
import org.fnives.android.qrcodetransfer.storage.LocalAppPreferencesProvider
import org.fnives.android.qrcodetransfer.ui.theme.QRCodeTransferTheme
@@ -37,28 +42,23 @@ class MainActivity : ComponentActivity() {
setContent {
LocalAppPreferencesProvider(this) {
- LocalIntentTextProvider(intent) {
+ LocalIntentProvider(intent) {
QRCodeTransferTheme {
- var writerSelected by rememberSaveable { mutableStateOf(true) }
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background,
) {
- Scaffold(bottomBar = {
- NavBar(
- writerSelected = writerSelected,
- setWriterSelected = { writerSelected = it })
- }) {
- Box(Modifier.padding(it)) {
- AnimatedContent(targetState = writerSelected) { showWriter ->
- if (showWriter) {
- CreateQRCode()
- } else {
- ReadQRCode()
- }
+ val intentImage = LocalIntentImageUri.current
+ if (intentImage != null) {
+ ImageReadQRCode(intentImage,
+ onErrorLoadingFile = {
+ showToast(R.string.could_not_read_content)
+ finishAfterTransition()
}
- }
+ )
+ } else {
+ NormalState()
}
}
}
@@ -68,6 +68,26 @@ class MainActivity : ComponentActivity() {
}
}
+@Composable
+fun NormalState() {
+ var writerSelected by rememberSaveable { mutableStateOf(true) }
+ Scaffold(bottomBar = {
+ NavBar(
+ writerSelected = writerSelected,
+ setWriterSelected = { writerSelected = it })
+ }) {
+ Box(Modifier.padding(it)) {
+ AnimatedContent(targetState = writerSelected) { showWriter ->
+ if (showWriter) {
+ CreateQRCode()
+ } else {
+ ReadQRCode()
+ }
+ }
+ }
+ }
+}
+
@Composable
fun NavBar(writerSelected: Boolean, setWriterSelected: (Boolean) -> Unit) {
BottomNavigation(Modifier.fillMaxWidth()) {
@@ -85,4 +105,12 @@ fun NavBar(writerSelected: Boolean, setWriterSelected: (Boolean) -> Unit) {
label = { Text(stringResource(id = R.string.read_qr_code)) },
)
}
+}
+
+private fun Activity.showToast(@StringRes stringRes: Int) {
+ Toast.makeText(
+ this,
+ getString(stringRes),
+ Toast.LENGTH_LONG
+ ).show()
}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/android/qrcodetransfer/Util.kt b/app/src/main/java/org/fnives/android/qrcodetransfer/Util.kt
index cbb87ad..fe791ae 100644
--- a/app/src/main/java/org/fnives/android/qrcodetransfer/Util.kt
+++ b/app/src/main/java/org/fnives/android/qrcodetransfer/Util.kt
@@ -37,11 +37,16 @@ fun BitMatrix.toBitmap(): Bitmap {
}
fun Bitmap.toBinaryBitmap(): BinaryBitmap {
+ val bitmap = if (this.config != Bitmap.Config.ARGB_8888) {
+ this.copy(Bitmap.Config.ARGB_8888, false)
+ } else {
+ this
+ }
//copy pixel data from the Bitmap into the 'intArray' array
- val intArray = IntArray(width * height)
- getPixels(intArray, 0, width, 0, 0, width, height)
+ val intArray = IntArray(bitmap.width * bitmap.height)
+ bitmap.getPixels(intArray, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
- val source: LuminanceSource = RGBLuminanceSource(width, height, intArray)
+ val source: LuminanceSource = RGBLuminanceSource(bitmap.width, bitmap.height, intArray)
return BinaryBitmap(HybridBinarizer(source))
}
diff --git a/app/src/main/java/org/fnives/android/qrcodetransfer/intent/LocalIntent.kt b/app/src/main/java/org/fnives/android/qrcodetransfer/intent/LocalIntent.kt
new file mode 100644
index 0000000..010bf2f
--- /dev/null
+++ b/app/src/main/java/org/fnives/android/qrcodetransfer/intent/LocalIntent.kt
@@ -0,0 +1,11 @@
+package org.fnives.android.qrcodetransfer.intent
+
+import android.content.Intent
+import androidx.compose.runtime.Composable
+
+@Composable
+fun LocalIntentProvider(intent: Intent?, content: @Composable () -> Unit) {
+ LocalIntentImageUriProvider(intent) {
+ LocalIntentTextProvider(intent, content)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/android/qrcodetransfer/intent/LocalIntentImageUri.kt b/app/src/main/java/org/fnives/android/qrcodetransfer/intent/LocalIntentImageUri.kt
new file mode 100644
index 0000000..a0c7aa1
--- /dev/null
+++ b/app/src/main/java/org/fnives/android/qrcodetransfer/intent/LocalIntentImageUri.kt
@@ -0,0 +1,26 @@
+package org.fnives.android.qrcodetransfer.intent
+
+import android.content.Intent
+import android.net.Uri
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.compositionLocalOf
+import androidx.core.content.IntentCompat
+
+val LocalIntentImageUri = compositionLocalOf {
+ error("CompositionLocal LocalIntentImageUri not present")
+}
+
+@Composable
+fun LocalIntentImageUriProvider(intent: Intent?, content: @Composable () -> Unit) {
+ CompositionLocalProvider(LocalIntentImageUri provides intent?.uri, content = content)
+}
+
+private val Intent.uri: Uri?
+ get() {
+ return try {
+ IntentCompat.getParcelableExtra(this, Intent.EXTRA_STREAM, Uri::class.java)
+ } catch (ignore: Throwable) {
+ null
+ }
+ }
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/android/qrcodetransfer/read/ActionRow.kt b/app/src/main/java/org/fnives/android/qrcodetransfer/read/ActionRow.kt
index 9204b9c..0cbe69e 100644
--- a/app/src/main/java/org/fnives/android/qrcodetransfer/read/ActionRow.kt
+++ b/app/src/main/java/org/fnives/android/qrcodetransfer/read/ActionRow.kt
@@ -15,19 +15,34 @@ import org.fnives.android.qrcodetransfer.R
import org.fnives.android.qrcodetransfer.copyToClipboard
import org.fnives.android.qrcodetransfer.isMaybeLink
import org.fnives.android.qrcodetransfer.openLink
+import org.fnives.android.qrcodetransfer.read.parsed.DataFormatter
@Composable
-fun ActionRow(readState: ReadState?) {
- val context = LocalContext.current
+fun ActionRow(readState: ReadState?) =
+ ActionRow(
+ parsedContent = readState?.currentText.orEmpty(),
+ show = readState?.smallestMissingIndex == null
+ )
- Row(Modifier.alpha(if (readState?.smallestMissingIndex == null) 1f else 0f)) {
+@Composable
+fun ActionRow(parsedContent: String, show: Boolean = true) {
+ val context = LocalContext.current
+ val formatted = DataFormatter.receivedDataFormatter(parsedContent)
+
+ Row(Modifier.alpha(if (show) 1f else 0f)) {
Spacer(Modifier.weight(1f))
- Button(onClick = { context.copyToClipboard(readState?.currentText.orEmpty()) }) {
+ Button(onClick = { context.copyToClipboard(formatted) }) {
Text(stringResource(id = R.string.copy))
}
- if (readState?.currentText?.isMaybeLink() == true) {
+ if (formatted != parsedContent) {
Spacer(Modifier.width(16.dp))
- Button(onClick = { context.openLink(readState.currentText.orEmpty()) }) {
+ Button(onClick = { context.copyToClipboard(parsedContent) }) {
+ Text(stringResource(id = R.string.copy_raw))
+ }
+ }
+ if (formatted.isMaybeLink()) {
+ Spacer(Modifier.width(16.dp))
+ Button(onClick = { context.openLink(formatted) }) {
Text(stringResource(id = R.string.open))
}
}
diff --git a/app/src/main/java/org/fnives/android/qrcodetransfer/read/ReadQRCode.kt b/app/src/main/java/org/fnives/android/qrcodetransfer/read/ReadQRCode.kt
index 71c525c..7467b1b 100644
--- a/app/src/main/java/org/fnives/android/qrcodetransfer/read/ReadQRCode.kt
+++ b/app/src/main/java/org/fnives/android/qrcodetransfer/read/ReadQRCode.kt
@@ -28,10 +28,10 @@ import androidx.compose.ui.unit.dp
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus
import com.google.accompanist.permissions.rememberPermissionState
-import java.time.Duration
import org.fnives.android.qrcodetransfer.R
import org.fnives.android.qrcodetransfer.SequenceProtocol
import org.fnives.android.qrcodetransfer.create.Base64EncodeCheckbox
+import org.fnives.android.qrcodetransfer.read.parsed.DataFormatter
import org.fnives.android.qrcodetransfer.storage.LocalAppPreferences
import org.fnives.android.qrcodetransfer.toBinaryBitmap
@@ -88,7 +88,7 @@ fun QRCodeReader() {
modifier = Modifier
.heightIn(max = 128.dp)
.verticalScroll(textScrollState),
- text = readState?.currentText.orEmpty(),
+ text = DataFormatter.receivedDataFormatter(readState?.currentText.orEmpty()),
)
}
diff --git a/app/src/main/java/org/fnives/android/qrcodetransfer/read/image/BitmapFromImageUri.kt b/app/src/main/java/org/fnives/android/qrcodetransfer/read/image/BitmapFromImageUri.kt
new file mode 100644
index 0000000..c8b0a39
--- /dev/null
+++ b/app/src/main/java/org/fnives/android/qrcodetransfer/read/image/BitmapFromImageUri.kt
@@ -0,0 +1,29 @@
+package org.fnives.android.qrcodetransfer.read.image
+
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.net.Uri
+import android.os.Build
+import android.provider.MediaStore
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+
+@Composable
+fun Uri.toBitmap(): Bitmap? {
+ val context = LocalContext.current
+
+ return remember(this) {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, this))
+ } else {
+ @Suppress("DEPRECATION")
+ MediaStore.Images.Media.getBitmap(context.contentResolver, this)
+ }
+
+ } catch (ignore: Throwable) {
+ null
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/android/qrcodetransfer/read/image/ImageReadQRCode.kt b/app/src/main/java/org/fnives/android/qrcodetransfer/read/image/ImageReadQRCode.kt
new file mode 100644
index 0000000..09b1c2a
--- /dev/null
+++ b/app/src/main/java/org/fnives/android/qrcodetransfer/read/image/ImageReadQRCode.kt
@@ -0,0 +1,113 @@
+package org.fnives.android.qrcodetransfer.read.image
+
+import android.net.Uri
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.LocalTextStyle
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.TextUnitType
+import androidx.compose.ui.unit.dp
+import org.fnives.android.qrcodetransfer.BuildConfig
+import org.fnives.android.qrcodetransfer.R
+import org.fnives.android.qrcodetransfer.SequenceProtocol
+import org.fnives.android.qrcodetransfer.create.Base64EncodeCheckbox
+import org.fnives.android.qrcodetransfer.read.ActionRow
+import org.fnives.android.qrcodetransfer.read.parsed.DataFormatter
+import org.fnives.android.qrcodetransfer.storage.LocalAppPreferences
+import org.fnives.android.qrcodetransfer.toBinaryBitmap
+
+
+@Composable
+fun ImageReadQRCode(imageUri: Uri, onErrorLoadingFile: () -> Unit) {
+ val imageBitmap = imageUri.toBitmap()
+ LaunchedEffect(imageBitmap) {
+ if (imageBitmap == null) {
+ onErrorLoadingFile()
+ }
+ }
+ if (imageBitmap == null) {
+ return
+ }
+
+ val appPreferences = LocalAppPreferences.current
+ val encodeBase64 by appPreferences.encodeBase64.collectAsState(initial = SequenceProtocol.encodeBase64)
+ val imageParsedContent = remember(imageUri, encodeBase64) {
+ try {
+ SequenceProtocol.read(imageBitmap.toBinaryBitmap())?.sequenceInfo?.content
+ } catch (ignored: Throwable) {
+ if (BuildConfig.DEBUG) {
+ ignored.printStackTrace()
+ }
+ null
+ }
+ }
+ val textScrollState = rememberScrollState()
+
+ Column {
+ Box(
+ Modifier
+ .weight(1f)
+ .fillMaxWidth(),
+ ) {
+ Image(
+ modifier = Modifier.fillMaxSize(),
+ bitmap = imageBitmap.asImageBitmap(),
+ contentScale = ContentScale.Fit,
+ contentDescription = null
+ )
+ }
+ Column {
+ Base64EncodeCheckbox(encode = encodeBase64, setEncode = {
+ SequenceProtocol.encodeBase64 = it
+ appPreferences.setEncodeBase64(SequenceProtocol.encodeBase64)
+ })
+ Column(
+ Modifier.padding(24.dp)
+ ) {
+ if (imageParsedContent == null) {
+ Text(
+ text = stringResource(id = R.string.could_not_read_content),
+ style = LocalTextStyle.current.copy(fontSize = TextUnit(16f, TextUnitType.Sp))
+ )
+ } else {
+ Text(
+ text = stringResource(id = R.string.content_read),
+ style = LocalTextStyle.current.copy(fontSize = TextUnit(12f, TextUnitType.Sp))
+ )
+
+ Spacer(Modifier.height(4.dp))
+ SelectionContainer {
+ Text(
+ modifier = Modifier
+ .heightIn(max = 128.dp)
+ .verticalScroll(textScrollState),
+ text = DataFormatter.receivedDataFormatter(imageParsedContent),
+ )
+ }
+
+ ActionRow(imageParsedContent)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/android/qrcodetransfer/read/parsed/DataFormatter.kt b/app/src/main/java/org/fnives/android/qrcodetransfer/read/parsed/DataFormatter.kt
new file mode 100644
index 0000000..6d2b421
--- /dev/null
+++ b/app/src/main/java/org/fnives/android/qrcodetransfer/read/parsed/DataFormatter.kt
@@ -0,0 +1,8 @@
+package org.fnives.android.qrcodetransfer.read.parsed
+
+object DataFormatter {
+
+ fun receivedDataFormatter(data: String): String {
+ return WiFiInfoFormatter.tryToParse(data)?.format() ?: data
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/android/qrcodetransfer/read/parsed/WiFiInfoFormatter.kt b/app/src/main/java/org/fnives/android/qrcodetransfer/read/parsed/WiFiInfoFormatter.kt
new file mode 100644
index 0000000..d2ce797
--- /dev/null
+++ b/app/src/main/java/org/fnives/android/qrcodetransfer/read/parsed/WiFiInfoFormatter.kt
@@ -0,0 +1,62 @@
+package org.fnives.android.qrcodetransfer.read.parsed
+
+import org.fnives.android.qrcodetransfer.BuildConfig
+
+object WiFiInfoFormatter {
+
+ private const val PREFIX = "WIFI:"
+ private const val EXTRA_KEY = "EXTRA"
+ private const val NAME_KEY = "S"
+ private const val PASSWORD_KEY = "P"
+ private const val SECURITY_KEY = "T"
+ private const val HIDDEN_KEY = "H"
+
+ fun tryToParse(data: String): WifiInfo? {
+ if (data.startsWith(PREFIX)) {
+ try {
+ val result = data.drop(PREFIX.length).split(";").map {
+ if (it.contains(":")) {
+ val (key, value) = it.split(":")
+ key to value
+ } else {
+ EXTRA_KEY to it
+ }
+ }.toMap()
+
+ return WifiInfo(
+ name = result[NAME_KEY]
+ ?: throw IllegalArgumentException("Could not find name"),
+ password = result[PASSWORD_KEY]
+ ?: throw IllegalArgumentException("Could not find password"),
+ security = result[SECURITY_KEY].orEmpty(),
+ extra = result[EXTRA_KEY].orEmpty(),
+ hidden = result[HIDDEN_KEY] == "true",
+ )
+ } catch (ignored: Throwable) {
+ if (BuildConfig.DEBUG) {
+ ignored.printStackTrace()
+ }
+ return null
+ }
+ } else {
+ return null
+ }
+ }
+}
+
+data class WifiInfo(
+ val name: String,
+ val security: String,
+ val password: String,
+ val hidden: Boolean,
+ val extra: String,
+)
+
+fun WifiInfo.format(): String =
+ """
+name: $name
+password: $password
+hidden: $hidden
+security: $security
+extra: $extra
+ """.trimIndent()
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2dd2872..6178517 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -13,9 +13,11 @@
Allow
Content read so far:
Content read:
+ Content could not be parsed.
Navigate to %1$s QR Code
QRCode Content
Copy
+ Copy Raw
Open
Scan QR Code
Create QR Code