Compare commits

...
4 Commits
Author SHA1 Message Date
Markus Fisch 40a6a77b8d Advance version number to 1.55.0 2022-10-30 14:21:01 +01:00
Markus Fisch 301a33a176 Update to latest ZXing C++ wrapper 2022-10-30 14:17:18 +01:00
GiovanniandGitHub 8d6623786c Update an Italian string 2022-10-27 22:59:07 +02:00
Markus Fisch a225eab6f2 Make recreated barcode interactive
This way the barcode can easily be exported, too.

Also live update the data views and clean up the DecodeFragment.
2022-10-26 21:45:06 +02:00
6 changed files with 203 additions and 146 deletions
+5
View File
@@ -1,5 +1,10 @@
# Change Log
## 1.55.0
* Make recreated barcode interactive
* Update to latest ZXing C++
* Update Italian translation
## 1.54.1
* Update Russian translation
* Update Turkish translation
+3 -3
View File
@@ -9,8 +9,8 @@ android {
minSdkVersion 9
targetSdkVersion sdk_version
versionCode 104
versionName '1.54.1'
versionCode 105
versionName '1.55.0'
}
signingConfigs {
@@ -66,5 +66,5 @@ dependencies {
implementation "com.android.support:preference-v14:$support_version"
implementation 'com.github.markusfisch:CameraView:1.9.1'
implementation 'com.github.markusfisch:ScalingImageView:1.4.0'
implementation 'com.github.markusfisch:zxing-cpp:v1.4.0.4'
implementation 'com.github.markusfisch:zxing-cpp:v1.4.0.6'
}
@@ -7,6 +7,7 @@ import android.os.Parcelable
import android.text.format.DateFormat
import de.markusfisch.android.zxingcpp.ZxingCpp
import de.markusfisch.android.zxingcpp.ZxingCpp.ContentType
import de.markusfisch.android.zxingcpp.ZxingCpp.Format
import de.markusfisch.android.zxingcpp.ZxingCpp.Result
data class Scan(
@@ -152,22 +153,13 @@ fun Result.toScan(): Scan {
)
}
fun Scan.recreate(
size: Int = 128,
margin: Int = -1
): Bitmap? {
if (content.isEmpty()) {
return null
}
val ecLevel = when (errorCorrectionLevel) {
"L" -> 0
"M" -> 1
"Q" -> 2
"H" -> 3
else -> -1
}
return try {
val format = ZxingCpp.Format.valueOf(format)
data class Recreation(
val format: Format,
val ecLevel: Int,
val size: Int,
val margin: Int
) {
fun encode(content: String): Bitmap? = try {
ZxingCpp.encodeAsBitmap(
content, format, size, size, margin, ecLevel
)
@@ -176,6 +168,26 @@ fun Scan.recreate(
}
}
fun Scan.toRecreation(
size: Int = 128,
margin: Int = -1
) = if (content.isEmpty()) {
null
} else {
Recreation(
Format.valueOf(format),
when (errorCorrectionLevel) {
"L" -> 0
"M" -> 1
"Q" -> 2
"H" -> 3
else -> -1
},
size,
margin
)
}
private fun getDateTime(
time: Long = System.currentTimeMillis()
) = DateFormat.format(
@@ -12,6 +12,7 @@ import android.view.*
import android.widget.*
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.ActionRegistry
import de.markusfisch.android.binaryeye.actions.IAction
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
import de.markusfisch.android.binaryeye.actions.wifi.WifiConnector
import de.markusfisch.android.binaryeye.activity.MainActivity
@@ -20,8 +21,9 @@ import de.markusfisch.android.binaryeye.app.*
import de.markusfisch.android.binaryeye.content.copyToClipboard
import de.markusfisch.android.binaryeye.content.shareText
import de.markusfisch.android.binaryeye.content.toHexString
import de.markusfisch.android.binaryeye.database.Recreation
import de.markusfisch.android.binaryeye.database.Scan
import de.markusfisch.android.binaryeye.database.recreate
import de.markusfisch.android.binaryeye.database.toRecreation
import de.markusfisch.android.binaryeye.io.askForFileName
import de.markusfisch.android.binaryeye.io.toSaveResult
import de.markusfisch.android.binaryeye.io.writeExternalFile
@@ -36,9 +38,10 @@ class DecodeFragment : Fragment() {
private lateinit var dataView: TableLayout
private lateinit var metaView: TableLayout
private lateinit var hexView: TextView
private lateinit var format: String
private lateinit var stampView: TextView
private lateinit var recreationView: ImageView
private lateinit var fab: FloatingActionButton
private lateinit var format: String
private val parentJob = Job()
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main + parentJob)
@@ -50,8 +53,9 @@ class DecodeFragment : Fragment() {
private var closeAutomatically = false
private var action = ActionRegistry.DEFAULT_ACTION
private var isBinary = false
private var raw: ByteArray = ByteArray(0)
private var originalBytes: ByteArray = ByteArray(0)
private var id = 0L
private var recreation: Recreation? = null
override fun onCreate(state: Bundle?) {
super.onCreate(state)
@@ -80,95 +84,36 @@ class DecodeFragment : Fragment() {
@Suppress("DEPRECATION")
arguments?.getParcelable(SCAN) as Scan?
} ?: throw IllegalArgumentException("DecodeFragment needs a Scan")
val originalContent = scan.content
isBinary = scan.raw != null
originalBytes = scan.raw ?: originalContent.toByteArray()
format = scan.format
id = scan.id
val inputContent = scan.content
isBinary = scan.raw != null
raw = scan.raw ?: inputContent.toByteArray()
format = scan.format
contentView = view.findViewById(R.id.content)
stampView = view.findViewById(R.id.stamp)
fab = view.findViewById(R.id.open)
if (!isBinary) {
contentView.setText(inputContent)
contentView.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
updateViewsAndAction(content.toByteArray())
}
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(
s: CharSequence?,
start: Int,
before: Int,
count: Int
) {
}
})
fab.setOnClickListener {
executeAction(content.toByteArray())
}
if (prefs.openImmediately) {
executeAction(content.toByteArray())
}
} else {
contentView.setText(String(raw).foldNonAlNum())
contentView.isEnabled = false
fab.setImageResource(R.drawable.ic_action_save)
fab.setOnClickListener {
askForFileNameAndSave(raw)
}
}
val trackingLink = generateDpTrackingLink(raw, scan.format)
if (trackingLink != null) {
stampView.apply {
text = trackingLink.fromHtml()
isClickable = true
movementMethod = LinkMovementMethod.getInstance()
}
} else {
stampView.visibility = View.GONE
}
formatView = view.findViewById(R.id.format)
dataView = view.findViewById(R.id.data)
metaView = view.findViewById(R.id.meta)
hexView = view.findViewById(R.id.hex)
stampView = view.findViewById(R.id.stamp)
recreationView = view.findViewById(R.id.recreation)
fab = view.findViewById(R.id.open)
updateViewsAndAction(raw)
if (!isBinary) {
fillDataView(dataView, inputContent)
}
initContentAndFab(originalContent)
if (prefs.showMetaData) {
fillMetaView(metaView, scan)
metaView.fillMetaView(scan)
} else {
metaView.visibility = View.GONE
}
if (prefs.showRecreation) {
recreation = scan.toRecreation(
(200f * dp).roundToInt()
)
}
view.findViewById<ImageView>(R.id.recreation).apply {
scope.launch(Dispatchers.IO) {
val bitmap = if (prefs.showRecreation) {
scan.recreate((200f * dp).roundToInt())
} else null
withContext(Dispatchers.Main) {
if (bitmap != null) {
setImageBitmap(bitmap)
} else {
visibility = View.GONE
}
}
}
}
updateViewsAndFab(originalContent, originalBytes)
(view.findViewById(R.id.inset_layout) as View).setPaddingFromWindowInsets()
(view.findViewById(R.id.scroll_view) as View).setPaddingFromWindowInsets()
@@ -181,35 +126,104 @@ class DecodeFragment : Fragment() {
parentJob.cancel()
}
private fun updateViewsAndAction(bytes: ByteArray) {
private fun initContentAndFab(originalContent: String) {
if (isBinary) {
contentView.setText(String(originalBytes).foldNonAlNum())
contentView.isEnabled = false
fab.setImageResource(R.drawable.ic_action_save)
fab.setOnClickListener {
askForFileNameAndSave(originalBytes)
}
} else {
contentView.setText(originalContent)
contentView.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
updateViewsAndFab(s?.toString() ?: "")
}
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) = Unit
override fun onTextChanged(
s: CharSequence?,
start: Int,
before: Int,
count: Int
) = Unit
})
fab.setOnClickListener {
executeAction(content)
}
if (prefs.openImmediately) {
executeAction(content)
}
}
}
private fun updateViewsAndFab(text: String, bytes: ByteArray? = null) {
val b = bytes ?: text.toByteArray()
resolveActionAndUpdateFab(b)
updateViews(text, b)
}
private fun resolveActionAndUpdateFab(bytes: ByteArray) {
val prevAction = action
if (!prevAction.canExecuteOn(bytes)) {
action = ActionRegistry.getAction(bytes)
}
if (prevAction !== action) {
updateFab(action)
}
}
private fun updateFab(action: IAction) {
fab.setImageResource(action.iconResId)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
fab.setOnLongClickListener { v ->
v.context.toast(action.titleResId)
true
}
} else {
fab.tooltipText = getString(action.titleResId)
}
}
private fun updateViews(text: String, bytes: ByteArray) {
formatView.text = resources.getQuantityString(
R.plurals.barcode_info,
bytes.size,
prettifyFormatName(format),
bytes.size
)
hexView.text = if (prefs.showHexDump) hexDump(bytes) else ""
if (prevAction !== action) {
fab.setImageResource(action.iconResId)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
fab.setOnLongClickListener { v ->
v.context.toast(action.titleResId)
true
}
} else {
fab.tooltipText = getString(action.titleResId)
hexView.showIf(prefs.showHexDump) { v ->
v.text = hexDump(bytes)
}
recreationView.showIf(prefs.showRecreation) { v ->
val r = recreation ?: return@showIf
v.setImageBitmap(r.encode(text))
v.setOnClickListener {
fragmentManager?.addFragment(
BarcodeFragment.newInstance(
text,
r.format,
r.size,
r.ecLevel
)
)
}
}
dataView.fillDataView(text)
stampView.setTrackingLink(bytes, format)
}
private fun fillDataView(tableLayout: TableLayout, content: String) {
private fun TableLayout.fillDataView(text: String) {
val items = LinkedHashMap<Int, String?>()
if (action is WifiAction) {
WifiConnector.parseMap(content)?.let { wifiData ->
WifiConnector.parseMap(text)?.let { wifiData ->
items.putAll(
linkedMapOf(
R.string.entry_type to getString(R.string.wifi_network),
@@ -225,10 +239,10 @@ class DecodeFragment : Fragment() {
)
}
}
fillDataTable(tableLayout, items)
fill(items)
}
private fun fillMetaView(tableLayout: TableLayout, scan: Scan) {
private fun TableLayout.fillMetaView(scan: Scan) {
val items = linkedMapOf(
R.string.error_correction_level to scan.errorCorrectionLevel,
R.string.sequence_size to scan.sequenceSize.positiveToString(),
@@ -255,32 +269,31 @@ class DecodeFragment : Fragment() {
)
)
}
fillDataTable(tableLayout, items)
fill(items)
}
private fun fillDataTable(
tableLayout: TableLayout,
private fun TableLayout.fill(
items: LinkedHashMap<Int, String?>
) {
if (items.isEmpty()) {
tableLayout.visibility = View.GONE
return
}
val ctx = tableLayout.context
val spaceBetween = (16f * dp).roundToInt()
items.forEach { item ->
val text = item.value
if (!text.isNullOrBlank()) {
val tr = TableRow(ctx)
val keyView = TextView(ctx)
keyView.setText(item.key)
val valueView = TextView(ctx)
valueView.setPadding(spaceBetween, 0, 0, 0)
valueView.text = text
tr.addView(keyView)
tr.addView(valueView)
tableLayout.addView(tr)
removeAllViews()
visibility = if (items.isEmpty()) View.GONE else {
val ctx = context
val spaceBetween = (16f * dp).roundToInt()
items.forEach { item ->
val text = item.value
if (!text.isNullOrBlank()) {
val tr = TableRow(ctx)
val keyView = TextView(ctx)
keyView.setText(item.key)
val valueView = TextView(ctx)
valueView.setPadding(spaceBetween, 0, 0, 0)
valueView.text = text
tr.addView(keyView)
tr.addView(valueView)
addView(tr)
}
}
View.VISIBLE
}
}
@@ -333,7 +346,7 @@ class DecodeFragment : Fragment() {
}
private fun textOrHex() = if (isBinary) {
raw.toHexString()
originalBytes.toHexString()
} else {
content
}
@@ -372,7 +385,7 @@ class DecodeFragment : Fragment() {
}
}
private fun executeAction(content: ByteArray) {
private fun executeAction(content: String) {
val ac = activity ?: return
if (content.isNotEmpty()) {
if (action is WifiAction &&
@@ -382,7 +395,7 @@ class DecodeFragment : Fragment() {
return
}
scope.launch {
action.execute(ac, content)
action.execute(ac, content.toByteArray())
maybeBackOrFinish()
}
}
@@ -425,7 +438,17 @@ class DecodeFragment : Fragment() {
}
}
private fun Int.positiveToString() = if (this > -1) this.toString() else ""
private inline fun <T : View> T.showIf(
visible: Boolean,
block: (T) -> Unit
) {
visibility = if (visible) {
block.invoke(this)
View.VISIBLE
} else {
View.GONE
}
}
private val nonAlNum = "[^a-zA-Z0-9]".toRegex()
private val multipleDots = "[…]+".toRegex()
@@ -471,18 +494,31 @@ private fun hexDump(bytes: ByteArray, charsPerLine: Int = 33): String {
return dump.toString()
}
private fun generateDpTrackingLink(raw: ByteArray, format: String): String? {
private fun Int.positiveToString() = if (this > -1) this.toString() else ""
private fun TextView.setTrackingLink(bytes: ByteArray, format: String) {
val trackingLink = generateDpTrackingLink(bytes, format)
if (trackingLink != null) {
text = trackingLink.fromHtml()
isClickable = true
movementMethod = LinkMovementMethod.getInstance()
} else {
visibility = View.GONE
}
}
private fun generateDpTrackingLink(bytes: ByteArray, format: String): String? {
// Check for Deutsche Post Matrixcode stamp.
var isStamp = false
var rawData = raw
var rawData = bytes
if (format == "DATA_MATRIX" &&
raw.toString(Charsets.ISO_8859_1).startsWith("DEA5")
bytes.toString(Charsets.ISO_8859_1).startsWith("DEA5")
) {
if (raw.size == 47) {
if (bytes.size == 47) {
isStamp = true
} else if (raw.size > 47) {
} else if (bytes.size > 47) {
// Transform back to original data.
rawData = raw.toString(Charsets.UTF_8).toByteArray(
rawData = bytes.toString(Charsets.UTF_8).toByteArray(
Charsets.ISO_8859_1
)
if (rawData.size == 47) {
+1 -1
View File
@@ -110,7 +110,7 @@
<string name="send_type_post_form">POST application/x-www-form-urlencoded</string>
<string name="send_type_post_json">POST application/json</string>
<string name="send_type_external_browser">Apri nel browser esterno</string>
<string name="test_url">URL di test</string>
<string name="test_url">Prova URL</string>
<string name="really_remove_scan">Vuoi veramente rimuovere la scansione?</string>
<string name="really_remove_all_scans">Vuoi veramente rimuovere tutte le scansioni?</string>
<string name="really_remove_selected_scans">Vuoi veramente rimuovere le scansioni selezionate?</string>
@@ -0,0 +1,4 @@
* Make recreated barcode interactive
* Update to latest ZXing C++
* Update Italian translation