Support parsing QRCode data from image intent
This commit is contained in:
parent
fba591277d
commit
b27d3c91fc
12 changed files with 327 additions and 27 deletions
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
<data android:mimeType="text/*" />
|
<data android:mimeType="text/*" />
|
||||||
<data android:mimeType="message/*" />
|
<data android:mimeType="message/*" />
|
||||||
|
<data android:mimeType="image/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
package org.fnives.android.qrcodetransfer
|
package org.fnives.android.qrcodetransfer
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
|
@ -26,8 +29,10 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import org.fnives.android.qrcodetransfer.create.CreateQRCode
|
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.ReadQRCode
|
||||||
|
import org.fnives.android.qrcodetransfer.read.image.ImageReadQRCode
|
||||||
import org.fnives.android.qrcodetransfer.storage.LocalAppPreferencesProvider
|
import org.fnives.android.qrcodetransfer.storage.LocalAppPreferencesProvider
|
||||||
import org.fnives.android.qrcodetransfer.ui.theme.QRCodeTransferTheme
|
import org.fnives.android.qrcodetransfer.ui.theme.QRCodeTransferTheme
|
||||||
|
|
||||||
|
|
@ -37,14 +42,35 @@ class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
LocalAppPreferencesProvider(this) {
|
LocalAppPreferencesProvider(this) {
|
||||||
LocalIntentTextProvider(intent) {
|
LocalIntentProvider(intent) {
|
||||||
QRCodeTransferTheme {
|
QRCodeTransferTheme {
|
||||||
var writerSelected by rememberSaveable { mutableStateOf(true) }
|
|
||||||
// A surface container using the 'background' color from the theme
|
// A surface container using the 'background' color from the theme
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colors.background,
|
color = MaterialTheme.colors.background,
|
||||||
) {
|
) {
|
||||||
|
val intentImage = LocalIntentImageUri.current
|
||||||
|
if (intentImage != null) {
|
||||||
|
ImageReadQRCode(intentImage,
|
||||||
|
onErrorLoadingFile = {
|
||||||
|
showToast(R.string.could_not_read_content)
|
||||||
|
finishAfterTransition()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
NormalState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NormalState() {
|
||||||
|
var writerSelected by rememberSaveable { mutableStateOf(true) }
|
||||||
Scaffold(bottomBar = {
|
Scaffold(bottomBar = {
|
||||||
NavBar(
|
NavBar(
|
||||||
writerSelected = writerSelected,
|
writerSelected = writerSelected,
|
||||||
|
|
@ -60,12 +86,6 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -86,3 +106,11 @@ fun NavBar(writerSelected: Boolean, setWriterSelected: (Boolean) -> Unit) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Activity.showToast(@StringRes stringRes: Int) {
|
||||||
|
Toast.makeText(
|
||||||
|
this,
|
||||||
|
getString(stringRes),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
@ -37,11 +37,16 @@ fun BitMatrix.toBitmap(): Bitmap {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Bitmap.toBinaryBitmap(): BinaryBitmap {
|
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
|
//copy pixel data from the Bitmap into the 'intArray' array
|
||||||
val intArray = IntArray(width * height)
|
val intArray = IntArray(bitmap.width * bitmap.height)
|
||||||
getPixels(intArray, 0, width, 0, 0, width, 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))
|
return BinaryBitmap(HybridBinarizer(source))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Uri?> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,19 +15,34 @@ import org.fnives.android.qrcodetransfer.R
|
||||||
import org.fnives.android.qrcodetransfer.copyToClipboard
|
import org.fnives.android.qrcodetransfer.copyToClipboard
|
||||||
import org.fnives.android.qrcodetransfer.isMaybeLink
|
import org.fnives.android.qrcodetransfer.isMaybeLink
|
||||||
import org.fnives.android.qrcodetransfer.openLink
|
import org.fnives.android.qrcodetransfer.openLink
|
||||||
|
import org.fnives.android.qrcodetransfer.read.parsed.DataFormatter
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ActionRow(readState: ReadState?) {
|
fun ActionRow(readState: ReadState?) =
|
||||||
val context = LocalContext.current
|
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))
|
Spacer(Modifier.weight(1f))
|
||||||
Button(onClick = { context.copyToClipboard(readState?.currentText.orEmpty()) }) {
|
Button(onClick = { context.copyToClipboard(formatted) }) {
|
||||||
Text(stringResource(id = R.string.copy))
|
Text(stringResource(id = R.string.copy))
|
||||||
}
|
}
|
||||||
if (readState?.currentText?.isMaybeLink() == true) {
|
if (formatted != parsedContent) {
|
||||||
Spacer(Modifier.width(16.dp))
|
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))
|
Text(stringResource(id = R.string.open))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,10 @@ import androidx.compose.ui.unit.dp
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.PermissionStatus
|
import com.google.accompanist.permissions.PermissionStatus
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import java.time.Duration
|
|
||||||
import org.fnives.android.qrcodetransfer.R
|
import org.fnives.android.qrcodetransfer.R
|
||||||
import org.fnives.android.qrcodetransfer.SequenceProtocol
|
import org.fnives.android.qrcodetransfer.SequenceProtocol
|
||||||
import org.fnives.android.qrcodetransfer.create.Base64EncodeCheckbox
|
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.storage.LocalAppPreferences
|
||||||
import org.fnives.android.qrcodetransfer.toBinaryBitmap
|
import org.fnives.android.qrcodetransfer.toBinaryBitmap
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ fun QRCodeReader() {
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.heightIn(max = 128.dp)
|
.heightIn(max = 128.dp)
|
||||||
.verticalScroll(textScrollState),
|
.verticalScroll(textScrollState),
|
||||||
text = readState?.currentText.orEmpty(),
|
text = DataFormatter.receivedDataFormatter(readState?.currentText.orEmpty()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.fnives.android.qrcodetransfer.read.parsed
|
||||||
|
|
||||||
|
object DataFormatter {
|
||||||
|
|
||||||
|
fun receivedDataFormatter(data: String): String {
|
||||||
|
return WiFiInfoFormatter.tryToParse(data)?.format() ?: data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -13,9 +13,11 @@
|
||||||
<string name="allow">Allow</string>
|
<string name="allow">Allow</string>
|
||||||
<string name="content_read_partial">Content read so far:</string>
|
<string name="content_read_partial">Content read so far:</string>
|
||||||
<string name="content_read">Content read:</string>
|
<string name="content_read">Content read:</string>
|
||||||
|
<string name="could_not_read_content">Content could not be parsed.</string>
|
||||||
<string name="next_qr_code">Navigate to %1$s QR Code</string>
|
<string name="next_qr_code">Navigate to %1$s QR Code</string>
|
||||||
<string name="qr_code_content">QRCode Content</string>
|
<string name="qr_code_content">QRCode Content</string>
|
||||||
<string name="copy">Copy</string>
|
<string name="copy">Copy</string>
|
||||||
|
<string name="copy_raw">Copy Raw</string>
|
||||||
<string name="open">Open</string>
|
<string name="open">Open</string>
|
||||||
<string name="read_qr_code">Scan QR Code</string>
|
<string name="read_qr_code">Scan QR Code</string>
|
||||||
<string name="create_qr_code">Create QR Code</string>
|
<string name="create_qr_code">Create QR Code</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue