Compare commits

..
39 Commits
Author SHA1 Message Date
Markus Fisch 62d3fd7e94 Advance version number to 1.30.1 2020-06-02 21:48:56 +02:00
Markus Fisch acdac4ca30 Improve german translation for ROI handle setting 2020-06-02 17:36:19 +02:00
Markus Fisch 221cf11576 Convert ROI handle positions to integer points
Because they're always discrete values and can never be inbetween.

Also, calling `drawBitmap()` with float values can lead to slightly
distorted renderings because the image is interpolated between
pixels then.

So let's just avoid this by using integers.
2020-06-02 16:04:39 +02:00
Markus Fisch d34fceb48f Move ROI button back in place if ROI is too small
It's probably better to restore the original situation than
having the button stay where it is.

Also clean up DetectorView a bit and refactor some variables.
2020-06-02 09:59:18 +02:00
Markus Fisch 4cc4cef4ca Show plus in FAB if flash is not available
So it doesn't look awkward when a device doesn't have a flash.
2020-06-01 22:49:25 +02:00
Markus Fisch 969ffd3d0a Move ROI button above FAB
Looks tidier.
2020-06-01 22:48:42 +02:00
Markus Fisch 81a505c752 Refactor Candidates to Dots
The dots aren't just candidates but may already be result points
so this name doesn't really fit.
2020-06-01 20:04:06 +02:00
Markus Fisch eafcf0dba6 Rename ic_crop_handle to button_crop
It's not an icon but a button.
2020-06-01 19:58:29 +02:00
zmniandGitHub 0539468d3d Update Indonesian translation 2020-06-01 19:48:09 +02:00
Markus Fisch 5e9361ac77 Keep ROI over orientation changes 2020-06-01 10:44:48 +02:00
Markus Fisch 54fb31d6a5 Show a default ROI when button is just tapped
Nobody knows the button is a handle that can be used.

So the natural thing to do is to tap on it which should do the
next best thing and that is showing a default region of interest.

Maybe the button should be positioned above the fab and have a
different color when inactive.
2020-05-31 22:11:43 +02:00
Markus Fisch 0f7da91e77 Draw round corners for ROI box
Looks much better.
2020-05-31 19:29:00 +02:00
Markus Fisch 13bbb3e435 Update build tools and gradle wrapper 2020-05-31 13:15:46 +02:00
Markus Fisch e318385032 Advance version number to 1.30.0 2020-05-30 14:42:17 +02:00
Markus Fisch 9b1445efb5 Draw a dotted rectangle around ROI below Kitkat
Because Canvas.clipRect() doesn't work very well below
Kitkat.
2020-05-30 14:32:35 +02:00
Markus Fisch 9a74098f11 Commit tryHarder and autoRotate instead of apply
It's important those values are updated before returning to the
CameraActivity. `apply()` saves preferences asynchronously what
can be too late.
2020-05-30 13:46:52 +02:00
Markus Fisch e22d242248 Add a setting to show/hide crop handle
In case somebody doesn't like/want this feature.
2020-05-30 13:40:01 +02:00
Markus Fisch 77d419aa7b Refactor axis to center in DetectorView
And name all crop handle variables just handle.
2020-05-30 13:22:05 +02:00
Markus Fisch b2fbdceee4 Put crop handle to center bottom for portait
And to center right in landscape.
2020-05-30 12:40:23 +02:00
Markus Fisch 8a625dc5d0 Fix malformed XML of spanish translation 2020-05-30 12:40:11 +02:00
Markus Fisch 6d9c6aa53f Snap crop area for cancelled touches too
And make handlePos private because it's not used from the outside yet.
2020-05-30 12:37:29 +02:00
Markus Fisch 6b03935b9a Add a drop shadow to crop handle
To give it a similar look like the FAB.
2020-05-30 12:34:32 +02:00
Markus Fisch fcac0add8b Add a handle to define a region of interest
And scan only within that region.

Helps to scan only a certain barcode when there are a lot
of barcodes very close together.
2020-05-29 21:02:45 +02:00
Markus Fisch f63b78cc28 Remove insignificant inline argument
Because "inlining works best for functions with parameters
of functional types".
2020-05-29 09:03:50 +02:00
Diego SanguniettiandGitHub f34194c5b5 Add spanish translation 2020-05-29 08:57:24 +02:00
Markus Fisch f2a22d2312 Keep camera selection over orientation changes
And save the facing flag in the state bundle too.
2020-05-28 22:24:56 +02:00
Markus Fisch 746bcc0706 Simplify generating hex dump
To export binary data.
2020-05-27 22:29:27 +02:00
Markus Fisch 2525b693c7 Add support for VCALENDAR types
Treat VCALENDAR like VEVENT types as VCALENDAR is just a wrapper
around a VEVENT.

Also add a few simple tests for VCARD, VCALENDAR and VEVENT to
make sure everything works as expected.
2020-05-27 22:23:00 +02:00
Markus Fisch ba9970eb4d Vibrate only if activity isn't finishing
With Gesture Navigation, a back may also move the image a bit
what will cause the view to redraw and to vibrate even if the
activity is already finishing.
2020-05-24 15:37:44 +02:00
Markus Fisch 0495985518 Add copy to clipboard button to context menu
So it's easy to quickly copy something from the history.
2020-05-22 19:18:33 +02:00
Markus Fisch b662ba9307 Enable/disable menu items in history list
Depending on if there are scans visible or not.
2020-05-22 19:04:47 +02:00
Markus Fisch c4fe713b6d Check for null before using last list child 2020-05-22 18:55:13 +02:00
Markus Fisch 3609c377d2 Always remove current listing in history only
When the list is filtered, the remove all button should only
affect those records.
2020-05-22 18:53:11 +02:00
Markus Fisch 8043ab7eff Advance version number to 1.29.0 2020-05-22 13:39:41 +02:00
Markus Fisch 4f7b392611 Add a note about using the NDK and RenderScript
At the time of writing, it's important to _not_ have the NDK available
when compiling the custom rotation kernel. This will produce a broken
build for some ARMv7 devices running Android 6 (e.g. One Plus X,
Yotaphone 2, Moto E) while it works for newer Android versions.
2020-05-19 22:12:19 +02:00
Markus Fisch 27fb611f4e Add a note about scanning 2D barcodes with ZXing 2020-05-19 22:07:35 +02:00
Markus Fisch 3c693c7bd5 Rotate every other frame if auto rotate is set
Add a new setting that will automatically rotate every other frame
by 90 degrees. This makes it possible to read vertical 1D barcodes
too. ZXing can read 1D barcodes horizontally only.

The setting is unset by default because it makes recognition of 1D
barcodes a tiny bit slower since half of the frames are now useless
for ZXing (because the barcode is in the wrong orientation on them).

On a fast device, this is hardly noticeable. But on a low-end device
it makes a slight difference which is why the setting is unset by
default.
2020-05-19 20:21:27 +02:00
Markus Fisch b2cc1c3c34 Move posting result into its on function
Easier to read.
2020-05-15 13:46:08 +02:00
Markus Fisch e2effb613f Add a rule to dump meminfo of debug version 2020-05-15 13:38:51 +02:00
46 changed files with 1123 additions and 190 deletions
+17
View File
@@ -1,5 +1,22 @@
# Change Log
## 1.30.1
* Improve usability of cropping limiter
* Draw round corners around region of interest
* Update Indonesian translation
## 1.30.0
* Add a handle to define a region of interest
* Add a setting to show/hide cropping limiter
* Add support for VCALENDAR types
* Add spanish translation
* Add copy to clipboard button to context menu in history listing
* Make history actions work on current listing only
* Keep camera selection over orientation changes
## 1.29.0
* Add a setting to enable reading of vertical 1D barcodes
## 1.28.0
* Share history as CSV and JSON too
* Export history in JSON format
+3
View File
@@ -33,6 +33,9 @@ start:
uninstall:
adb $(TARGET) uninstall $(PACKAGE).debug
meminfo:
adb shell dumpsys meminfo $(PACKAGE).debug
images:
svg/update.sh
+16
View File
@@ -89,12 +89,28 @@ So *do not* build release builts on macOS Catalina with build tools 29.0.3.
Building on Linux with 29.0.3 works fine.
### Side by Side NDK
At the time of writing, it's important to _not_ have the NDK available when
compiling the custom rotation kernel. This will produce a broken build for
some ARMv7 devices running Android 6 (e.g. One Plus X, Yotaphone 2, Moto E)
while it works for newer Android versions.
### App Bundle
At the time of writing, `./gradlew bundleRelease` will produce a broken
App Bundle without the necessary RenderScript libraries. This is another
bug in the build tools.
### 2D barcodes
If you want to fork this and are only interested in reading 2D barcodes
(like QR or Aztec), you may want to remove the custom rotation kernel
altogether as ZXing can read 2D barcodes in any orientation.
This will make your app a bit simpler and saves you from compiling a
custom RenderScript kernel for each architecture you want to support.
[play]: https://play.google.com/store/search?q=barcode%20scanner&c=apps
[zxing]: https://github.com/zxing/zxing
[kotlin]: http://kotlinlang.org/
+2 -2
View File
@@ -9,8 +9,8 @@ android {
minSdkVersion 9
targetSdkVersion sdk_version
versionCode 64
versionName '1.28.0'
versionCode 67
versionName '1.30.1'
// it's recommended to set this value to the lowest API level
// able to provide all the functionality
@@ -34,15 +34,21 @@ class PreprocessorTest {
val preprocessor = Preprocessor(
InstrumentationRegistry.getTargetContext(),
frameWidth.toInt(),
frameHeight.toInt(),
frameOrientation.toInt()
)
preprocessor.process(frameData)
val result = zxing.decode(
frameData,
preprocessor.outWidth,
preprocessor.outHeight
frameHeight.toInt()
)
val outWidth: Int
val outHeight: Int
val orientation = frameOrientation.toInt()
if (orientation == 90 || orientation == 270) {
preprocessor.resizeAndRotate(frameData)
outWidth = preprocessor.outHeight
outHeight = preprocessor.outWidth
} else {
preprocessor.resizeOnly(frameData)
outWidth = preprocessor.outWidth
outHeight = preprocessor.outHeight
}
val result = zxing.decode(frameData, outWidth, outHeight)
preprocessor.destroy()
checkNotNull(result) { "no barcode found in $file" }
@@ -6,17 +6,25 @@ object VTypeParser {
private const val BACKSLASH_R_LEGACY =
"(?:\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])"
private val vTypeRegex =
"""^BEGIN:(VCARD|VEVENT)(?:$BACKSLASH_R_LEGACY.+?:[\s\S]+?)+?${BACKSLASH_R_LEGACY}END:\1$BACKSLASH_R_LEGACY?$""".toRegex(
"""^BEGIN:(V.+)(?:$BACKSLASH_R_LEGACY.+?:[\s\S]+?)+?${BACKSLASH_R_LEGACY}END:\1$BACKSLASH_R_LEGACY?$""".toRegex(
RegexOption.IGNORE_CASE
)
private val propertyRegex = """^(.+?):([\s\S]*?)$""".toRegex(RegexOption.MULTILINE)
private val propertyRegex = """^(.+?):([\s\S]*?)$""".toRegex(
RegexOption.MULTILINE
)
fun parseVType(data: String): String? =
vTypeRegex.matchEntire(data)?.groupValues?.get(1)?.toUpperCase(Locale.US)
fun parseVType(data: String): String? = vTypeRegex.matchEntire(
data
)?.groupValues?.get(1)?.toUpperCase(Locale.US)
fun parseMap(data: String): Map<String, List<VTypeProperty>> = propertyRegex.findAll(data).map {
fun parseMap(
data: String
): Map<String, List<VTypeProperty>> = propertyRegex.findAll(data).map {
it.groupValues[1].split(';').let { typeAndInfo ->
typeAndInfo[0] to VTypeProperty(typeAndInfo.drop(1), it.groupValues[2])
typeAndInfo[0] to VTypeProperty(
typeAndInfo.drop(1),
it.groupValues[2]
)
}
}.groupBy({ it.first.toUpperCase(Locale.US) }) { it.second }
}
@@ -21,8 +21,11 @@ object VEventAction : IntentAction() {
get() = R.string.vevent_failed
override fun canExecuteOn(data: ByteArray): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH
&& VTypeParser.parseVType(String(data)) == "VEVENT"
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return false
}
val type = VTypeParser.parseVType(String(data))
return type == "VEVENT" || type == "VCALENDAR"
}
@RequiresApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@@ -4,10 +4,13 @@ import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Rect
import android.graphics.RectF
import android.hardware.Camera
import android.net.Uri
import android.os.Bundle
import android.os.Vibrator
import android.support.design.widget.FloatingActionButton
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.support.v8.renderscript.RSRuntimeException
@@ -24,7 +27,9 @@ import de.markusfisch.android.binaryeye.app.*
import de.markusfisch.android.binaryeye.data.Scan
import de.markusfisch.android.binaryeye.graphics.Mapping
import de.markusfisch.android.binaryeye.graphics.frameToView
import de.markusfisch.android.binaryeye.graphics.isPortrait
import de.markusfisch.android.binaryeye.rs.Preprocessor
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
import de.markusfisch.android.binaryeye.widget.DetectorView
import de.markusfisch.android.binaryeye.widget.toast
import de.markusfisch.android.binaryeye.zxing.Zxing
@@ -38,7 +43,7 @@ import kotlin.math.roundToInt
class CameraActivity : AppCompatActivity() {
private val zxing = Zxing(ResultPointCallback { point ->
point?.let {
mapping?.map(it)?.let {
getMapping()?.map(it)?.let {
detectorView.post {
detectorView.mark(listOf(it))
}
@@ -50,10 +55,13 @@ class CameraActivity : AppCompatActivity() {
private lateinit var cameraView: CameraView
private lateinit var detectorView: DetectorView
private lateinit var zoomBar: SeekBar
private lateinit var flashFab: View
private lateinit var flashFab: FloatingActionButton
private var preprocessor: Preprocessor? = null
private var mapping: Mapping? = null
private var nativeMapping: Mapping? = null
private var rotatedMapping: Mapping? = null
private var recreatePreprocessor = false
private var rotate = false
private var invert = false
private var flash = false
private var returnResult = false
@@ -104,12 +112,13 @@ class CameraActivity : AppCompatActivity() {
cameraView = findViewById(R.id.camera_view) as CameraView
detectorView = findViewById(R.id.detector_view) as DetectorView
zoomBar = findViewById(R.id.zoom) as SeekBar
flashFab = findViewById(R.id.flash)
flashFab.setOnClickListener { toggleTorchMode() }
flashFab = findViewById(R.id.flash) as FloatingActionButton
initCameraView()
initZoomBar()
restoreZoom()
detectorView.updateRoi = { recreatePreprocessor = true }
detectorView.setPaddingFromWindowInsets()
if (intent?.action == Intent.ACTION_SEND &&
intent.type == "text/plain"
@@ -168,11 +177,13 @@ class CameraActivity : AppCompatActivity() {
super.onRestoreInstanceState(savedState)
zoomBar.max = savedState.getInt(ZOOM_MAX)
zoomBar.progress = savedState.getInt(ZOOM_LEVEL)
frontFacing = savedState.getBoolean(FRONT_FACING)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(ZOOM_MAX, zoomBar.max)
outState.putInt(ZOOM_LEVEL, zoomBar.progress)
outState.putBoolean(FRONT_FACING, frontFacing)
super.onSaveInstanceState(outState)
}
@@ -184,7 +195,7 @@ class CameraActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.create -> {
startActivity(MainActivity.getEncodeIntent(this))
createBarcode()
true
}
R.id.history -> {
@@ -219,6 +230,10 @@ class CameraActivity : AppCompatActivity() {
}
}
private fun createBarcode() {
startActivity(MainActivity.getEncodeIntent(this))
}
private fun switchCamera() {
closeCamera()
frontFacing = frontFacing xor true
@@ -325,36 +340,23 @@ class CameraActivity : AppCompatActivity() {
}
override fun onCameraReady(camera: Camera) {
// reset preprocessor to make sure it always fits the current
// frame orientation; important for landscape to landscape
// orientation changes
resetPreProcessor()
val frameWidth = cameraView.frameWidth
val frameHeight = cameraView.frameHeight
val frameOrientation = cameraView.frameOrientation
var decoding = true
// reset preprocessor to make sure it fits the current
// frame orientation; important for landscape to landscape
// orientation changes
resetPreProcessor()
camera.setPreviewCallback { frameData, _ ->
if (decoding) {
val result = decodeFrame(
decodeFrame(
frameData,
frameWidth,
frameHeight,
frameOrientation
)
result?.let {
cameraView.post {
val rp = result.resultPoints
val m = mapping
if (m != null && rp != null && rp.isNotEmpty()) {
detectorView.mark(m.map(rp))
}
vibrator.vibrate()
showResult(
this@CameraActivity,
result,
returnResult
)
}
)?.let { result ->
postResult(result)
decoding = false
}
}
@@ -416,11 +418,13 @@ class CameraActivity : AppCompatActivity() {
)
}
private fun updateFlashFab(available: Boolean) {
flashFab.visibility = if (available) {
View.GONE
private fun updateFlashFab(unavailable: Boolean) {
if (unavailable) {
flashFab.setImageResource(R.drawable.ic_action_create)
flashFab.setOnClickListener { createBarcode() }
} else {
View.VISIBLE
flashFab.setImageResource(R.drawable.ic_action_flash)
flashFab.setOnClickListener { toggleTorchMode() }
}
}
@@ -450,21 +454,36 @@ class CameraActivity : AppCompatActivity() {
frameOrientation: Int
): Result? {
frameData ?: return null
invert = invert xor true
rotate = if (prefs.autoRotate) {
rotate xor true
} else {
invert = invert xor true
isPortrait(frameOrientation)
}
return try {
if (recreatePreprocessor) {
resetPreProcessor()
recreatePreprocessor = false
}
val pp = preprocessor ?: createPreprocessorAndMapping(
frameWidth,
frameHeight,
frameOrientation
)
pp.process(frameData)
val w: Int
val h: Int
if (rotate) {
pp.resizeAndRotate(frameData)
w = pp.outHeight
h = pp.outWidth
} else {
invert = invert xor true
pp.resizeOnly(frameData)
w = pp.outWidth
h = pp.outHeight
}
preprocessor = pp
zxing.decode(
frameData,
pp.outWidth,
pp.outHeight,
invert
)
zxing.decode(frameData, w, h, invert)
} catch (e: RSRuntimeException) {
prefs.forceCompat = prefs.forceCompat xor true
// now the only option is to restart the app because
@@ -480,25 +499,111 @@ class CameraActivity : AppCompatActivity() {
frameHeight: Int,
frameOrientation: Int
): Preprocessor {
val pp = Preprocessor(
this,
val frameRoi = calculateFrameRect(
frameWidth,
frameHeight,
frameOrientation
)
mapping = frameToView(
val pp = Preprocessor(
this,
frameWidth,
frameHeight,
frameRoi
)
val viewRect = if (frameRoi != null) {
detectorView.roi
} else {
cameraView.previewRect
}
nativeMapping = frameToView(
pp.outWidth,
pp.outHeight,
cameraView.previewRect
frameOrientation,
viewRect
)
rotatedMapping = frameToView(
pp.outHeight,
pp.outWidth,
(frameOrientation - 90 + 360) % 360,
viewRect
)
return pp
}
private fun calculateFrameRect(
frameWidth: Int,
frameHeight: Int,
frameOrientation: Int
): Rect? {
val viewRoi = detectorView.roi
return if (
viewRoi.width() == 0 ||
viewRoi.height() == 0
) {
null
} else {
// map ROI in detectorView to cameraView because cameraView may
// be larger than the detectorView
val previewRect = cameraView.previewRect
val previewRoi = Rect(
previewRect.left + viewRoi.left,
previewRect.top + viewRoi.top,
previewRect.left + viewRoi.right,
previewRect.top + viewRoi.bottom
)
val previewRectWidth = previewRect.width().toFloat()
val previewRectHeight = previewRect.height().toFloat()
val normalizedRoi = RectF(
previewRoi.left.toFloat() / previewRectWidth,
previewRoi.top.toFloat() / previewRectHeight,
previewRoi.right.toFloat() / previewRectWidth,
previewRoi.bottom.toFloat() / previewRectHeight
)
// since the ROI is always centered and symmetric, we don't
// need to distinguish between 0 and 180 or 90 and 270 degree
if (isPortrait(frameOrientation)) {
Rect(
(normalizedRoi.top * frameWidth.toFloat()).roundToInt(),
(normalizedRoi.left * frameHeight.toFloat()).roundToInt(),
(normalizedRoi.bottom * frameWidth.toFloat()).roundToInt(),
(normalizedRoi.right * frameHeight.toFloat()).roundToInt()
)
} else {
Rect(
(normalizedRoi.left * frameWidth.toFloat()).roundToInt(),
(normalizedRoi.top * frameHeight.toFloat()).roundToInt(),
(normalizedRoi.right * frameWidth.toFloat()).roundToInt(),
(normalizedRoi.bottom * frameHeight.toFloat()).roundToInt()
)
}
}
}
private fun postResult(result: Result) {
// get mapping for the current rotate value
val mapping = getMapping()
cameraView.post {
val rp = result.resultPoints
if (mapping != null && rp != null && rp.isNotEmpty()) {
detectorView.mark(mapping.map(rp))
}
vibrator.vibrate()
showResult(
this@CameraActivity,
result,
returnResult
)
}
}
private fun getMapping() = if (rotate) rotatedMapping else nativeMapping
companion object {
private const val REQUEST_CAMERA = 1
private const val PICK_FILE_RESULT_CODE = 1
private const val ZOOM_MAX = "zoom_max"
private const val ZOOM_LEVEL = "zoom_level"
private const val FRONT_FACING = "front_facing"
}
}
@@ -3,7 +3,6 @@ package de.markusfisch.android.binaryeye.activity
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
@@ -70,33 +69,30 @@ class PickActivity : AppCompatActivity() {
}
val scannedRect = Rect()
val scanWithinBounds: () -> Result? = {
var result: Result? = null
crop(
bitmap,
cropImageView.normalizedRectInBounds,
cropImageView.imageRotation
)?.also {
scannedRect.set(0, 0, it.width, it.height)
result = zxing.decodePositiveNegative(it)
}
result
fun scanWithinBounds() = crop(
bitmap,
cropImageView.normalizedRectInBounds,
cropImageView.imageRotation
)?.let {
scannedRect.set(0, 0, it.width, it.height)
zxing.decodePositiveNegative(it)
}
cropImageView = findViewById(R.id.image) as CropImageView
cropImageView.setImageBitmap(bitmap)
cropImageView.onScan = {
var points: List<Point>? = null
scanWithinBounds()?.let {
points = mapResult(
if (!isFinishing) {
vibrator.vibrate()
}
mapResult(
scannedRect.width(),
scannedRect.height(),
0,
cropImageView.getBoundsRect(),
it
)
vibrator.vibrate()
}
points
}
cropImageView.doOnApplyWindowInsets { v, insets ->
(v as CropImageView).windowInsets.set(insets)
@@ -31,6 +31,17 @@ class ScansAdapter(context: Context, cursor: Cursor) :
selectedScanPosition = -1
}
fun getSelectedContent() = if (selectedScanPosition < 0) {
null
} else {
val cursor = getItem(selectedScanPosition)
if (cursor is Cursor) {
cursor.getString(contentIndex)
} else {
null
}
}
override fun newView(
context: Context,
cursor: Cursor,
@@ -11,10 +11,4 @@ fun parseAndNormalizeUri(input: String): Uri = Uri.parse(input).let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) it.normalizeScheme() else it
}
fun ByteArray.toHexString(): String {
val hex = StringBuilder()
for (i in this.indices) {
hex.append(String.format("%02X", this[i]))
}
return hex.toString()
}
fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }
@@ -45,7 +45,7 @@ private fun firstChildScrolled(listView: AbsListView): Boolean {
private fun lastChildOutOfView(listView: AbsListView): Boolean {
val child = listView.getChildAt(listView.lastVisiblePosition)
return child.bottom >= listView.height
return child != null && child.bottom >= listView.height
}
fun initSystemBars(activity: AppCompatActivity) {
@@ -50,14 +50,19 @@ class Database {
""", getWhereArguments(query)
)
private fun getWhereClause(query: String?) = if (query?.isNotEmpty() == true) {
"""WHERE $SCANS_CONTENT LIKE ?
private fun getWhereClause(
query: String?,
prefix: String = "WHERE"
) = if (query?.isNotEmpty() == true) {
"""$prefix $SCANS_CONTENT LIKE ?
OR $SCANS_NAME LIKE ?"""
} else {
""
}
private fun getWhereArguments(query: String?) = if (query?.isNotEmpty() == true) {
private fun getWhereArguments(
query: String?
) = if (query?.isNotEmpty() == true) {
val instr = "%$query%"
arrayOf(instr, instr)
} else {
@@ -173,8 +178,8 @@ class Database {
db.delete(SCANS, "$SCANS_ID = ?", arrayOf("$id"))
}
fun removeScans() {
db.delete(SCANS, null, null)
fun removeScans(query: String? = null) {
db.delete(SCANS, getWhereClause(query, ""), getWhereArguments(query))
}
fun renameScan(id: Long, name: String) {
@@ -71,6 +71,14 @@ class HistoryFragment : Fragment() {
): Boolean {
val ac = activity ?: return false
return when (item.itemId) {
R.id.copy_scan -> {
scansAdapter?.getSelectedContent()?.let {
ac.copyToClipboard(it)
ac.toast(R.string.copied_to_clipboard)
}
closeActionMode()
true
}
R.id.edit_scan -> {
scansAdapter?.let {
askForName(
@@ -102,6 +110,8 @@ class HistoryFragment : Fragment() {
private var listViewState: Parcelable? = null
private var actionMode: ActionMode? = null
private var filter: String? = null
private var clearListMenuItem: MenuItem? = null
private var exportHistoryMenuItem: MenuItem? = null
override fun onCreate(state: Bundle?) {
super.onCreate(state)
@@ -173,6 +183,8 @@ class HistoryFragment : Fragment() {
inflater.inflate(R.menu.fragment_history, menu)
initSearchView(menu.findItem(R.id.search))
menu.setGroupVisible(R.id.scans_available, scansAdapter?.count != 0)
clearListMenuItem = menu.findItem(R.id.clear)
exportHistoryMenuItem = menu.findItem(R.id.export_history)
}
private fun initSearchView(item: MenuItem?) {
@@ -205,7 +217,7 @@ class HistoryFragment : Fragment() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.clear -> {
askToRemoveAllScans(context)
askToRemoveScans(context)
true
}
R.id.export_history -> {
@@ -243,6 +255,7 @@ class HistoryFragment : Fragment() {
}
ActivityCompat.invalidateOptionsMenu(ac)
}
enableMenuItems(hasScans)
fab.visibility = if (hasScans) {
View.VISIBLE
} else {
@@ -261,6 +274,11 @@ class HistoryFragment : Fragment() {
}
}
private fun enableMenuItems(enabled: Boolean) {
clearListMenuItem?.isEnabled = enabled
exportHistoryMenuItem?.isEnabled = enabled
}
private fun closeActionMode() {
unlockStatusBarColor()
scansAdapter?.clearSelection()
@@ -321,11 +339,17 @@ class HistoryFragment : Fragment() {
.show()
}
private fun askToRemoveAllScans(context: Context) {
private fun askToRemoveScans(context: Context) {
AlertDialog.Builder(context)
.setMessage(R.string.really_remove_all_scans)
.setMessage(
if (filter == null) {
R.string.really_remove_all_scans
} else {
R.string.really_remove_selected_scans
}
)
.setPositiveButton(android.R.string.ok) { _, _ ->
db.removeScans()
db.removeScans(filter)
updateAndClearFilter()
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
@@ -12,7 +12,9 @@ import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
class PreferencesFragment : Fragment() {
private lateinit var showCropHandleSwitch: SwitchCompat
private lateinit var zoomBySwipingSwitch: SwitchCompat
private lateinit var autoResizeSwitch: SwitchCompat
private lateinit var tryHarderSwitch: SwitchCompat
private lateinit var vibrateSwitch: SwitchCompat
private lateinit var useHistorySwitch: SwitchCompat
@@ -35,11 +37,21 @@ class PreferencesFragment : Fragment() {
false
)
showCropHandleSwitch = view.findViewById(R.id.show_crop_handle)
if (prefs.showCropHandle) {
showCropHandleSwitch.toggle()
}
zoomBySwipingSwitch = view.findViewById(R.id.zoom_by_swiping)
if (prefs.zoomBySwiping) {
zoomBySwipingSwitch.toggle()
}
autoResizeSwitch = view.findViewById(R.id.auto_rotate)
if (prefs.autoRotate) {
autoResizeSwitch.toggle()
}
tryHarderSwitch = view.findViewById(R.id.try_harder)
if (prefs.tryHarder) {
tryHarderSwitch.toggle()
@@ -87,7 +99,9 @@ class PreferencesFragment : Fragment() {
override fun onStop() {
super.onStop()
prefs.showCropHandle = showCropHandleSwitch.isChecked
prefs.zoomBySwiping = zoomBySwipingSwitch.isChecked
prefs.autoRotate = autoResizeSwitch.isChecked
prefs.tryHarder = tryHarderSwitch.isChecked
prefs.vibrate = vibrateSwitch.isChecked
prefs.useHistory = useHistorySwitch.isChecked
@@ -0,0 +1,15 @@
package de.markusfisch.android.binaryeye.graphics
import android.content.Context
import android.graphics.DashPathEffect
import android.graphics.Paint
import android.support.v4.content.ContextCompat
import de.markusfisch.android.binaryeye.R
fun Context.getDashedBorderPaint() = Paint(Paint.ANTI_ALIAS_FLAG).apply {
val dp = resources.displayMetrics.density
color = ContextCompat.getColor(applicationContext, R.color.crop_bound)
style = Paint.Style.STROKE
strokeWidth = dp * 2f
pathEffect = DashPathEffect(floatArrayOf(10f * dp, 10f * dp), 0f)
}
@@ -7,14 +7,14 @@ import android.graphics.Point
import android.support.v4.content.ContextCompat
import de.markusfisch.android.binaryeye.R
class Candidates(context: Context) {
class Dots(context: Context) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val radius = 8f * context.resources.displayMetrics.density
init {
paint.color = ContextCompat.getColor(
context,
R.color.candidate
R.color.dot
)
paint.style = Paint.Style.FILL
}
@@ -0,0 +1,36 @@
package de.markusfisch.android.binaryeye.graphics
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Build
fun Resources.getBitmapFromDrawable(
resId: Int
): Bitmap = getBitmapFromDrawable(getDrawableCompat(resId))
fun Resources.getDrawableCompat(
resId: Int
): Drawable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getDrawable(resId, null)
} else {
@Suppress("DEPRECATION")
getDrawable(resId)
}
private fun getBitmapFromDrawable(drawable: Drawable): Bitmap {
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
@@ -7,31 +7,79 @@ import com.google.zxing.ResultPoint
import kotlin.math.roundToInt
fun mapResult(
width: Int,
height: Int,
frameWidth: Int,
frameHeight: Int,
frameOrientation: Int,
viewRect: Rect,
result: Result
): List<Point> = frameToView(width, height, viewRect).map(result.resultPoints)
): List<Point> = frameToView(
frameWidth,
frameHeight,
frameOrientation,
viewRect
).map(
result.resultPoints
)
fun frameToView(width: Int, height: Int, viewRect: Rect) = Mapping(
viewRect.width().toFloat() / width.toFloat(),
viewRect.height().toFloat() / height.toFloat(),
fun frameToView(
frameWidth: Int,
frameHeight: Int,
frameOrientation: Int,
viewRect: Rect
) = Mapping(
frameWidth,
frameHeight,
frameOrientation,
viewRect.width().toFloat(),
viewRect.height().toFloat(),
viewRect.left,
viewRect.top
)
fun isPortrait(orientation: Int) = orientation == 90 || orientation == 270
data class Mapping(
val ratioX: Float,
val ratioY: Float,
val frameWidth: Int,
val frameHeight: Int,
val frameOrientation: Int,
val viewWidth: Float,
val viewHeight: Float,
val offsetX: Int,
val offsetY: Int
) {
fun map(point: ResultPoint) = Point(
(point.x * ratioX).roundToInt() + offsetX,
(point.y * ratioY).roundToInt() + offsetY
)
fun map(resultPoint: ResultPoint): Point {
val point = Point(
resultPoint.x.roundToInt(),
resultPoint.y.roundToInt()
)
rotate(point)
val w: Int
val h: Int
if (isPortrait(frameOrientation)) {
w = frameHeight
h = frameWidth
} else {
w = frameWidth
h = frameHeight
}
val ratioX = viewWidth / w.toFloat()
val ratioY = viewHeight / h.toFloat()
point.set(
(point.x * ratioX).roundToInt() + offsetX,
(point.y * ratioY).roundToInt() + offsetY
)
return point
}
fun map(points: Array<ResultPoint?>): List<Point> =
// because ZXing apparently returns null in this array sometimes
points.filterNotNull().map { map(it) }
private fun rotate(point: Point) = when (frameOrientation) {
90 -> point.set(frameHeight - point.y, point.x)
180 -> point.set(frameWidth - point.x, frameHeight - point.y)
270 -> point.set(point.y, frameWidth - point.x)
else -> {
}
}
}
@@ -7,14 +7,28 @@ import android.preference.PreferenceManager
class Preferences {
lateinit var preferences: SharedPreferences
var showCropHandle = true
set(value) {
apply(SHOW_CROP_HANDLE, value)
field = value
}
var zoomBySwiping = true
set(value) {
apply(ZOOM_BY_SWIPING, value)
field = value
}
var autoRotate = false
set(value) {
// immediately save this setting before it shouldn't change
// on the fly while scanning
commit(AUTO_ROTATE, value)
field = value
}
var tryHarder = false
set(value) {
apply(TRY_HARDER, value)
// immediately save this setting because it's only ever read
// before the camera is opened
commit(TRY_HARDER, value)
field = value
}
var vibrate = true
@@ -71,7 +85,9 @@ class Preferences {
}
fun update() {
showCropHandle = preferences.getBoolean(SHOW_CROP_HANDLE, showCropHandle)
zoomBySwiping = preferences.getBoolean(ZOOM_BY_SWIPING, zoomBySwiping)
autoRotate = preferences.getBoolean(AUTO_ROTATE, autoRotate)
tryHarder = preferences.getBoolean(TRY_HARDER, tryHarder)
vibrate = preferences.getBoolean(VIBRATE, vibrate)
useHistory = preferences.getBoolean(USE_HISTORY, useHistory)
@@ -115,7 +131,9 @@ class Preferences {
}
companion object {
const val SHOW_CROP_HANDLE = "show_crop_handle"
const val ZOOM_BY_SWIPING = "zoom_by_swiping"
const val AUTO_ROTATE = "auto_rotate"
const val TRY_HARDER = "try_harder"
const val VIBRATE = "vibrate"
const val USE_HISTORY = "use_history"
@@ -1,15 +1,18 @@
package de.markusfisch.android.binaryeye.rs
import android.content.Context
import android.graphics.Rect
import android.support.v8.renderscript.*
import de.markusfisch.android.binaryeye.renderscript.ScriptC_rotator
import kotlin.math.roundToInt
private const val SCALE_FACTOR = .75f
class Preprocessor(
context: Context,
width: Int,
height: Int,
private val orientation: Int
private val roi: Rect?
) {
var outWidth = 0
var outHeight = 0
@@ -20,6 +23,8 @@ class Preprocessor(
private var yuvType: Type? = null
private var yuvAlloc: Allocation? = null
private var roiType: Type? = null
private var roiAlloc: Allocation? = null
private var resizedType: Type? = null
private var resizedAlloc: Allocation? = null
private var rotatedType: Type? = null
@@ -38,15 +43,38 @@ class Preprocessor(
Allocation.USAGE_SCRIPT
)
val f = .75f
var w = (width * f).roundToInt()
var h = (height * f).roundToInt()
if (roi != null) {
val roiWidth = roi.width()
val roiHeight = roi.height()
roiType = Type.createXY(
rs,
Element.U8(rs),
roiWidth,
roiHeight
)
roiAlloc = Allocation.createTyped(
rs,
roiType,
Allocation.USAGE_SCRIPT
)
outWidth = (roiWidth * SCALE_FACTOR).roundToInt()
outHeight = (roiHeight * SCALE_FACTOR).roundToInt()
// make sure the dimensions are always a multiple of 4
outWidth -= outWidth % 4
outHeight -= outHeight % 4
} else {
outWidth = (width * SCALE_FACTOR).roundToInt()
outHeight = (height * SCALE_FACTOR).roundToInt()
}
resizedType = Type.createXY(
rs,
Element.U8(rs),
w,
h
outWidth,
outHeight
)
resizedAlloc = Allocation.createTyped(
rs,
@@ -54,28 +82,17 @@ class Preprocessor(
Allocation.USAGE_SCRIPT
)
if (orientation == 90 || orientation == 270) {
val tmp = w
w = h
h = tmp
}
outWidth = w
outHeight = h
if (orientation != 0) {
rotatedType = Type.createXY(
rs,
Element.U8(rs),
w,
h
)
rotatedAlloc = Allocation.createTyped(
rs,
rotatedType,
Allocation.USAGE_SCRIPT
)
}
rotatedType = Type.createXY(
rs,
Element.U8(rs),
outHeight,
outWidth
)
rotatedAlloc = Allocation.createTyped(
rs,
rotatedType,
Allocation.USAGE_SCRIPT
)
}
fun destroy() {
@@ -83,6 +100,10 @@ class Preprocessor(
yuvType = null
yuvAlloc?.destroy()
yuvAlloc = null
roiType?.destroy()
roiType = null
roiAlloc?.destroy()
roiAlloc = null
resizedType?.destroy()
resizedType = null
resizedAlloc?.destroy()
@@ -96,34 +117,39 @@ class Preprocessor(
rs.destroy()
}
fun process(frame: ByteArray) {
fun resizeOnly(frame: ByteArray) {
resize(frame)
resizedAlloc?.copyTo(frame)
}
fun resizeAndRotate(frame: ByteArray) {
resize(frame)
val t = resizedType ?: return
rotatorScript._inImage = resizedAlloc
rotatorScript._inWidth = t.x
rotatorScript._inHeight = t.y
rotatorScript.forEach_rotate90(
rotatedAlloc, // ignored in kernel, just to satisfy forEach
rotatedAlloc
)
rotatedAlloc?.copyTo(frame)
}
private fun resize(frame: ByteArray) {
yuvAlloc?.copyFrom(frame)
resizeScript.setInput(yuvAlloc)
resizeScript.forEach_bicubic(resizedAlloc)
val t = resizedType
if (t == null || orientation == 0) {
resizedAlloc?.copyTo(frame)
} else {
rotatorScript._inImage = resizedAlloc
rotatorScript._inWidth = t.x
rotatorScript._inHeight = t.y
when (orientation) {
90 -> rotatorScript.forEach_rotate90(
rotatedAlloc,
rotatedAlloc
)
180 -> rotatorScript.forEach_rotate180(
rotatedAlloc,
rotatedAlloc
)
270 -> rotatorScript.forEach_rotate270(
rotatedAlloc,
rotatedAlloc
resizeScript.setInput(
if (roi != null) {
roiAlloc?.copy2DRangeFrom(
0, 0,
roi.width(), roi.height(),
yuvAlloc,
roi.left, roi.top
)
roiAlloc
} else {
yuvAlloc
}
rotatedAlloc?.copyTo(frame)
}
)
resizeScript.forEach_bicubic(resizedAlloc)
}
}
@@ -1,11 +1,13 @@
package de.markusfisch.android.binaryeye.widget
import android.content.Context
import android.graphics.*
import android.support.v4.content.ContextCompat
import android.graphics.Canvas
import android.graphics.Point
import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.graphics.Candidates
import de.markusfisch.android.binaryeye.graphics.Dots
import de.markusfisch.android.binaryeye.graphics.getDashedBorderPaint
import kotlin.math.roundToInt
class CropImageView(context: Context, attr: AttributeSet) :
@@ -14,25 +16,20 @@ class CropImageView(context: Context, attr: AttributeSet) :
var onScan: (() -> List<Point>?)? = null
private val candidates = Candidates(context)
private val boundsPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val dots = Dots(context)
private val boundsPaint = context.getDashedBorderPaint()
private val lastMappedRect = RectF()
private val dp = context.resources.displayMetrics.density
private val padding: Int = (dp * 24f).roundToInt()
private val padding: Int = (24f * context.resources.displayMetrics.density).roundToInt()
private val onScanRunnable = Runnable {
onScan?.invoke()?.also {
candidatePoints = it
onScan?.invoke()?.let {
resultPoints = it
invalidate()
}
}
private var candidatePoints: List<Point>? = null
private var resultPoints: List<Point>? = null
init {
boundsPaint.color = ContextCompat.getColor(context, R.color.crop_bound)
boundsPaint.style = Paint.Style.STROKE
boundsPaint.strokeWidth = dp * 2f
boundsPaint.pathEffect = DashPathEffect(floatArrayOf(dp * 10f, dp * 10f), 0f)
scaleType = ScaleType.CENTER_CROP
}
@@ -85,10 +82,10 @@ class CropImageView(context: Context, attr: AttributeSet) :
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawRect(bounds, boundsPaint)
candidatePoints?.let {
candidates.draw(canvas, it)
resultPoints?.let {
dots.draw(canvas, it)
}
candidatePoints = null
resultPoints = null
val mr = mappedRect ?: return
if (mr != lastMappedRect) {
removeCallbacks(onScanRunnable)
@@ -1,20 +1,65 @@
package de.markusfisch.android.binaryeye.widget
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Point
import android.graphics.PorterDuff
import android.graphics.*
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import de.markusfisch.android.binaryeye.graphics.Candidates
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.graphics.Dots
import de.markusfisch.android.binaryeye.graphics.getBitmapFromDrawable
import de.markusfisch.android.binaryeye.graphics.getDashedBorderPaint
import kotlin.math.abs
import kotlin.math.min
import kotlin.math.roundToInt
class DetectorView : View {
private val candidates = Candidates(context)
val roi = Rect()
var updateRoi: (() -> Unit)? = null
private val dots = Dots(context)
private val invalidateRunnable: Runnable = Runnable {
marks = null
invalidate()
}
private val roiPaint = context.getDashedBorderPaint()
private val handleBitmap = resources.getBitmapFromDrawable(
R.drawable.button_crop
)
private val handleXRadius = handleBitmap.width / 2
private val handleYRadius = handleBitmap.height / 2
private val handleHome = Point()
private val handlePos = Point(-1, -1)
private val center = Point()
private val touchDown = Point()
private val distToFull: Int
private val minMoveThresholdSq: Int
private val cornerRadius: Int
private val fabHeight: Int
private val padding: Int
private var marks: List<Point>? = null
private var orientation = resources.configuration.orientation
private var handleGrabbed = false
private var handleMoved = false
private var shadeColor = 0
init {
val dp = context.resources.displayMetrics.density
distToFull = (24f * dp).roundToInt()
val minMoveThreshold = (8f * dp).roundToInt()
minMoveThresholdSq = minMoveThreshold * minMoveThreshold
cornerRadius = (8f * dp).roundToInt()
fabHeight = (92f * dp).roundToInt()
padding = (20f * dp).roundToInt()
isSaveEnabled = true
}
constructor(context: Context, attrs: AttributeSet) :
super(context, attrs)
@@ -29,10 +74,253 @@ class DetectorView : View {
postDelayed(invalidateRunnable, 500)
}
override fun onSaveInstanceState(): Parcelable? {
if (!handleMoved) {
return super.onSaveInstanceState()
}
return SavedState(super.onSaveInstanceState()).apply {
savedHandlePos.set(handlePos)
savedOrientation = orientation
}
}
override fun onRestoreInstanceState(state: Parcelable) {
super.onRestoreInstanceState(
if (state is SavedState) {
if (state.savedOrientation == orientation) {
handlePos.set(state.savedHandlePos)
} else {
handlePos.set(
state.savedHandlePos.y,
state.savedHandlePos.x
)
}
handleMoved = true
state.superState
} else {
state
}
)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
event ?: return super.onTouchEvent(event)
val x = event.x.roundToInt()
val y = event.y.roundToInt()
return when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
if (prefs.showCropHandle) {
touchDown.set(x, y)
handleGrabbed = abs(x - handlePos.x) < handleXRadius &&
abs(y - handlePos.y) < handleYRadius
handleGrabbed
} else {
false
}
}
MotionEvent.ACTION_MOVE -> {
if (handleGrabbed) {
handlePos.set(x, y)
if (distSq(handlePos, touchDown) > minMoveThresholdSq) {
handleMoved = true
}
invalidate()
true
} else {
false
}
}
MotionEvent.ACTION_CANCEL -> {
if (handleGrabbed) {
snap(x, y)
handleGrabbed = false
}
false
}
MotionEvent.ACTION_UP -> {
if (handleGrabbed) {
if (!handleMoved) {
handlePos.set(
(center.x * 1.5f).roundToInt(),
(center.y * 1.25f).roundToInt()
)
handleMoved = true
invalidate()
} else {
snap(x, y)
}
updateRoi?.invoke()
handleGrabbed = false
}
false
}
else -> super.onTouchEvent(event)
}
}
private fun snap(x: Int, y: Int) {
if (abs(x - center.x) < distToFull ||
abs(y - center.y) < distToFull
) {
handlePos.set(handleHome)
handleMoved = false
invalidate()
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
val width = right - left
val height = bottom - top
center.set(
left + (width / 2),
top + (height / 2)
)
handleHome.set(
width - handleXRadius - paddingRight - padding,
height - handleYRadius - paddingBottom - fabHeight
)
if (handlePos.x < 0) {
handlePos.set(handleHome)
}
}
override fun onDraw(canvas: Canvas) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR)
if (handleMoved) {
drawClip(canvas)
}
marks?.let {
candidates.draw(canvas, it)
dots.draw(canvas, it)
}
if (prefs.showCropHandle) {
canvas.drawBitmap(
handleBitmap,
(handlePos.x - handleXRadius).toFloat(),
(handlePos.y - handleYRadius).toFloat(),
null
)
}
}
private fun drawClip(canvas: Canvas) {
val minDist = updateClipRect()
if (minDist < 1) {
return
}
// canvas.clipRect() doesn't work reliably below KITKAT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
val radius = min(minDist / 2, cornerRadius).toFloat()
canvas.save()
canvas.clipOutPathCompat(
calculateRoundedRectPath(
roi.left.toFloat(),
roi.top.toFloat(),
roi.right.toFloat(),
roi.bottom.toFloat(),
radius,
radius
)
)
canvas.drawColor(shadeColor, PorterDuff.Mode.SRC)
canvas.restore()
} else {
canvas.drawRect(roi, roiPaint)
}
}
private fun updateClipRect(): Int {
val dx = abs(handlePos.x - center.x)
val dy = abs(handlePos.y - center.y)
val d = min(dx, dy)
shadeColor = (min(
1f,
d.toFloat() / distToFull.toFloat()
) * 128f).toInt() shl 24
roi.set(
center.x - dx,
center.y - dy,
center.x + dx,
center.y + dy
)
return d
}
internal class SavedState : BaseSavedState {
val savedHandlePos = Point()
var savedOrientation = 0
constructor(superState: Parcelable?) : super(superState)
private constructor(parcel: Parcel) : super(parcel) {
savedHandlePos.set(
parcel.readInt(),
parcel.readInt()
)
savedOrientation = parcel.readInt()
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeInt(savedHandlePos.x)
out.writeInt(savedHandlePos.y)
out.writeInt(savedOrientation)
}
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(source: Parcel) = SavedState(source)
override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)
}
}
}
}
private fun distSq(a: Point, b: Point): Int {
val dx = a.x - b.x
val dy = a.y - b.y
return dx * dx + dy * dy
}
private fun Point.set(point: Point) {
x = point.x
y = point.y
}
private fun Canvas.clipOutPathCompat(path: Path) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
clipOutPath(path)
} else {
@Suppress("DEPRECATION")
clipPath(path, Region.Op.DIFFERENCE)
}
}
private fun calculateRoundedRectPath(
left: Float,
top: Float,
right: Float,
bottom: Float,
rx: Float,
ry: Float
): Path {
val width = right - left
val height = bottom - top
val widthMinusCorners = width - 2 * rx
val heightMinusCorners = height - 2 * ry
return Path().apply {
moveTo(right, top + ry)
rQuadTo(0f, -ry, -rx, -ry)
rLineTo(-widthMinusCorners, 0f)
rQuadTo(-rx, 0f, -rx, ry)
rLineTo(0f, heightMinusCorners)
rQuadTo(0f, ry, rx, ry)
rLineTo(widthMinusCorners, 0f)
rQuadTo(rx, 0f, rx, -ry)
rLineTo(0f, -heightMinusCorners)
close()
}
}
+26
View File
@@ -0,0 +1,26 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M28,32m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0">
<aapt:attr name="android:fillColor">
<gradient
android:gradientRadius="24"
android:centerX="28"
android:centerY="32"
android:type="radial">
<item android:offset="0" android:color="#FF000000"/>
<item android:offset="1" android:color="#00000000"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M28,28m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"
android:fillColor="#B6D46F"/>
<path
android:pathData="M33,31L35,31L35,23C35,21.9 34.1,21 33,21L25,21L25,23L33,23L33,31ZM23,33L23,17L21,17L21,21L17,21L17,23L21,23L21,33C21,34.1 21.9,35 23,35L33,35L33,39L35,39L35,35L39,35L39,33L23,33Z"
android:fillColor="#FFFFFF"/>
</vector>
@@ -10,12 +10,24 @@
android:layout_height="match_parent"
android:padding="16dp"
android:orientation="vertical">
<android.support.v7.widget.SwitchCompat
android:id="@+id/show_crop_handle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/show_crop_handle"/>
<android.support.v7.widget.SwitchCompat
android:id="@+id/zoom_by_swiping"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/zoom_by_swiping"/>
<android.support.v7.widget.SwitchCompat
android:id="@+id/auto_rotate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/auto_rotate"/>
<android.support.v7.widget.SwitchCompat
android:id="@+id/try_harder"
android:layout_width="match_parent"
@@ -1,6 +1,11 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:material="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/copy_scan"
android:title="@string/copy_scan"
android:icon="@drawable/ic_action_copy"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/edit_scan"
android:title="@string/edit_scan"
+4
View File
@@ -38,7 +38,9 @@
<string name="switch_camera">Kamera wechseln</string>
<string name="history">Gespeicherte Codes</string>
<string name="preferences">Einstellungen</string>
<string name="show_crop_handle">Bereich verkleinern anzeigen</string>
<string name="zoom_by_swiping">Hoch/Runter streichen um zu zoomen</string>
<string name="auto_rotate">Vertikale Erkennung von 1D Barcodes</string>
<string name="try_harder">Suche länger und gründlicher nach Barcodes</string>
<string name="vibrate">Bei Erkennung vibrieren</string>
<string name="use_history">Codes speichern</string>
@@ -49,7 +51,9 @@
<string name="open_with_url">URL zum Öffnen unbekannter Daten</string>
<string name="really_remove_scan">Code wirklich entfernen?</string>
<string name="really_remove_all_scans">Alle Codes entfernen?</string>
<string name="really_remove_selected_scans">Alle ausgewählten Codes entfernen?</string>
<string name="clear_history">Gespeicherte Codes löschen</string>
<string name="copy_scan">Code kopieren</string>
<string name="edit_scan">Bezeichnung ändern</string>
<string name="remove_scan">Code löschen</string>
<string name="enter_name">Geben Sie eine Bezeichnung für den Code ein</string>
+98
View File
@@ -0,0 +1,98 @@
<resources>
<string name="no_camera_no_fun">Esta aplicación no tendría sentido usar sin acceso a la cámara. Adiós.</string>
<string name="camera_error">Sin acceso a la cámara, por favor, inténtelo de nuevo</string>
<string name="scan_code">Escanear código de barras</string>
<string name="compose_barcode">Componer código de barras</string>
<string name="decode_barcode">Decodificar código de barras</string>
<string name="content">Contenido</string>
<string name="binary_data">(binario)</string>
<plurals name="barcode_info">
<item quantity="one">%2$d caracter, %1$s</item>
<item quantity="other">%2$d caracteres, %1$s</item>
</plurals>
<string name="error_correction_level">Nivel de corrección de errores</string>
<string name="issue_number">Número de emisión</string>
<string name="orientation">Orientación</string>
<string name="other_meta_data">Metadatos</string>
<string name="pdf417_extra_metadata">Metadatos PDF417</string>
<string name="possible_country">Posible país de fabricación</string>
<string name="suggested_price">Precio sugerido</string>
<string name="upc_ean_extension">UPC EAN extension</string>
<string name="toggle_flash">Activar la linterna</string>
<string name="share">Compartir</string>
<string name="copy_to_clipboard">Copiar al portapapeles</string>
<string name="copied_to_clipboard">Copiado al portapapeles</string>
<string name="copied_password_to_clipboard">Contraseña copiada al portapapeles</string>
<string name="open_url">Abrir URL</string>
<string name="cannot_resolve_action">Ninguna aplicación puede abrir esto</string>
<string name="pick_search_engine">Elija un motor de búsqueda</string>
<string name="format">Formato</string>
<string name="size">Tamaño en píxeles</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Introduzca el contenido aquí</string>
<string name="encode">CODIFICAR</string>
<string name="error_no_content">Contenido no disponible</string>
<string name="error_encoding_barcode">No pudo generar el código de barras</string>
<string name="view_barcode">Ver código de barras</string>
<string name="info">Información</string>
<string name="switch_camera">Cambiar cámara</string>
<string name="history">Historial</string>
<string name="preferences">Configuración</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="zoom_by_swiping">Aumento de cámara al deslizar hacia arriba/abajo</string>
<string name="auto_rotate">Reconocer los códigos de barras 1D verticalmente</string>
<string name="try_harder">Optimizar el lector para la precisión, no la velocidad</string>
<string name="vibrate">Vibración en la detección</string>
<string name="use_history">Guardar historial de escaneados</string>
<string name="ignore_consecutive_duplicates">No guardar duplicados consecutivos</string>
<string name="open_immediately">Saltar la inspección y abrir el contenido inmediatamente</string>
<string name="show_meta_data">Mostrar metadatos</string>
<string name="show_hex_dump">Mostrar volcado hexagonal</string>
<string name="open_with_url">Abrir datos desconocidos con URL</string>
<string name="really_remove_scan">¿Realmente desea borrar el escaneo seleccionado?</string>
<string name="really_remove_all_scans">¿Realmente desea borrar todo lo escaneado?</string>
<string name="really_remove_selected_scans">¿Realmente desea borrar los escaneos seleccionados?</string>
<string name="clear_history">Borrar historial</string>
<string name="copy_scan">Copiar escaneo</string>
<string name="edit_scan">Editar etiqueta</string>
<string name="remove_scan">Borrar escaneo</string>
<string name="enter_name">Introduzca un nombre para describir el escaneo</string>
<string name="enter_name_hint">Nombre del escaneo</string>
<string name="no_barcode_found">No se encontró ningún código de barras</string>
<string name="pick_list_separator">¿Cómo separar los elementos de la lista?</string>
<string name="separator_line_break">Línea separadora</string>
<string name="separator_ruler">Regla</string>
<string name="save_as_file_name">¿Desea guardar el archivo en Descargas?</string>
<string name="file_name">Nombre del archivo</string>
<string name="error_saving_file">No se puede guardar el archivo</string>
<string name="error_file_exists">El archivo ya existe</string>
<string name="connect_to_wifi">Conectar a WiFi</string>
<string name="wifi_config_failed">No se pudo configurar el WiFi</string>
<string name="wifi_added">WiFi añadido</string>
<string name="sms_send">Enviar SMS</string>
<string name="sms_error">No se puedo enviar SMS</string>
<string name="tel_dial">Marcar número</string>
<string name="tel_error">No se pudo marcar el número</string>
<string name="mail_send">Enviar correo</string>
<string name="mail_error">No se pudo enviar correo</string>
<string name="vcard_add">Añadir a contactos</string>
<string name="vcard_failed">No se pudo añadir a los contactos</string>
<string name="vevent_add">Añadir se pudo calendario</string>
<string name="vevent_failed">No se pudo añadir al calendario</string>
<string name="otpauth_add">Añadir 2FA</string>
<string name="search_web">Buscar en la web</string>
<string name="search_scan">Buscar escaneos</string>
<string name="export_to_file">Exportar a archivo</string>
<string name="export_as">¿Dónde desea exportar?</string>
<string name="export_csv_comma">CSV con comas</string>
<string name="export_csv_semicolon">CSV con punto y coma</string>
<string name="export_json">Exportar como JSON</string>
<string name="export_database">Exportar base de datos SQLite</string>
<string name="saved_in_downloads">Guardado en Descargas</string>
<string name="rotate_image_cw">Rotar la imagen en el sentido de las agujas del reloj</string>
<string name="pick_file">Seleccionar archivo de imagen</string>
<string name="pick_code_to_scan">Escoge el código para escanear</string>
<string name="shortcut_decode">Escanear un código de barras</string>
<string name="shortcut_encode">Crear un código de barras</string>
<string name="shortcut_preferences">Ajustes</string>
</resources>
+4
View File
@@ -38,7 +38,9 @@
<string name="switch_camera">Changer de camera</string>
<string name="history">Historique</string>
<string name="preferences">Preferences</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
<string name="try_harder">Optimize reader for accuracy, not speed</string>
<string name="vibrate">Vibrate on detection</string>
<string name="use_history">Sauvegarder l\'histoire des scans</string>
@@ -49,7 +51,9 @@
<string name="open_with_url">Open unknown data with URL</string>
<string name="really_remove_scan">Supprimer le scan?</string>
<string name="really_remove_all_scans">Supprimer tous les scans?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
<string name="clear_history">Clear history</string>
<string name="copy_scan">Copy scan</string>
<string name="edit_scan">Edit label</string>
<string name="remove_scan">Remove scan</string>
<string name="enter_name">Enter a name to describe the scan</string>
+4
View File
@@ -38,7 +38,9 @@
<string name="switch_camera">Kamera átkapcsolása</string>
<string name="history">Előzmények</string>
<string name="preferences">Beállítások</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
<string name="try_harder">Optimize reader for accuracy, not speed</string>
<string name="vibrate">Vibrate on detection</string>
<string name="use_history">Beolvasási előzmények mentése</string>
@@ -49,7 +51,9 @@
<string name="open_with_url">Ismeretlen adat megnyitása URL-lel</string>
<string name="really_remove_scan">Valóban eltávolítja a beolvasást?</string>
<string name="really_remove_all_scans">Valóban eltávolítja az összes beolvasását?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
<string name="clear_history">Előzmények törlése</string>
<string name="copy_scan">Copy scan</string>
<string name="edit_scan">Edit label</string>
<string name="remove_scan">Remove scan</string>
<string name="enter_name">Enter a name to describe the scan</string>
+5
View File
@@ -8,6 +8,7 @@
<string name="binary_data">(data biner)</string>
<plurals name="barcode_info">
<item quantity="other">%1$s, %2$d karakter</item>
</plurals>
<string name="error_correction_level">Level koreksi kesalahan</string>
<string name="issue_number">Nomor pemindaian</string>
@@ -37,7 +38,9 @@
<string name="switch_camera">Ganti kamera</string>
<string name="history">Riwayat</string>
<string name="preferences">Preferensi</string>
<string name="show_crop_handle">Tampilkan pembatas pemotongan</string>
<string name="zoom_by_swiping">Perbesar kamera dengan menggeser ke atas/bawah</string>
<string name="auto_rotate">Kenali barcode 1D secara vertikal</string>
<string name="try_harder">Optimalkan akurasi, bukan kecepatan</string>
<string name="vibrate">Bergetar saat mendeteksi</string>
<string name="use_history">Simpan riwayat pemindaian</string>
@@ -48,7 +51,9 @@
<string name="open_with_url">Buka data tak dikenal dengan URL</string>
<string name="really_remove_scan">Yakin menghapus pemindaian?</string>
<string name="really_remove_all_scans">Yakin menghapus semua pemindaian?</string>
<string name="really_remove_selected_scans">Yakin menghapus semua pemindaian?</string>
<string name="clear_history">Hapus riwayat</string>
<string name="copy_scan">Salin pemindaian</string>
<string name="edit_scan">Sunting label</string>
<string name="remove_scan">Buang pindaian</string>
<string name="enter_name">Masukkan keterangan pemindaian</string>
+4
View File
@@ -38,7 +38,9 @@
<string name="switch_camera">Cambia fotocamera</string>
<string name="history">Cronologia</string>
<string name="preferences">Preferenze</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="zoom_by_swiping">Zoom fotocamera scorrendo su/giù</string>
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
<string name="try_harder">Ottimizza il lettore per accuratezza, non velocità</string>
<string name="vibrate">Vibra quando rilevato</string>
<string name="use_history">Salva cronologia delle scansioni</string>
@@ -49,7 +51,9 @@
<string name="open_with_url">Apri dati sconosciuti con 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">Really remove selected scans?</string>
<string name="clear_history">Pulisci cronologia</string>
<string name="copy_scan">Copy scan</string>
<string name="edit_scan">Modifica etichetta</string>
<string name="remove_scan">Rimuovi scansione</string>
<string name="enter_name">Inserisci un nome per descrivere la scansione</string>
+4
View File
@@ -38,7 +38,9 @@
<string name="switch_camera">Camera wisselen</string>
<string name="history">Geschiedenis</string>
<string name="preferences">Voorkeuren</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
<string name="try_harder">Optimize reader for accuracy, not speed</string>
<string name="vibrate">Vibrate on detection</string>
<string name="use_history">Scan-geschiedenis behouden</string>
@@ -48,7 +50,9 @@
<string name="show_hex_dump">Show hex dump</string>
<string name="really_remove_scan">Scan echt verwijderen?</string>
<string name="really_remove_all_scans">Echt alle scans verwijderen?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
<string name="clear_history">Geschiedenis wissen</string>
<string name="copy_scan">Copy scan</string>
<string name="edit_scan">Edit label</string>
<string name="remove_scan">Remove scan</string>
<string name="enter_name">Enter a name to describe the scan</string>
@@ -38,7 +38,9 @@
<string name="switch_camera">Trocar câmera</string>
<string name="history">Histórico</string>
<string name="preferences">Preferências</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
<string name="try_harder">Optimize reader for accuracy, not speed</string>
<string name="vibrate">Vibrate on detection</string>
<string name="use_history">Salvar histórico de escaneamento</string>
@@ -49,7 +51,9 @@
<string name="open_with_url">Abrir dados desconhecidos com URL</string>
<string name="really_remove_scan">Realmente deseja remover o escaneamento?</string>
<string name="really_remove_all_scans">Realmente deseja remover todos escaneamentos?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
<string name="clear_history">Limpar histórico</string>
<string name="copy_scan">Copy scan</string>
<string name="edit_scan">Edit label</string>
<string name="remove_scan">Remove scan</string>
<string name="enter_name">Enter a name to describe the scan</string>
@@ -42,7 +42,9 @@
<string name="switch_camera">Переключить камеру</string>
<string name="history">История</string>
<string name="preferences">Настройки</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="zoom_by_swiping">Масштабировать движением пальцем вверх/вниз</string>
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
<string name="try_harder">Оптимизировать считывание на точность, а не скорость</string>
<string name="vibrate">Вибрировать при обнаружении</string>
<string name="use_history">Сохранять историю сканирования</string>
@@ -53,7 +55,9 @@
<string name="open_with_url">Открыть неизвестные данные с помощью URL</string>
<string name="really_remove_scan">Действительно удалить сканирование?</string>
<string name="really_remove_all_scans">Действительно удалить все сканирования?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
<string name="clear_history">Очистить историю</string>
<string name="copy_scan">Copy scan</string>
<string name="edit_scan">Редактировать</string>
<string name="remove_scan">Удалить сканирование</string>
<string name="enter_name">Укажите название сканирования</string>
@@ -37,7 +37,9 @@
<string name="switch_camera">切换摄像头</string>
<string name="history">历史</string>
<string name="preferences">首选项</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
<string name="try_harder">Optimize reader for accuracy, not speed</string>
<string name="vibrate">Vibrate on detection</string>
<string name="use_history">保存扫描历史</string>
@@ -48,7 +50,9 @@
<string name="open_with_url">使用URL打开未知数据</string>
<string name="really_remove_scan">确定删除记录?</string>
<string name="really_remove_all_scans">确定删除全部记录?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
<string name="clear_history">删除记录</string>
<string name="copy_scan">Copy scan</string>
<string name="edit_scan">Edit label</string>
<string name="remove_scan">Remove scan</string>
<string name="enter_name">Enter a name to describe the scan</string>
+4
View File
@@ -36,7 +36,9 @@
<string name="info">資訊</string>
<string name="switch_camera">切換相機</string>
<string name="history">歷史</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
<string name="try_harder">Optimize reader for accuracy, not speed</string>
<string name="vibrate">Vibrate on detection</string>
<string name="use_history">儲存掃描歷史</string>
@@ -46,7 +48,9 @@
<string name="show_hex_dump">Show hex dump</string>
<string name="really_remove_scan">真的要移除掃描記錄?</string>
<string name="really_remove_all_scans">真的要移除所有掃描記錄?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
<string name="clear_history">清除記錄</string>
<string name="copy_scan">Copy scan</string>
<string name="edit_scan">Edit label</string>
<string name="remove_scan">Remove scan</string>
<string name="enter_name">Enter a name to describe the scan</string>
+1 -1
View File
@@ -9,5 +9,5 @@
<color name="selected_row">#222</color>
<color name="separator">#333</color>
<color name="crop_bound">#fff</color>
<color name="candidate">#80b6d46f</color>
<color name="dot">#80b6d46f</color>
</resources>
+4
View File
@@ -38,7 +38,9 @@
<string name="switch_camera">Switch camera</string>
<string name="history">History</string>
<string name="preferences">Preferences</string>
<string name="show_crop_handle">Show cropping limiter</string>
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
<string name="try_harder">Optimize reader for accuracy, not speed</string>
<string name="vibrate">Vibrate on detection</string>
<string name="use_history">Save scan history</string>
@@ -49,7 +51,9 @@
<string name="open_with_url">Open unknown data with URL</string>
<string name="really_remove_scan">Really remove scan?</string>
<string name="really_remove_all_scans">Really remove all scans?</string>
<string name="really_remove_selected_scans">Really remove selected scans?</string>
<string name="clear_history">Clear history</string>
<string name="copy_scan">Copy scan</string>
<string name="edit_scan">Edit label</string>
<string name="remove_scan">Remove scan</string>
<string name="enter_name">Enter a name to describe the scan</string>
@@ -0,0 +1,93 @@
package de.markusfisch.android.binaryeye.actions.vtype
import de.markusfisch.android.binaryeye.simpleFail
import junit.framework.TestCase.*
import org.junit.Test
class VTypeTest {
@Test
fun vcard() {
val content = """BEGIN:VCARD
N:Doe;John
END:VCARD"""
assertEquals("VCARD", VTypeParser.parseVType(content))
val info = VTypeParser.parseMap(content)
info["N"]?.singleOrNull()?.also {
assertEquals("Doe;John", it.value)
}
}
@Test
fun vcalendar() {
val content = """BEGIN:VCALENDAR
BEGIN:VEVENT
SUMMARY:foo
DTSTART:20080504T123456Z
DTEND:20080505T234555Z
END:VEVENT
END:VCALENDAR"""
assertEquals("VCALENDAR", VTypeParser.parseVType(content))
val info = VTypeParser.parseMap(content)
info["SUMMARY"]?.singleOrNull()?.also {
assertEquals("foo", it.value)
}
info["DTSTART"]?.singleOrNull()?.also {
assertEquals("20080504T123456Z", it.value)
}
info["DTEND"]?.singleOrNull()?.also {
assertEquals("20080505T234555Z", it.value)
}
}
@Test
fun vevent() {
val content = """BEGIN:VEVENT
SUMMARY:foo
DTSTART:20080504T123456Z
DTEND:20080505T234555Z
END:VEVENT"""
assertEquals("VEVENT", VTypeParser.parseVType(content))
val info = VTypeParser.parseMap(content)
info["SUMMARY"]?.singleOrNull()?.also {
assertEquals("foo", it.value)
}
info["DTSTART"]?.singleOrNull()?.also {
assertEquals("20080504T123456Z", it.value)
}
info["DTEND"]?.singleOrNull()?.also {
assertEquals("20080505T234555Z", it.value)
}
}
@Test
fun veventWithCarriageReturn() {
val content = """BEGIN:VEVENT${'\r'}
SUMMARY:foo${'\r'}
DTSTART:20080504T123456Z${'\r'}
DTEND:20080505T234555Z${'\r'}
END:VEVENT"""
assertEquals("VEVENT", VTypeParser.parseVType(content))
val info = VTypeParser.parseMap(content)
info["SUMMARY"]?.singleOrNull()?.also {
assertEquals("foo", it.value)
}
info["DTSTART"]?.singleOrNull()?.also {
assertEquals("20080504T123456Z", it.value)
}
info["DTEND"]?.singleOrNull()?.also {
assertEquals("20080505T234555Z", it.value)
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
buildscript {
ext {
kotlin_version = '1.3.72'
tools_version = '3.6.3'
tools_version = '4.0.0'
build_tools_version = '29.0.3'
sdk_version = 29
support_version = '25.3.1'
@@ -0,0 +1,10 @@
Funciona en orientación vertical y horizontal, puede leer códigos invertidos,
interfaz Material Design y también genera códigos de barras.
Utiliza la biblioteca de escaneo de códigos de barras ZXing ("Zebra Crossing").
Los formatos de código de barras soportados son: AZTEC, CODABAR, CODE 39, CODE 93, CODE 128,
DATA MATRIX, EAN 8, EAN 13, ITF, PDF417, QR CODE, RSS 14, RSS EXPANDED,
UPC A, UPC E y UPC EAN EXTENSION.
Esto es de código abierto:
https://github.com/markusfisch/BinaryEye
@@ -0,0 +1 @@
Otro escáner de código de barras para Android. Gratis, sin anuncios y de código abierto.
+1
View File
@@ -0,0 +1 @@
Binary Eye
+2 -2
View File
@@ -1,6 +1,6 @@
#Tue May 07 14:37:45 CEST 2019
#Sun May 31 13:11:56 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<svg height="56px" viewBox="0 0 56 56" width="56px" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient gradientUnits="userSpaceOnUse" cx="28" cy="30" r="24" id="gradient-0" gradientTransform="matrix(1, 0, 0, 1, 0, 2)">
<stop offset="0" style="stop-color: rgb(0, 0, 0);"/>
<stop offset="1" style="stop-color: rgb(0, 0, 0); stop-opacity: 0;"/>
</radialGradient>
</defs>
<circle style="filter: none; fill: url(#gradient-0);" cx="28" cy="32" r="24"/>
<circle style="fill: rgb(182, 212, 111); filter: none;" cx="28" cy="28" r="24"/>
<path d="M 33 31 L 35 31 L 35 23 C 35 21.9 34.1 21 33 21 L 25 21 L 25 23 L 33 23 L 33 31 Z M 23 33 L 23 17 L 21 17 L 21 21 L 17 21 L 17 23 L 21 23 L 21 33 C 21 34.1 21.9 35 23 35 L 33 35 L 33 39 L 35 39 L 35 35 L 39 35 L 39 33 L 23 33 Z" style="fill: rgb(255, 255, 255);"/>
</svg>

After

Width:  |  Height:  |  Size: 885 B