Compare commits

...
30 Commits
Author SHA1 Message Date
Markus Fisch c9c1b2f5bf Advance version number to 1.65.1 2025-03-12 12:23:43 +01:00
Markus Fisch 1dbc0f0fa1 Fix fastlane changelogs
And insert the missing log for version code 141.
2025-03-12 12:20:20 +01:00
solokotandGitHub ab8170ee72 Update Russian translation
Fix machine translation
2025-03-12 12:11:05 +01:00
Markus Fisch 551464dc6f Refactor for an early return when content is empty
Easier to read and less indent.
2025-03-12 00:54:59 +01:00
Markus Fisch c3aac0a1e1 Set a default search URL for simple mode
So any (otherwise unregistered) content is always redirected to
the browser.
2025-03-12 00:53:37 +01:00
Markus Fisch d678e1cfd0 Hide keyboard when expanding recreated barcode
So the layout of `BarcodeFragment` isn't affected by it.
2025-03-12 00:49:30 +01:00
Markus Fisch a445ade877 Reduce height for 1D barcodes
1D barcodes don't need as much vertical space.
2025-03-12 00:48:20 +01:00
Markus Fisch 1fd0b7637f Add execute argument for binaryeye://encode
For immediate code generation.
2025-03-12 00:37:47 +01:00
Markus Fisch fdc88df897 Remember quiet zone settings
And use a quiet zone by default because it's recommended.
2025-03-12 00:19:41 +01:00
Markus Fisch d0f133ae00 Fix showing a scan from history
When `openImmediately` is `true`.

Of course, the action should only be executed when we're coming
from the scanner, not the history.
2025-03-12 00:11:14 +01:00
Markus Fisch 67646261e7 Remove unnecessary id field 2025-03-12 00:08:11 +01:00
Markus Fisch faeae9e904 Advance version number to 1.65.0 2025-03-11 23:23:55 +01:00
Markus Fisch 77b21a9e83 Add a welcome screen to choose a mode
To address these bad reviews when people don't understand that
they have to press this little green button.
2025-03-11 23:17:36 +01:00
Markus Fisch cbe7860a3d Add a deep link to encode a barcode 2025-03-11 22:58:45 +01:00
Markus Fisch fc29267d3d Update ZXing-C++ library 2025-03-11 22:58:10 +01:00
Markus Fisch 890506328b Fix show crop handle setting
This switch isn't part of the crop configuration, but controls if
the crop handle is actually visible or not.
2025-03-11 22:58:10 +01:00
Markus Fisch 04036b6cd6 Use apply scope function to initialize views
And remove all this repetition of names.
2025-03-11 22:58:10 +01:00
Markus Fisch 2602021c1c Remove unused import from PickActivity 2025-03-11 22:58:10 +01:00
Markus Fisch 17a0bdc0a9 Allow shrinking of image samples too
So they can fit in the crop window.
2025-03-11 22:58:10 +01:00
Markus Fisch d71a220eca Remember zoom level of barcodes in view
Solves #490.
2025-03-11 22:58:10 +01:00
Markus Fisch 0c2e711d79 Add a comment why Scan.text can be an empty string
So it's immediately clear why `Scan.text` can be empty.
2025-03-11 22:58:10 +01:00
Markus Fisch ca342a83d3 Configure ZXingCpp to return plain text only
And remove the HRI decoration because it's confusing people and
doesn't work well when the barcode is re-generated from this
content.

Alternatively, we could filter the HRI decoration for generation,
but that's probably overkill.

Solves #506.
2025-03-11 22:58:10 +01:00
Markus Fisch 3c44ff56eb Refactor SCAN_CONTENT to SCAN_TEXT
To match the refactoring of `Scan.text` in 5189fc.
2025-03-11 22:58:10 +01:00
bovirusandGitHub bcae64c0d0 Italian language update 2025-03-11 22:57:50 +01:00
Markus Fisch c2f673729b Advance version number to 1.64.2 2025-03-10 21:23:52 +01:00
Markus Fisch 2043e0f349 Use separate crop handle config for image scanning
These are two different use cases.
2025-03-10 21:11:41 +01:00
Markus Fisch b93eb5963e Use raw content for Barcode<*> when text is empty
So binary content is supported here, too.
2025-03-10 11:01:15 +01:00
Markus Fisch 5189fc9c5a Refactor Scan.content to Scan.text
Because `text` is more specific, and the term `content` is often
used for generics.
2025-03-10 10:54:44 +01:00
Markus Fisch 2d311ff92a Fix initializing dataMask
And show this item for QR Codes only.
2025-03-10 19:50:19 +01:00
solokotandGitHub 14bb08003d Update Russian translation 2025-03-10 10:31:50 +01:00
59 changed files with 705 additions and 180 deletions
+19
View File
@@ -1,5 +1,24 @@
# Change Log
## 1.65.1
* Improve simple mode
* Remember quiet zone settings
* Reduce height for 1D barcodes
* Add execute argument for binaryeye://encode
* Update Russian translation
## 1.65.0
* Add a welcome screen to choose a mode
* Add a deep link to encode a barcode
* Remember zoom level of generated barcodes
* Remove Human Readable Interpretation (HRI) decoration
* Update Italian translation
## 1.64.2
* Separate crop window settings for image scanning
* Fix initializing dataMask
* Update Russian translation
## 1.64.1
* Handle empty input for recreation
* Fix setting up database
+10
View File
@@ -28,6 +28,16 @@ testscan:
-c android.intent.category.BROWSABLE \
-d 'binaryeye://scan/?ret=http%3A%2F%2Fmarkusfisch.de%2F%3Fresult%3D{RESULT}'
testencode:
adb shell am start -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d 'binaryeye://encode?content=Test\&format=QRCode'
testencodeexec:
adb shell am start -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d 'binaryeye://encode?content=Test\&format=QRCode\&execute=true'
testurl:
adb shell am start -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
+15 -2
View File
@@ -88,9 +88,11 @@ ZXing can generate the following barcode formats:
### Deep Links
You can invoke Binary Eye with a web URI intent from anything that can
open URIs. There are two options:
open URIs.
1. [binaryeye://scan](binaryeye://scan) (note: Github Markdown does not render this as clickable link)
#### Decoding
1. [binaryeye://scan](binaryeye://scan) (note: GitHub does not render this as clickable link)
2. [http(s)://markusfisch.de/BinaryEye](http://markusfisch.de/BinaryEye)
If you want to get the scanned contents, you can add a `ret` query
@@ -104,6 +106,17 @@ Supported symbols are:
* `RESULT_BYTES` - raw result as a hex string
* `FORMAT` - barcode format
#### Encoding
1. [binaryeye://encode](binaryeye://encode)
You can use the URL arguments `content` and `format` to automatically
preset this data. For example:
[binaryeye://encode?content=Test&format=QRCODE](binaryeye://encode?content=Test&format=QRCODE)
If you want the code to be generated immediately, add `execute=true`.
### SCAN Intent
You can also use Binary Eye from other apps by using the
+3 -3
View File
@@ -9,8 +9,8 @@ android {
minSdk 9
targetSdk sdk_version
versionCode 143
versionName '1.64.1'
versionCode 146
versionName '1.65.1'
}
signingConfigs {
@@ -82,7 +82,7 @@ dependencies {
// External
implementation 'com.github.markusfisch:CameraView:1.10.0'
implementation 'com.github.markusfisch:ScalingImageView:1.4.1'
implementation 'com.github.markusfisch:zxing-cpp:v2.3.0.2'
implementation 'com.github.markusfisch:zxing-cpp:v2.3.0.3'
// Testing
testImplementation 'junit:junit:4.13.2'
+11 -1
View File
@@ -109,8 +109,18 @@
</intent-filter>
</activity>
<activity
android:name=".activity.MainActivity"
android:name=".activity.WelcomeActivity"
android:exported="true"/>
<activity
android:name=".activity.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="binaryeye" android:host="encode"/>
</intent-filter>
</activity>
<service
android:name=".service.ScanTileService"
android:icon="@drawable/ic_action_scan"
@@ -54,12 +54,14 @@ import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
import de.markusfisch.android.zxingcpp.ZxingCpp.Binarizer
import de.markusfisch.android.zxingcpp.ZxingCpp.ReaderOptions
import de.markusfisch.android.zxingcpp.ZxingCpp.Result
import de.markusfisch.android.zxingcpp.ZxingCpp.TextMode
import java.io.FileInputStream
import java.util.Scanner
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
class CameraActivity : AppCompatActivity() {
private val frameRoi = Rect()
private val matrix = Matrix()
@@ -149,12 +151,16 @@ class CameraActivity : AppCompatActivity() {
super.onDestroy()
fallbackBuffer = null
saveZoom()
detectorView.saveCropHandlePos()
detectorView.storeCropHandlePos(CAMERA_CROP_HANDLE)
releaseToneGenerators()
}
override fun onResume() {
super.onResume()
if (firstStart()) {
startActivity(Intent(this, WelcomeActivity::class.java))
return
}
System.gc()
updateHintsAndTitle()
if (prefs.bulkMode && bulkMode != prefs.bulkMode) {
@@ -172,6 +178,19 @@ class CameraActivity : AppCompatActivity() {
requestCameraPermission = false
}
private fun Context.firstStart(): Boolean {
val welcomeShownName = "welcome_shown"
if (prefs.preferences.getBoolean(welcomeShownName, false)) {
android.util.Log.d("mfdbg", "mfdbg: already welcome shown")
return false
}
prefs.preferences.edit().putBoolean(welcomeShownName, true).apply()
val packageInfo = packageManager.getPackageInfo(packageName, 0)
val installedSince = System.currentTimeMillis() - packageInfo.firstInstallTime
android.util.Log.d("mfdbg", "mfdbg: installed since is $installedSince")
return installedSince < 86400000
}
private fun updateHintsAndTitle() {
val restriction = restrictFormat
formatsToRead = if (restriction != null) {
@@ -522,7 +541,8 @@ class CameraActivity : AppCompatActivity() {
tryInvert = true,
tryDownscale = true,
returnCodabarStartEnd = true,
maxNumberOfSymbols = 1
maxNumberOfSymbols = 1,
textMode = TextMode.PLAIN
)
var useLocalAverage = false
camera.setPreviewCallback { frameData, _ ->
@@ -622,7 +642,7 @@ class CameraActivity : AppCompatActivity() {
updateFrameRoiAndMappingMatrix()
}
detectorView.setPaddingFromWindowInsets()
detectorView.restoreCropHandlePos()
detectorView.restoreCropHandlePos(CAMERA_CROP_HANDLE)
}
private fun updateFrameRoiAndMappingMatrix() {
@@ -722,6 +742,8 @@ class CameraActivity : AppCompatActivity() {
}
companion object {
const val CAMERA_CROP_HANDLE = "camera_crop_handle"
private const val PICK_FILE_RESULT_CODE = 1
private const val ZOOM_MAX = "zoom_max"
private const val ZOOM_LEVEL = "zoom_level"
@@ -750,7 +772,7 @@ fun Activity.showResult(
if (prefs.sendScanActive && prefs.sendScanUrl.isNotEmpty()) {
if (prefs.sendScanType == "4") {
openUrl(
prefs.sendScanUrl + scan.content.urlEncode()
prefs.sendScanUrl + scan.text.urlEncode()
)
return
}
@@ -3,6 +3,7 @@ package de.markusfisch.android.binaryeye.activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
@@ -80,6 +81,16 @@ class MainActivity : AppCompatActivity() {
return when {
intent.hasExtra(PREFERENCES) -> PreferencesFragment()
intent.hasExtra(HISTORY) -> HistoryFragment()
intent.dataString?.startsWith("binaryeye://encode") == true -> {
val uri = Uri.parse(intent.dataString)
EncodeFragment.newInstance(
uri.getQueryParameter("content"),
uri.getQueryParameter("format"),
!uri.getQueryParameter("execute").isNullOrEmpty()
)
}
intent.hasExtra(ENCODE) -> EncodeFragment.newInstance(
intent.getStringExtra(ENCODE)
)
@@ -86,20 +86,28 @@ class PickActivity : AppCompatActivity() {
return
}
cropImageView = findViewById(R.id.image) as CropImageView
cropImageView.restrictTranslation = false
cropImageView.freeRotation = prefs.freeRotation
cropImageView.setImageBitmap(bitmap)
cropImageView.onScan = {
scanWithinBounds(bitmap)
cropImageView = (findViewById(R.id.image) as CropImageView).apply {
restrictTranslation = false
freeRotation = prefs.freeRotation
setImageBitmap(bitmap)
onScan = {
scanWithinBounds(bitmap)
}
runAfterLayout = {
minWidth = min(
minWidth / 2f,
max(bitmap.width, bitmap.height).toFloat()
)
}
}
detectorView = findViewById(R.id.detector_view) as DetectorView
detectorView.onRoiChanged = {
scanWithinBounds(bitmap)
detectorView = (findViewById(R.id.detector_view) as DetectorView).apply {
onRoiChanged = {
scanWithinBounds(bitmap)
}
setPaddingFromWindowInsets()
restoreCropHandlePos(PICKER_CROP_HANDLE)
}
detectorView.setPaddingFromWindowInsets()
detectorView.restoreCropHandlePos()
findViewById(R.id.scan).setOnClickListener {
showResult()
@@ -195,7 +203,7 @@ class PickActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
detectorView.saveCropHandlePos()
detectorView.storeCropHandlePos(PICKER_CROP_HANDLE)
parentJob.cancel()
releaseToneGenerators()
}
@@ -259,6 +267,10 @@ class PickActivity : AppCompatActivity() {
applicationContext.toast(R.string.no_barcode_found)
}
}
companion object {
private const val PICKER_CROP_HANDLE = "picker_crop_handle"
}
}
private fun getNormalizedRoi(imageRect: RectF, roi: Rect): RectF {
@@ -0,0 +1,32 @@
package de.markusfisch.android.binaryeye.activity
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.prefs
class WelcomeActivity : AppCompatActivity() {
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setContentView(R.layout.activity_welcome)
findViewById(R.id.expert).setOnClickListener {
finish()
}
findViewById(R.id.simple).setOnClickListener {
prefs.apply {
showCropHandle = false
zoomBySwiping = false
beep = true
copyImmediately = true
openImmediately = true
showMetaData = false
showHexDump = false
defaultSearchUrl = "https://www.google.com/search?q="
openWithUrl = defaultSearchUrl
expandEscapeSequences = false
brightenScreen = true
}
finish()
}
}
}
@@ -20,7 +20,7 @@ class ScansAdapter(context: Context, cursor: Cursor) :
private val idIndex = cursor.getColumnIndex(Database.SCANS_ID)
private val timeIndex = cursor.getColumnIndex(Database.SCANS_DATETIME)
private val nameIndex = cursor.getColumnIndex(Database.SCANS_NAME)
private val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
private val textIndex = cursor.getColumnIndex(Database.SCANS_TEXT)
private val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
private val selections = mutableMapOf<Long, Int>()
private val selectedColor = ContextCompat.getColor(
@@ -74,7 +74,7 @@ class ScansAdapter(context: Context, cursor: Cursor) :
fun getContent(
position: Int
) = (getItem(position) as Cursor?)?.getString(contentIndex)
) = (getItem(position) as Cursor?)?.getString(textIndex)
override fun newView(
context: Context,
@@ -92,7 +92,7 @@ class ScansAdapter(context: Context, cursor: Cursor) :
getViewHolder(view).apply {
timeView.text = formatDateTime(cursor.getString(timeIndex))
val name = cursor.getString(nameIndex)
val content = cursor.getString(contentIndex)
val text = cursor.getString(textIndex)
var icon = 0
contentView.text = when {
name?.isNotEmpty() == true -> {
@@ -100,11 +100,11 @@ class ScansAdapter(context: Context, cursor: Cursor) :
name
}
content.isNullOrEmpty() -> context.getString(
text.isNullOrEmpty() -> context.getString(
R.string.binary_data
)
else -> content
else -> text
}
contentView.setCompoundDrawablesWithIntrinsicBounds(
icon, 0, 0, 0
@@ -24,7 +24,7 @@ fun Scan.sendBluetoothAsync(
connect(host, true)
}
val sent = if (connected) {
send(content, host, true)
send(text, host, true)
} else {
false
}
@@ -34,18 +34,23 @@ enum class BarcodeColors {
}
}
fun Scan.toBarcode() = if (symbol != null) {
BitMatrixBarcode(
symbol,
content,
format
)
} else {
ContentBarcode(
content,
format,
errorCorrectionLevel.toErrorCorrectionInt()
)
fun Scan.toBarcode(): Barcode<*> {
val content = text.ifEmpty {
raw
}
return if (symbol != null) {
BitMatrixBarcode(
symbol,
content,
format
)
} else {
ContentBarcode(
content,
format,
errorCorrectionLevel.toErrorCorrectionInt()
)
}
}
fun String?.toErrorCorrectionInt() = when (this) {
@@ -101,7 +106,7 @@ class ContentBarcode<T>(
colors: BarcodeColors = BarcodeColors.BLACK_ON_WHITE
) : Barcode<T>(content, format, colors) {
override fun toBitmap(size: Int) = ZxingCpp.encodeAsBitmap(
content, format, size, size, margin, ecLevel,
content, format, size, format.height(size), margin, ecLevel,
setColor = colors.foregroundColor(),
unsetColor = colors.backgroundColor()
)
@@ -172,3 +177,21 @@ private fun BitMatrix.inflate(
}
return scaled
}
private fun BarcodeFormat.height(size: Int) = when (this) {
// 1D barcodes don't need as much vertical space.
BarcodeFormat.CODABAR,
BarcodeFormat.CODE_39,
BarcodeFormat.CODE_93,
BarcodeFormat.CODE_128,
BarcodeFormat.DATA_BAR,
BarcodeFormat.DATA_BAR_EXPANDED,
BarcodeFormat.DX_FILM_EDGE,
BarcodeFormat.EAN_8,
BarcodeFormat.EAN_13,
BarcodeFormat.ITF,
BarcodeFormat.UPC_A,
BarcodeFormat.UPC_E -> size / 3
else -> size
}
@@ -33,7 +33,7 @@ private fun exportCsv(
Database.SCANS_DATETIME,
Database.SCANS_FORMAT,
Database.SCANS_NAME,
Database.SCANS_CONTENT,
Database.SCANS_TEXT,
Database.SCANS_ERROR_CORRECTION_LEVEL,
Database.SCANS_VERSION,
Database.SCANS_SEQUENCE_SIZE,
@@ -47,7 +47,7 @@ private fun exportCsv(
val indices = columns.map {
cursor.getColumnIndex(it)
}
val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
val contentIndex = cursor.getColumnIndex(Database.SCANS_TEXT)
val rawIndex = cursor.getColumnIndex(Database.SCANS_RAW)
outputStream.write(
columns.joinToString(
@@ -21,7 +21,7 @@ class Database {
$SCANS_ID,
$SCANS_DATETIME,
$SCANS_NAME,
$SCANS_CONTENT,
$SCANS_TEXT,
$SCANS_FORMAT
FROM $SCANS
${getWhereClause(query)}
@@ -34,7 +34,7 @@ class Database {
$SCANS_ID,
$SCANS_DATETIME,
$SCANS_NAME,
$SCANS_CONTENT,
$SCANS_TEXT,
$SCANS_RAW,
$SCANS_FORMAT,
$SCANS_ERROR_CORRECTION_LEVEL,
@@ -56,7 +56,7 @@ class Database {
query: String?,
prefix: String = "WHERE"
) = if (query?.isNotEmpty() == true) {
"""$prefix $SCANS_CONTENT LIKE ?
"""$prefix $SCANS_TEXT LIKE ?
OR $SCANS_NAME LIKE ?"""
} else {
""
@@ -76,7 +76,7 @@ class Database {
$SCANS_ID,
$SCANS_DATETIME,
$SCANS_NAME,
$SCANS_CONTENT,
$SCANS_TEXT,
$SCANS_RAW,
$SCANS_FORMAT,
$SCANS_ERROR_CORRECTION_LEVEL,
@@ -99,7 +99,7 @@ class Database {
$SCANS_ID,
$SCANS_DATETIME,
$SCANS_NAME,
$SCANS_CONTENT,
$SCANS_TEXT,
$SCANS_RAW,
$SCANS_FORMAT,
$SCANS_ERROR_CORRECTION_LEVEL,
@@ -121,7 +121,7 @@ class Database {
).use {
if (it.moveToFirst()) {
Scan(
it.getString(SCANS_CONTENT),
it.getString(SCANS_TEXT),
it.getBlob(SCANS_RAW),
BarcodeFormat.valueOf(it.getString(SCANS_FORMAT)),
it.getString(SCANS_ERROR_CORRECTION_LEVEL),
@@ -152,7 +152,7 @@ class Database {
when (prefs.ignoreDuplicates()) {
Preferences.Companion.IgnoreDuplicates.Consecutive -> {
val id = getIdOfLastScan(
scan.content,
scan.text,
scan.raw,
scan.format
)
@@ -162,7 +162,7 @@ class Database {
}
Preferences.Companion.IgnoreDuplicates.Any -> {
val id = getIdOfScanByContent(scan.content, scan.format)
val id = getIdOfScanByContent(scan.text, scan.format)
if (id > 0L) {
return id
}
@@ -175,7 +175,7 @@ class Database {
null,
ContentValues().apply {
put(SCANS_DATETIME, scan.dateTime)
put(SCANS_CONTENT, scan.content)
put(SCANS_TEXT, scan.text)
if (scan.raw != null) {
put(SCANS_RAW, scan.raw)
}
@@ -208,7 +208,7 @@ class Database {
): Long = db.rawQuery(
"""SELECT
$SCANS_ID,
$SCANS_CONTENT,
$SCANS_TEXT,
$SCANS_RAW,
$SCANS_FORMAT
FROM $SCANS
@@ -218,7 +218,7 @@ class Database {
).use {
if (it.count > 0 &&
it.moveToFirst() &&
it.getString(SCANS_CONTENT) == content &&
it.getString(SCANS_TEXT) == content &&
(raw == null || it.getBlob(SCANS_RAW)
?.contentEquals(raw) == true) &&
it.getString(SCANS_FORMAT) == format.name
@@ -236,7 +236,7 @@ class Database {
"""SELECT
$SCANS_ID
FROM $SCANS
WHERE $SCANS_CONTENT = ?
WHERE $SCANS_TEXT = ?
AND $SCANS_FORMAT = ?
LIMIT 1
""".trimMargin(), arrayOf(content, format.name)
@@ -303,7 +303,7 @@ class Database {
const val SCANS_ID = "_id"
const val SCANS_DATETIME = "_datetime"
const val SCANS_NAME = "name"
const val SCANS_CONTENT = "content"
const val SCANS_TEXT = "content"
const val SCANS_RAW = "raw"
const val SCANS_FORMAT = "format"
const val SCANS_ERROR_CORRECTION_LEVEL = "error_correction_level"
@@ -335,7 +335,7 @@ class Database {
$SCANS_ID INTEGER PRIMARY KEY AUTOINCREMENT,
$SCANS_DATETIME DATETIME NOT NULL,
$SCANS_NAME TEXT,
$SCANS_CONTENT TEXT NOT NULL,
$SCANS_TEXT TEXT NOT NULL,
$SCANS_RAW BLOB,
$SCANS_FORMAT TEXT NOT NULL,
$SCANS_ERROR_CORRECTION_LEVEL TEXT,
@@ -398,6 +398,7 @@ class Database {
private fun SQLiteDatabase.addDataMaskColumn() {
execSQL("ALTER TABLE $SCANS ADD $SCANS_DATA_MASK INTEGER")
execSQL("UPDATE $SCANS SET $SCANS_DATA_MASK = -1")
}
private fun SQLiteDatabase.addSymbolColumns() {
@@ -25,7 +25,7 @@ fun Cursor.exportJson(): String? {
Database.SCANS_DATETIME,
Database.SCANS_FORMAT,
Database.SCANS_NAME,
Database.SCANS_CONTENT,
Database.SCANS_TEXT,
Database.SCANS_ERROR_CORRECTION_LEVEL,
Database.SCANS_VERSION,
Database.SCANS_SEQUENCE_SIZE,
@@ -39,7 +39,7 @@ fun Cursor.exportJson(): String? {
val indices = columns.map {
Pair(getColumnIndex(it), it)
}
val contentIndex = getColumnIndex(Database.SCANS_CONTENT)
val contentIndex = getColumnIndex(Database.SCANS_TEXT)
val rawIndex = getColumnIndex(Database.SCANS_RAW)
val root = JSONArray()
do {
@@ -11,12 +11,12 @@ import de.markusfisch.android.zxingcpp.ZxingCpp.Result
import java.util.Locale
data class Scan(
val content: String,
val text: String,
val raw: ByteArray?,
val format: BarcodeFormat,
val errorCorrectionLevel: String? = null,
val version: String? = null,
val dataMask: Int = 0,
val dataMask: Int = -1,
val symbol: BitMatrix? = null,
val sequenceSize: Int = -1,
val sequenceIndex: Int = -1,
@@ -43,7 +43,7 @@ data class Scan(
return id == other.id &&
dateTime == other.dateTime &&
content == other.content &&
text == other.text &&
((raw == null && other.raw == null) ||
(raw != null && other.raw != null && raw.contentEquals(other.raw))) &&
format == other.format &&
@@ -64,7 +64,7 @@ data class Scan(
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + dateTime.hashCode()
result = 31 * result + content.hashCode()
result = 31 * result + text.hashCode()
result = 31 * result + (raw?.contentHashCode() ?: 0)
result = 31 * result + format.hashCode()
result = 31 * result + (errorCorrectionLevel?.hashCode() ?: 0)
@@ -81,7 +81,7 @@ data class Scan(
}
private constructor(parcel: Parcel) : this(
content = parcel.readString() ?: "",
text = parcel.readString() ?: "",
raw = parcel.readSizedByteArray(),
format = BarcodeFormat.valueOf(parcel.readString() ?: ""),
errorCorrectionLevel = parcel.readString(),
@@ -102,7 +102,7 @@ data class Scan(
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.apply {
writeString(content)
writeString(text)
writeSizedByteArray(raw)
writeString(format.name)
writeString(errorCorrectionLevel)
@@ -140,7 +140,7 @@ fun Result.toScan(): Scan {
ContentType.ISO15434,
ContentType.TEXT -> text
else -> ""
else -> "" // Show as binary.
},
rawBytes,
format,
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Matrix
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
@@ -14,6 +15,7 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageView
import android.widget.SeekBar
import android.widget.TextView
import de.markusfisch.android.binaryeye.R
@@ -21,8 +23,8 @@ import de.markusfisch.android.binaryeye.app.db
import de.markusfisch.android.binaryeye.app.hasWritePermission
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.content.Barcode
import de.markusfisch.android.binaryeye.content.BitMatrixBarcode
import de.markusfisch.android.binaryeye.content.BarcodeColors
import de.markusfisch.android.binaryeye.content.BitMatrixBarcode
import de.markusfisch.android.binaryeye.content.ContentBarcode
import de.markusfisch.android.binaryeye.content.copyToClipboard
import de.markusfisch.android.binaryeye.content.shareFile
@@ -60,6 +62,7 @@ class BarcodeFragment : Fragment() {
private val parentJob = Job()
private val scope = CoroutineScope(Dispatchers.IO + parentJob)
private lateinit var imageView: ConfinedScalingImageView
private lateinit var barcode: Barcode<*>
private lateinit var addToHistoryItem: MenuItem
private lateinit var brightenScreenItem: MenuItem
@@ -85,6 +88,9 @@ class BarcodeFragment : Fragment() {
false
)
// Make `imageView` available for `onPause()`.
imageView = view.findViewById(R.id.barcode)
val bitmap: Bitmap
try {
barcode = arguments?.toBarcode() ?: throw IllegalArgumentException(
@@ -104,12 +110,17 @@ class BarcodeFragment : Fragment() {
return null
}
val imageView = view.findViewById<ConfinedScalingImageView>(
R.id.barcode
)
imageView.setImageBitmap(bitmap)
imageView.post {
// Make sure to invoke this after ScalingImageView.onLayout().
imageView.runAfterLayout = {
val scale = prefs.previewScale
if (scale > 0) {
imageView.setScale(
scale,
bitmap.width / 2f,
bitmap.height / 2f
)
}
imageView.minWidth = min(
imageView.minWidth / 2f,
max(bitmap.width, bitmap.height).toFloat()
@@ -125,8 +136,8 @@ class BarcodeFragment : Fragment() {
}
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
imageView.doOnApplyWindowInsets { v, insets ->
(v as ConfinedScalingImageView).insets.set(insets)
imageView.doOnApplyWindowInsets { _, insets ->
imageView.insets.set(insets)
}
return view
@@ -181,6 +192,9 @@ class BarcodeFragment : Fragment() {
if (currentBrightness > -1f) {
restoreScreenBrightness()
}
if (imageView.isInBounds()) {
prefs.previewScale = imageView.getScale()
}
}
override fun onDestroyView() {
@@ -526,6 +540,20 @@ class BarcodeFragment : Fragment() {
}
}
private fun ImageView.setScale(scale: Float, x: Float, y: Float) {
imageMatrix = Matrix().apply {
setTranslate(-x, -y)
postScale(scale, scale)
postTranslate(x, y)
}
}
private fun ImageView.getScale(): Float {
val values = FloatArray(9)
imageMatrix.getValues(values)
return values[Matrix.MSCALE_X]
}
private fun Bitmap.saveAsPng(outputStream: OutputStream, quality: Int = 90) =
compress(Bitmap.CompressFormat.PNG, quality, outputStream)
@@ -45,8 +45,10 @@ import de.markusfisch.android.binaryeye.database.Scan
import de.markusfisch.android.binaryeye.io.askForFileName
import de.markusfisch.android.binaryeye.io.toSaveResult
import de.markusfisch.android.binaryeye.io.writeExternalFile
import de.markusfisch.android.binaryeye.view.hideSoftKeyboard
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
import de.markusfisch.android.binaryeye.widget.toast
import de.markusfisch.android.zxingcpp.ZxingCpp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -79,7 +81,6 @@ class DecodeFragment : Fragment() {
private var action = ActionRegistry.DEFAULT_ACTION
private var isBinary = false
private var originalBytes: ByteArray = ByteArray(0)
private var id = 0L
private var label: String? = null
private var recreationSize = 0
@@ -101,8 +102,8 @@ class DecodeFragment : Fragment() {
false
)
closeAutomatically = prefs.closeAutomatically &&
activity?.intent?.hasExtra(MainActivity.DECODED) == true
val justScanned = activity?.intent?.hasExtra(MainActivity.DECODED) == true
closeAutomatically = prefs.closeAutomatically && justScanned
scan = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arguments?.getParcelable(SCAN, Scan::class.java)
@@ -111,10 +112,9 @@ class DecodeFragment : Fragment() {
arguments?.getParcelable(SCAN) as Scan?
} ?: throw IllegalArgumentException("DecodeFragment needs a Scan")
isBinary = scan.content.isEmpty()
originalBytes = scan.raw ?: scan.content.toByteArray()
isBinary = scan.text.isEmpty()
originalBytes = scan.raw ?: scan.text.toByteArray()
format = scan.format.name
id = scan.id
recreationSize = (200f * dp).roundToInt()
contentView = view.findViewById(R.id.content)
@@ -127,7 +127,7 @@ class DecodeFragment : Fragment() {
labelView = view.findViewById(R.id.label)
fab = view.findViewById(R.id.open)
initContentAndFab()
initContentAndFab(justScanned)
if (prefs.showMetaData) {
metaView.fillMetaView(scan)
@@ -143,7 +143,7 @@ class DecodeFragment : Fragment() {
labelView.visibility = View.GONE
}
updateViewsAndFab(scan.content, originalBytes)
updateViewsAndFab(scan.text, originalBytes)
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
view.findViewById<View>(R.id.scroll_view).setPaddingFromWindowInsets()
@@ -153,6 +153,7 @@ class DecodeFragment : Fragment() {
override fun onPause() {
super.onPause()
val id = scan.id
if (id > 0) {
val newLabel = labelView.text.toString().trim()
if (newLabel != (label ?: "")) {
@@ -168,7 +169,7 @@ class DecodeFragment : Fragment() {
wipeLastShareFile()
}
private fun initContentAndFab() {
private fun initContentAndFab(justScanned: Boolean) {
if (isBinary) {
contentView.setText(String(originalBytes).foldNonAlNum())
contentView.isEnabled = false
@@ -177,7 +178,7 @@ class DecodeFragment : Fragment() {
askForFileNameAndSave(originalBytes)
}
} else {
contentView.setText(scan.content)
contentView.setText(scan.text)
contentView.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
updateViewsAndFab(s?.toString() ?: "")
@@ -200,8 +201,9 @@ class DecodeFragment : Fragment() {
fab.setOnClickListener {
executeAction(this.content)
}
if (prefs.openImmediately) {
if (justScanned && prefs.openImmediately) {
executeAction(this.content)
backOrFinish()
}
}
}
@@ -255,7 +257,7 @@ class DecodeFragment : Fragment() {
clearRecreation()
return@showIf
}
unmodified = text == scan.content
unmodified = text == scan.text
text
}
val barcode = if (unmodified) {
@@ -270,9 +272,12 @@ class DecodeFragment : Fragment() {
try {
v.setImageBitmap(barcode.bitmap(recreationSize))
v.setOnClickListener {
fragmentManager?.addFragment(
BarcodeFragment.newInstance(barcode)
)
v.context.hideSoftKeyboard(contentView)
v.post {
fragmentManager?.addFragment(
BarcodeFragment.newInstance(barcode)
)
}
}
} catch (e: RuntimeException) {
clearRecreation()
@@ -345,7 +350,9 @@ class DecodeFragment : Fragment() {
)
)
}
if (scan.dataMask > -1) {
if (scan.format == ZxingCpp.BarcodeFormat.QR_CODE &&
scan.dataMask > -1
) {
items.putAll(
linkedMapOf(
R.string.qr_data_mask to scan.dataMask.toString()
@@ -436,7 +443,7 @@ class DecodeFragment : Fragment() {
}
R.id.remove -> {
db.removeScan(id)
db.removeScan(scan.id)
backOrFinish()
true
}
@@ -487,17 +494,18 @@ class DecodeFragment : Fragment() {
private fun executeAction(content: String) {
val ac = activity ?: return
if (content.isNotEmpty()) {
if (action is WifiAction &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
!ac.hasLocationPermission { executeAction(content) }
) {
return
}
scope.launch {
action.execute(ac, content.toByteArray())
maybeBackOrFinish()
}
if (content.isEmpty()) {
return
}
if (action is WifiAction &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
!ac.hasLocationPermission { executeAction(content) }
) {
return
}
scope.launch {
action.execute(ac, content.toByteArray())
maybeBackOrFinish()
}
}
@@ -22,6 +22,7 @@ import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.adapter.prettifyFormatName
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.app.setFragment
import de.markusfisch.android.binaryeye.text.unescape
import de.markusfisch.android.binaryeye.view.hideSoftKeyboard
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
@@ -54,7 +55,6 @@ class EncodeFragment : Fragment() {
BarcodeFormat.UPC_A
)
private var minMargin = 0
private var bytes: ByteArray? = null
override fun onActivityResult(
@@ -173,17 +173,21 @@ class EncodeFragment : Fragment() {
colorsLabel = view.findViewById(R.id.colors_label)
colorsSpinner = view.findViewById(R.id.colors)
addQuietZoneSwitch = view.findViewById(R.id.add_quiet_zone)
addQuietZoneSwitch.isChecked = prefs.addQuietZone
contentView = view.findViewById(R.id.content)
unescapeCheckBox = view.findViewById(R.id.unescape)
unescapeCheckBox.isChecked = prefs.expandEscapeSequences
var complete = false
val args = arguments
args?.getString(CONTENT_TEXT)?.let {
contentView.setText(it)
complete = it.isNotEmpty()
}
args?.getByteArray(CONTENT_RAW)?.let {
bytes = it
complete = it.isNotEmpty()
setEncodeByteArray()
}
@@ -205,6 +209,15 @@ class EncodeFragment : Fragment() {
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
view.findViewById<View>(R.id.scroll_view).setPaddingFromWindowInsets()
if (complete &&
args?.getBoolean(EXECUTE, false) == true
) {
view.post {
val content = view.context.getContent() ?: return@post
fragmentManager?.setFragment(newEncodeFragment(content))
}
}
return view
}
@@ -235,31 +248,36 @@ class EncodeFragment : Fragment() {
super.onPause()
prefs.indexOfLastSelectedFormat = formatView.selectedItemPosition
prefs.expandEscapeSequences = unescapeCheckBox.isChecked
prefs.addQuietZone = addQuietZoneSwitch.isChecked
}
private fun Context.encode() {
hideSoftKeyboard(contentView)
val content = getContent() ?: return
val format = formats[formatView.selectedItemPosition]
fragmentManager?.addFragment(
BarcodeFragment.newInstance(
content,
format,
format.getErrorCorrectionLevel(ecSpinner.selectedItemPosition),
when (format) {
BarcodeFormat.AZTEC,
BarcodeFormat.DATA_MATRIX,
BarcodeFormat.QR_CODE,
BarcodeFormat.PDF_417 -> if (
addQuietZoneSwitch.isChecked
) 1 else 0
contentView.post {
fragmentManager?.addFragment(newEncodeFragment(content))
}
}
else -> -1
},
if (format.canBeInverted()) {
colorsSpinner.selectedItemPosition
} else 0
)
private fun newEncodeFragment(content: Any): Fragment {
val format = formats[formatView.selectedItemPosition]
return BarcodeFragment.newInstance(
content,
format,
format.getErrorCorrectionLevel(ecSpinner.selectedItemPosition),
when (format) {
BarcodeFormat.AZTEC,
BarcodeFormat.DATA_MATRIX,
BarcodeFormat.QR_CODE,
BarcodeFormat.PDF_417 -> if (
addQuietZoneSwitch.isChecked
) 1 else 0
else -> -1
},
if (format.canBeInverted()) {
colorsSpinner.selectedItemPosition
} else 0
)
}
@@ -302,11 +320,13 @@ class EncodeFragment : Fragment() {
private const val CONTENT_TEXT = "content_text"
private const val CONTENT_RAW = "content_raw"
private const val FORMAT = "format"
private const val EXECUTE = "execute"
private const val PICK_FILE_RESULT_CODE = 1
fun <T> newInstance(
content: T? = null,
format: String? = null
format: String? = null,
execute: Boolean = false
): Fragment {
val args = Bundle()
content?.let {
@@ -317,6 +337,7 @@ class EncodeFragment : Fragment() {
"content must be a String or a ByteArray"
)
}
args.putBoolean(EXECUTE, execute)
}
format?.let { args.putString(FORMAT, it) }
val fragment = EncodeFragment()
@@ -506,7 +506,7 @@ class HistoryFragment : Fragment() {
private fun Cursor.exportText(separator: String): String {
val sb = StringBuilder()
val contentIndex = getColumnIndex(Database.SCANS_CONTENT)
val contentIndex = getColumnIndex(Database.SCANS_TEXT)
if (contentIndex > -1 && moveToFirst()) {
do {
val content = getString(contentIndex)
@@ -53,7 +53,7 @@ private fun Scan.send(url: String, type: String): Response {
}
else -> request(
url + content.urlEncode()
url + text.urlEncode()
)
}
}
@@ -67,7 +67,7 @@ private fun Scan.asJson() = JSONObject().apply {
}
private fun Scan.getMap(): Map<String, String> = mapOf(
"content" to content,
"content" to text,
"raw" to raw?.toHexString(),
"format" to format.name,
"errorCorrectionLevel" to errorCorrectionLevel,
@@ -0,0 +1,41 @@
package de.markusfisch.android.binaryeye.preference
import android.content.SharedPreferences
import org.json.JSONObject
data class CropHandle(
val x: Int = -2, // -2 means set default roi.
val y: Int = -2,
val orientation: Int = 0
)
fun SharedPreferences.storeCropHandle(
name: String,
cropHandle: CropHandle
) {
edit().putString(
name,
JSONObject().apply {
put("x", cropHandle.x)
put("y", cropHandle.y)
put("orientation", cropHandle.orientation)
}.toString()
).apply()
}
fun SharedPreferences.restoreCropHandle(
name: String,
default: CropHandle = CropHandle()
): CropHandle {
val s = getString(name, null) ?: return default
return try {
val json = JSONObject(s)
CropHandle(
json.getInt("x"),
json.getInt("y"),
json.getInt("orientation")
)
} catch (e: Exception) {
return default
}
}
@@ -7,8 +7,10 @@ import android.media.ToneGenerator
import android.os.Build
import android.preference.PreferenceManager
import android.support.annotation.RequiresApi
import de.markusfisch.android.binaryeye.activity.CameraActivity
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
class Preferences {
lateinit var preferences: SharedPreferences
@@ -38,21 +40,6 @@ class Preferences {
apply(BARCODE_FORMATS, value)
field = value
}
var cropHandleX = -2 // -2 means set default roi.
set(value) {
apply(CROP_HANDLE_X, value)
field = value
}
var cropHandleY = -2
set(value) {
apply(CROP_HANDLE_Y, value)
field = value
}
var cropHandleOrientation = 0
set(value) {
apply(CROP_HANDLE_ORIENTATION, value)
field = value
}
var showCropHandle = true
set(value) {
apply(SHOW_CROP_HANDLE, value)
@@ -210,11 +197,21 @@ class Preferences {
apply(EXPAND_ESCAPE_SEQUENCES, value)
field = value
}
var addQuietZone = true
set(value) {
apply(ADD_QUIET_ZONE, value)
field = value
}
var brightenScreen = false
set(value) {
apply(BRIGHTEN_SCREEN, value)
field = value
}
var previewScale = 0f
set(value) {
apply(PREVIEW_SCALE, value)
field = value
}
fun init(context: Context) {
preferences = PreferenceManager.getDefaultSharedPreferences(context)
@@ -232,12 +229,8 @@ class Preferences {
}
}
cropHandleX = preferences.getInt(CROP_HANDLE_X, cropHandleX)
cropHandleY = preferences.getInt(CROP_HANDLE_Y, cropHandleY)
cropHandleOrientation = preferences.getInt(
CROP_HANDLE_ORIENTATION,
cropHandleOrientation
)
migrateCropHandle()
showCropHandle = preferences.getBoolean(
SHOW_CROP_HANDLE,
showCropHandle
@@ -331,10 +324,15 @@ class Preferences {
EXPAND_ESCAPE_SEQUENCES,
expandEscapeSequences
)
addQuietZone = preferences.getBoolean(ADD_QUIET_ZONE, addQuietZone)
brightenScreen = preferences.getBoolean(
BRIGHTEN_SCREEN,
brightenScreen
)
previewScale = preferences.getFloat(
PREVIEW_SCALE,
previewScale
)
}
private fun addFormatsOnUpdate(
@@ -350,6 +348,31 @@ class Preferences {
}
}
private fun migrateCropHandle() {
val cropHandleXName = "crop_handle_x"
val def = CropHandle()
if (preferences.getInt(cropHandleXName, def.x) == def.x) {
return;
}
storeCropHandle(
CameraActivity.CAMERA_CROP_HANDLE,
CropHandle(
preferences.getInt(cropHandleXName, def.x),
preferences.getInt("crop_handle_y", def.y),
preferences.getInt("crop_handle_orientation", def.orientation)
)
)
preferences.edit().putInt(cropHandleXName, def.x).apply()
}
fun restoreCropHandle(
name: String
) = preferences.restoreCropHandle(name)
fun storeCropHandle(name: String, cropHandle: CropHandle) {
preferences.storeCropHandle(name, cropHandle)
}
fun beepTone() = when (beepToneName) {
"tone_cdma_confirm" -> ToneGenerator.TONE_CDMA_CONFIRM
"tone_sup_radio_ack" -> ToneGenerator.TONE_SUP_RADIO_ACK
@@ -368,9 +391,6 @@ class Preferences {
private fun put(label: String, value: Boolean) =
preferences.edit().putBoolean(label, value)
private fun put(label: String, value: String) =
preferences.edit().putString(label, value)
private fun apply(label: String, value: Boolean) {
put(label, value).apply()
}
@@ -383,6 +403,10 @@ class Preferences {
preferences.edit().putInt(label, value).apply()
}
private fun apply(label: String, value: Float) {
preferences.edit().putFloat(label, value).apply()
}
@SuppressLint("ApplySharedPref")
private fun commit(label: String, value: String) {
preferences.edit().putString(label, value).commit()
@@ -399,9 +423,6 @@ class Preferences {
}
private const val BARCODE_FORMATS = "formats"
private const val CROP_HANDLE_X = "crop_handle_x"
private const val CROP_HANDLE_Y = "crop_handle_y"
private const val CROP_HANDLE_ORIENTATION = "crop_handle_orientation"
private const val SHOW_CROP_HANDLE = "show_crop_handle"
private const val ZOOM_BY_SWIPING = "zoom_by_swiping"
private const val AUTO_ROTATE = "auto_rotate"
@@ -433,6 +454,8 @@ class Preferences {
private const val INDEX_OF_LAST_SELECTED_EC_LEVEL = "index_of_last_selected_ec_level"
private const val FREE_ROTATION = "free_rotation"
private const val EXPAND_ESCAPE_SEQUENCES = "expand_escape_sequences"
private const val ADD_QUIET_ZONE = "add_quiet_zone"
private const val BRIGHTEN_SCREEN = "brighten_screen"
private const val PREVIEW_SCALE = "preview_scale"
}
}
@@ -10,6 +10,8 @@ import de.markusfisch.android.scalingimageview.widget.ScalingImageView
open class ConfinedScalingImageView : ScalingImageView {
val insets = Rect()
var runAfterLayout: (() -> Unit)? = null
constructor(context: Context, attrs: AttributeSet, defStyle: Int) :
super(context, attrs, defStyle)
@@ -37,5 +39,8 @@ open class ConfinedScalingImageView : ScalingImageView {
)
}
centerRemap()
runAfterLayout?.invoke()
}
fun isInBounds(): Boolean = inBounds()
}
@@ -19,6 +19,7 @@ import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.graphics.getBitmapFromDrawable
import de.markusfisch.android.binaryeye.graphics.getDashedBorderPaint
import de.markusfisch.android.binaryeye.preference.CropHandle
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -83,11 +84,16 @@ class DetectorView : View {
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
super(context, attrs, defStyleAttr)
fun saveCropHandlePos() {
fun storeCropHandlePos(name: String) {
val pos = getCropHandlePos()
prefs.cropHandleX = pos.x
prefs.cropHandleY = pos.y
prefs.cropHandleOrientation = currentOrientation
prefs.storeCropHandle(
name,
CropHandle(
pos.x,
pos.y,
currentOrientation
)
)
}
private fun getCropHandlePos() = if (handleActive) {
@@ -96,11 +102,12 @@ class DetectorView : View {
Point(-1, -1)
}
fun restoreCropHandlePos() {
fun restoreCropHandlePos(name: String) {
val cropHandle = prefs.restoreCropHandle(name)
setCropHandlePos(
prefs.cropHandleX,
prefs.cropHandleY,
prefs.cropHandleOrientation
cropHandle.x,
cropHandle.y,
cropHandle.orientation
)
}
@@ -0,0 +1,16 @@
<selector
xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize="true">
<item android:state_pressed="false">
<shape android:shape="rectangle">
<corners android:radius="2dp"/>
<solid android:color="@color/selected_row"/>
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<corners android:radius="2dp"/>
<solid android:color="@color/pressed_row"/>
</shape>
</item>
</selector>
@@ -0,0 +1,57 @@
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<!-- The FrameLayout is necessary because this LinearLayout
should have a padding which would be reset by the window
insets from `fitsSystemWindows`. -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
android:padding="24dp"
android:orientation="vertical"
tools:ignore="UselessParent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:drawableTop="@drawable/logo"
android:drawablePadding="16dp"
android:gravity="center"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/welcome"/>
<LinearLayout
style="@style/WelcomeButton"
android:id="@+id/expert"
android:layout_marginBottom="16dp"
android:orientation="vertical">
<TextView
style="@style/WelcomeButtonText"
android:text="@string/mode_expert"/>
<TextView
style="@style/WelcomeButtonSubText"
android:text="@string/mode_expert_description"/>
</LinearLayout>
<LinearLayout
style="@style/WelcomeButton"
android:id="@+id/simple"
android:orientation="vertical">
<TextView
style="@style/WelcomeButtonText"
android:text="@string/mode_simple"/>
<TextView
style="@style/WelcomeButtonSubText"
android:text="@string/mode_simple_description"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</ScrollView>
+5
View File
@@ -1,4 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="welcome">مرحبًا!</string>
<string name="mode_expert">متقدم</string>
<string name="mode_expert_description">أود التحقق من المحتوى المقروء وفتحه يدويًا.</string>
<string name="mode_simple">بسيط</string>
<string name="mode_simple_description">أريد فقط قراءة الباركود وفتحه فورًا.</string>
<string name="camera_error">يتعذر الوصول إلى الكاميرا، يرجى المحاولة مرة أخرى</string>
<string name="scan_code">مسح الرمز</string>
<string name="compose_barcode">إنشاء رمز Bardcode</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Добре дошли!</string>
<string name="mode_expert">Разширен</string>
<string name="mode_expert_description">Искам да проверя прочетеното съдържание и да го отворя ръчно.</string>
<string name="mode_simple">Опростен</string>
<string name="mode_simple_description">Искам просто да сканирам баркодове и да ги отварям веднага.</string>
<string name="camera_error">Няма достъп до камерата, моля, опитайте отново</string>
<string name="scan_code">Сканирайте код</string>
<string name="compose_barcode">Създаване на баркод</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">স্বাগতম!</string>
<string name="mode_expert">উন্নত</string>
<string name="mode_expert_description">আমি পড়া বিষয়বস্তু যাচাই করতে চাই এবং এটি নিজে খুলতে চাই।</string>
<string name="mode_simple">সহজ</string>
<string name="mode_simple_description">আমি কেবল বারকোড স্ক্যান করতে চাই এবং তা সঙ্গে সঙ্গে খুলতে চাই।</string>
<string name="camera_error">ক্যামেরা পাওয়া যাচ্ছে না, পরে চেষ্টা করো</string>
<string name="scan_code">সংকেতের পাঠোদ্ধার</string>
<string name="compose_barcode">বারকোড বানাও</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Vítejte!</string>
<string name="mode_expert">Pokročilý</string>
<string name="mode_expert_description">Chci zkontrolovat načtený obsah a otevřít jej ručně.</string>
<string name="mode_simple">Jednoduchý</string>
<string name="mode_simple_description">Chci jen skenovat čárové kódy a okamžitě je otevírat.</string>
<string name="camera_error">Nepodařilo se otevřít fotoaparát, prosím zkuste to znovu</string>
<string name="scan_code">Naskenovat kód</string>
<string name="compose_barcode">Vytvořit čárový kód</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Velkommen!</string>
<string name="mode_expert">Avanceret</string>
<string name="mode_expert_description">Jeg vil kontrollere det læste indhold og åbne det manuelt.</string>
<string name="mode_simple">Simpel</string>
<string name="mode_simple_description">Jeg vil bare scanne stregkoder og få dem åbnet med det samme.</string>
<string name="camera_error">Kan ikke få adgang til kamera, prøv igen</string>
<string name="scan_code">Scan kode</string>
<string name="compose_barcode">Opret stregkode</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Willkommen!</string>
<string name="mode_expert">Erweitert</string>
<string name="mode_expert_description">Ich möchte den gelesenen Inhalt überprüfen und manuell öffnen.</string>
<string name="mode_simple">Einfach</string>
<string name="mode_simple_description">Ich möchte nur Barcodes scannen und sie sofort öffnen.</string>
<string name="camera_error">Auf die Kamera kann nicht zugegriffen werden, bitte versuche es nochmal</string>
<string name="scan_code">Code scannen</string>
<string name="compose_barcode">Barcode erstellen</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">¡Bienvenido!</string>
<string name="mode_expert">Avanzado</string>
<string name="mode_expert_description">Quiero verificar el contenido leído y abrirlo manualmente.</string>
<string name="mode_simple">Simple</string>
<string name="mode_simple_description">Solo quiero escanear códigos de barras y abrirlos de inmediato.</string>
<string name="camera_error">No se puede acceder a la cámara, por favor, inténtelo de nuevo</string>
<string name="scan_code">Escanear código</string>
<string name="compose_barcode">Componer código</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">خوش آمدید!</string>
<string name="mode_expert">پیشرفته</string>
<string name="mode_expert_description">می‌خواهم محتوای خوانده شده را بررسی کرده و به صورت دستی باز کنم.</string>
<string name="mode_simple">ساده</string>
<string name="mode_simple_description">فقط می‌خواهم بارکدها را اسکن کنم و فوراً باز شوند.</string>
<string name="camera_error">نمی‌توان به دوربین دسترسی داشت، لطفاً دوباره تلاش کنید</string>
<string name="scan_code">پویش کد</string>
<string name="compose_barcode">ایجاد بارکد</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Bienvenue !</string>
<string name="mode_expert">Avancé</string>
<string name="mode_expert_description">Je souhaite vérifier le contenu lu et l\'ouvrir manuellement.</string>
<string name="mode_simple">Simple</string>
<string name="mode_simple_description">Je veux simplement scanner les codes-barres et les ouvrir immédiatement.</string>
<string name="camera_error">Impossible d\'accéder à la caméra, veuillez réessayer</string>
<string name="scan_code">Scanner un code</string>
<string name="compose_barcode">Créer un code-barres</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Üdvözöljük!</string>
<string name="mode_expert">Haladó</string>
<string name="mode_expert_description">Szeretném ellenőrizni az olvasott tartalmat és kézzel megnyitni.</string>
<string name="mode_simple">Egyszerű</string>
<string name="mode_simple_description">Csak be akarom olvasni a vonalkódokat, és azonnal megnyitni őket.</string>
<string name="camera_error">Nem lehet hozzáférni a kamerához, próbálja meg újra</string>
<string name="scan_code">Kód beolvasása</string>
<string name="compose_barcode">Vonalkód létrehozása</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Selamat datang!</string>
<string name="mode_expert">Lanjutan</string>
<string name="mode_expert_description">Saya ingin memeriksa konten yang dibaca dan membukanya secara manual.</string>
<string name="mode_simple">Sederhana</string>
<string name="mode_simple_description">Saya hanya ingin memindai kode batang dan langsung membukanya.</string>
<string name="camera_error">Tidak bisa mengakses kamera, silakan coba lagi</string>
<string name="scan_code">Pindai kode</string>
<string name="compose_barcode">Buat barcode</string>
+11 -6
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Benvenuto!</string>
<string name="mode_expert">Avanzato</string>
<string name="mode_expert_description">Voglio controllare il contenuto letto e aprirlo manualmente.</string>
<string name="mode_simple">Semplice</string>
<string name="mode_simple_description">Voglio solo scansionare codici a barre e aprirli immediatamente.</string>
<string name="camera_error">Impossibile accedere alla fotocamera. Riprova</string>
<string name="scan_code">Scansione codice</string>
<string name="compose_barcode">Crea codice a barre</string>
@@ -29,7 +34,7 @@
<string name="gtin_add_on">Estensione UPC EAN</string>
<string name="barcode_version_number">Versione</string>
<string name="qr_version_and_modules">%1$s (%2$d moduli)</string>
<string name="qr_data_mask">Data mask</string>
<string name="qr_data_mask">Maschera dati</string>
<string name="toggle_flash">Attiva/disattiva flash</string>
<string name="error_flash">Errore durante attivazione/disattivazione flash</string>
<string name="share">Condividi</string>
@@ -45,7 +50,7 @@
<string name="format">Formato</string>
<string name="size_width_by_height">Dimensioni (pixel): %1$d×%2$d</string>
<string name="size_no_magnification">Nessun ingrandimento</string>
<string name="add_quiet_zone">Add quiet zone</string>
<string name="add_quiet_zone">Aggiungi zona tranquilla</string>
<string name="input_content_here">Inserisci qui il contenuto</string>
<string name="unescape">Espandi sequenze escape</string>
<string name="encode">CODIFICA</string>
@@ -59,9 +64,9 @@
<string name="bulk_mode_delay">Ritardo tra scansioni continue</string>
<string name="restrict_format">Limita formato</string>
<string name="remove_restriction">Rimuovi limitazione</string>
<string name="find_code">Find code</string>
<string name="find_code_with_content">What content?</string>
<string name="find_code_hint">Enter a term</string>
<string name="find_code">Trova codice</string>
<string name="find_code_with_content">Quale contenuto?</string>
<string name="find_code_hint">Inserisci un termine</string>
<string name="scan_format">Scansiona %s</string>
<string name="quarter_second">Un quarto di secondo</string>
<string name="half_second">Mezzo secondo</string>
@@ -110,7 +115,7 @@
<string name="show_meta_data">Visualizza metadati</string>
<string name="show_meta_data_summary">Visualizza dati aggiuntivi codice a barre scansionato.</string>
<string name="show_hex_dump">Visualizza dump esadecimale</string>
<string name="show_hex_dump_summary">Visualizza un dump esadecimale dei contenuti scansionati.</string>
<string name="show_hex_dump_summary">Visualizza dump esadecimale contenuti scansionati.</string>
<string name="show_checksum">Visualizza checksum</string>
<string name="no_checksum">Nessuna checksum</string>
<string name="show_recreation">Visualizza codice a barre ricreato</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">ようこそ!</string>
<string name="mode_expert">上級</string>
<string name="mode_expert_description">読み取った内容を確認し、手動で開きたい。</string>
<string name="mode_simple">簡単</string>
<string name="mode_simple_description">バーコードをスキャンしてすぐに開きたい。</string>
<string name="camera_error">カメラに接続できませんでした。再試行してください。</string>
<string name="scan_code">バーコードをスキャン</string>
<string name="compose_barcode">バーコードを作成</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">მოგესალმებით!</string>
<string name="mode_expert">გაფართოებული</string>
<string name="mode_expert_description">მინდა გადავამოწმო წაკითხული შინაარსი და გავხსნა ხელით.</string>
<string name="mode_simple">მარტივი</string>
<string name="mode_simple_description">მხოლოდ მინდა დავასკანერო ბარკოდი და გავხსნა დაუყოვნებლივ.</string>
<string name="camera_error">კამერაზე წვდომის მოპოვება ვერ ხერხდება, გთხოვთ, კიდევ სცადეთ</string>
<string name="scan_code">შტრიხ-კოდის სკანირება</string>
<string name="compose_barcode">შტრიხ-კოდის შექმნა</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">환영합니다!</string>
<string name="mode_expert">고급</string>
<string name="mode_expert_description">읽은 내용을 확인하고 수동으로 열고 싶습니다.</string>
<string name="mode_simple">간단</string>
<string name="mode_simple_description">바코드를 스캔하고 즉시 열고 싶습니다.</string>
<string name="camera_error">카메라를 접근할 수 없습니다. 다시 시도하세요</string>
<string name="scan_code">Scan code</string>
<string name="compose_barcode">Compose barcode</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Welkom!</string>
<string name="mode_expert">Geavanceerd</string>
<string name="mode_expert_description">Ik wil de gelezen inhoud controleren en handmatig openen.</string>
<string name="mode_simple">Eenvoudig</string>
<string name="mode_simple_description">Ik wil gewoon barcodes scannen en direct openen.</string>
<string name="camera_error">Geen toegang tot camera, probeer opnieuw.</string>
<string name="scan_code">Scan een code</string>
<string name="compose_barcode">Barcode scannen</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Witamy!</string>
<string name="mode_expert">Zaawansowany</string>
<string name="mode_expert_description">Chcę sprawdzić odczytaną treść i otworzyć ją ręcznie.</string>
<string name="mode_simple">Prosty</string>
<string name="mode_simple_description">Chcę tylko skanować kody kreskowe i otwierać je natychmiast.</string>
<string name="open_url">Otwórz URL</string>
<string name="content">Zawartość</string>
<string name="copied_to_clipboard">Skopiowano do schowka</string>
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Bem-vindo!</string>
<string name="mode_expert">Avançado</string>
<string name="mode_expert_description">Quero verificar o conteúdo lido e abri-lo manualmente.</string>
<string name="mode_simple">Simples</string>
<string name="mode_simple_description">Quero apenas ler códigos de barras e abri-los imediatamente.</string>
<string name="camera_error">Não foi possível acessar a câmera, tente novamente</string>
<string name="scan_code">Escanear código</string>
<string name="compose_barcode">Compor código de barras</string>
+8 -3
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Добро пожаловать!</string>
<string name="mode_expert">Расширенный</string>
<string name="mode_expert_description">Я хочу проверять считанные данные и открывать их вручную.</string>
<string name="mode_simple">Простой</string>
<string name="mode_simple_description">Я просто хочу сканировать штрих-коды и сразу их открывать.</string>
<string name="camera_error">Невозможно получить доступ к камере, попробуйте ещё раз</string>
<string name="scan_code">Сканировать штрих-код</string>
<string name="compose_barcode">Создать штрих-код</string>
@@ -61,9 +66,9 @@
<string name="bulk_mode_delay">Задержка непрерывного сканирования</string>
<string name="restrict_format">Исключить формат</string>
<string name="remove_restriction">Включить формат</string>
<string name="find_code">Find code</string>
<string name="find_code_with_content">What content?</string>
<string name="find_code_hint">Enter a term</string>
<string name="find_code">Поиск кода</string>
<string name="find_code_with_content">Что искать?</string>
<string name="find_code_hint">Введите поисковый запрос</string>
<string name="scan_format">Сканировать %s</string>
<string name="quarter_second">Четверть секунды</string>
<string name="half_second">Полсекунды</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Hoş geldiniz!</string>
<string name="mode_expert">Gelişmiş</string>
<string name="mode_expert_description">Okunan içeriği kontrol etmek ve manuel olarak açmak istiyorum.</string>
<string name="mode_simple">Basit</string>
<string name="mode_simple_description">Barkodları taramak ve hemen açmak istiyorum.</string>
<string name="camera_error">Kameraya erişilemiyor, lütfen yeniden deneyin</string>
<string name="scan_code">Kod tara</string>
<string name="compose_barcode">Barkod oluştur</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Ласкаво просимо!</string>
<string name="mode_expert">Розширений</string>
<string name="mode_expert_description">Я хочу перевірити прочитаний вміст і відкрити його вручну.</string>
<string name="mode_simple">Простий</string>
<string name="mode_simple_description">Я просто хочу сканувати штрих-коди і відкривати їх одразу.</string>
<string name="camera_error">Неможливо отримати доступ до камери, будь ласка, спробуйте ще раз</string>
<string name="scan_code">Сканування коду</string>
<string name="compose_barcode">Створити штрих-код</string>
+5
View File
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">Chào mừng!</string>
<string name="mode_expert">Nâng cao</string>
<string name="mode_expert_description">Tôi muốn kiểm tra nội dung đã đọc và mở nó theo cách thủ công.</string>
<string name="mode_simple">Đơn giản</string>
<string name="mode_simple_description">Tôi chỉ muốn quét mã vạch và mở ngay lập tức.</string>
<string name="camera_error">Không thể mở camera, hãy thử lại</string>
<string name="scan_code">Quét mã</string>
<string name="compose_barcode">Tạo mã vạch</string>
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">欢迎!</string>
<string name="mode_expert">高级</string>
<string name="mode_expert_description">我想检查读取的内容并手动打开。</string>
<string name="mode_simple">简单</string>
<string name="mode_simple_description">我只想扫描条形码并立即打开。</string>
<string name="camera_error">无法访问相机,请重试</string>
<string name="scan_code">扫码</string>
<string name="compose_barcode">生成条码</string>
@@ -1,4 +1,9 @@
<resources>
<string name="welcome">歡迎!</string>
<string name="mode_expert">進階</string>
<string name="mode_expert_description">我想檢查讀取的內容並手動開啟。</string>
<string name="mode_simple">簡單</string>
<string name="mode_simple_description">我只想掃描條碼並立即開啟。</string>
<string name="camera_error">無法開啟相機,請再試一次</string>
<string name="scan_code">掃描條碼</string>
<string name="compose_barcode">製作條碼</string>
+5
View File
@@ -1,4 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="welcome">Welcome!</string>
<string name="mode_expert">Advanced</string>
<string name="mode_expert_description">I would like to check the read content and open it manually.</string>
<string name="mode_simple">Simple</string>
<string name="mode_simple_description">I just want to read barcodes and have them opened immediately.</string>
<string name="camera_error">Can\'t access camera, please try again</string>
<string name="scan_code">Scan code</string>
<string name="compose_barcode">Compose barcode</string>
+20
View File
@@ -26,6 +26,26 @@
<item name="android:paddingRight">@dimen/button_padding</item>
<item name="android:maxLines">1</item>
</style>
<style name="WelcomeButton" parent="Button">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">@drawable/button_welcome</item>
<item name="android:padding">24dp</item>
</style>
<style name="WelcomeButtonText">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginBottom">8dp</item>
<item name="android:gravity">center</item>
<item name="android:textStyle">bold</item>
<item name="android:textAppearance">?android:attr/textAppearanceLarge</item>
</style>
<style name="WelcomeButtonSubText">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center</item>
<item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
</style>
<style name="Spinner">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
@@ -1,9 +1,5 @@
* Find barcode by content
* Immediately replace toasts
* Use detected barcode for recreation
* Show data mask of QR Codes
* Fix deep linking
* Replace margin with quiet zone switch when generating barcodes
* Update Russian translation
* Update Portuguese (Brazil) translation
* Fix UTF-8 conversion of binary escape sequences when creating barcodes
* Fix scan order for intervals under one second
* Add arabic translation
* Update French translation
@@ -1,3 +1,9 @@
* Handle empty input for recreation
* Fix setting up database
* Find barcode by content
* Immediately replace toasts
* Use detected barcode for recreation
* Show data mask of QR Codes
* Fix deep linking
* Replace margin with quiet zone switch when generating barcodes
* Update Russian translation
* Update Portuguese (Brazil) translation
@@ -0,0 +1,3 @@
* Handle empty input for recreation
* Fix setting up database
@@ -0,0 +1,4 @@
* Separate crop window settings for image scanning
* Fix initializing dataMask
* Update Russian translation
@@ -0,0 +1,6 @@
* Add a welcome screen to choose a mode
* Add a deep link to encode a barcode
* Remember zoom level of generated barcodes
* Remove Human Readable Interpretation (HRI) decoration
* Update Italian translation
@@ -0,0 +1,6 @@
* Improve simple mode
* Remember quiet zone settings
* Reduce height for 1D barcodes
* Add execute argument for binaryeye://encode
* Update Russian translation