Compare commits

...
42 Commits
Author SHA1 Message Date
Markus Fisch af528df448 Advance version number to 1.31.0 2020-06-07 16:19:23 +02:00
Markus Fisch a9b5771d57 Fix initializing preprocessor for device tests 2020-06-07 16:19:08 +02:00
Markus Fisch a161e8fc0f Remove app name from polish translation
Because the app name isn't part of the default locale but the
same for all languages. Lint will fail otherwise.
2020-06-07 16:07:54 +02:00
Markus Fisch ec17d07695 Reset ROI when handle snaps back in place
And make sure ROI dimensions are never smaller than 4.
2020-06-07 15:51:53 +02:00
Karol KosekandGitHub be2fc2f559 Add Polish translation 2020-06-07 15:19:00 +02:00
Markus Fisch de3aa752ff Refactor data namespace to database
It's better to be explicit.
2020-06-04 09:19:50 +02:00
Markus Fisch 411cbb243f Clean up app initialization
This got a bit mixed up by all the special RenderScript handling.
2020-06-04 09:13:33 +02:00
Markus Fisch f76c1521fe Stop detection when ROI is modified
Avoids accidently detecting a barcode while a user is about
to change the region of interest.
2020-06-03 22:49:01 +02:00
Markus Fisch 9b9224b100 Optimize crop button drawable with avocado 2020-06-03 22:39:42 +02:00
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
52 changed files with 1035 additions and 114 deletions
+19
View File
@@ -1,5 +1,24 @@
# Change Log
## 1.31.0
* Stop detection while region of interest is modified
* Fix resetting region of interest
* Add Polish translation
## 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
+2 -2
View File
@@ -9,8 +9,8 @@ android {
minSdkVersion 9
targetSdkVersion sdk_version
versionCode 65
versionName '1.29.0'
versionCode 68
versionName '1.31.0'
// it's recommended to set this value to the lowest API level
// able to provide all the functionality
@@ -34,7 +34,8 @@ class PreprocessorTest {
val preprocessor = Preprocessor(
InstrumentationRegistry.getTargetContext(),
frameWidth.toInt(),
frameHeight.toInt()
frameHeight.toInt(),
null
)
val outWidth: Int
val outHeight: Int
@@ -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
@@ -21,11 +24,12 @@ import com.google.zxing.ResultMetadataType
import com.google.zxing.ResultPointCallback
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.*
import de.markusfisch.android.binaryeye.data.Scan
import de.markusfisch.android.binaryeye.database.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
@@ -51,14 +55,16 @@ 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 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 decoding = true
private var returnResult = false
private var frontFacing = false
private var fallbackBuffer: IntArray? = null
@@ -107,13 +113,21 @@ 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.onRoiChange = {
decoding = false
}
detectorView.onRoiChanged = {
decoding = true
recreatePreprocessor = true
}
detectorView.setPaddingFromWindowInsets()
if (intent?.action == Intent.ACTION_SEND &&
intent.type == "text/plain"
) {
@@ -171,11 +185,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)
}
@@ -187,7 +203,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 -> {
@@ -222,6 +238,10 @@ class CameraActivity : AppCompatActivity() {
}
}
private fun createBarcode() {
startActivity(MainActivity.getEncodeIntent(this))
}
private fun switchCamera() {
closeCamera()
frontFacing = frontFacing xor true
@@ -328,14 +348,14 @@ class CameraActivity : AppCompatActivity() {
}
override fun onCameraReady(camera: Camera) {
val frameWidth = cameraView.frameWidth
val frameHeight = cameraView.frameHeight
val frameOrientation = cameraView.frameOrientation
var decoding = true
// reset preprocessor to make sure it fits the current
// 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
decoding = true
camera.setPreviewCallback { frameData, _ ->
if (decoding) {
decodeFrame(
@@ -406,11 +426,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() }
}
}
@@ -447,6 +469,10 @@ class CameraActivity : AppCompatActivity() {
isPortrait(frameOrientation)
}
return try {
if (recreatePreprocessor) {
resetPreProcessor()
recreatePreprocessor = false
}
val pp = preprocessor ?: createPreprocessorAndMapping(
frameWidth,
frameHeight,
@@ -481,26 +507,86 @@ class CameraActivity : AppCompatActivity() {
frameHeight: Int,
frameOrientation: Int
): Preprocessor {
val frameRoi = calculateFrameRect(
frameWidth,
frameHeight,
frameOrientation
)
val pp = Preprocessor(
this,
frameWidth,
frameHeight
frameHeight,
frameRoi
)
val viewRect = if (frameRoi != null) {
detectorView.roi
} else {
cameraView.previewRect
}
nativeMapping = frameToView(
pp.outWidth,
pp.outHeight,
frameOrientation,
cameraView.previewRect
viewRect
)
rotatedMapping = frameToView(
pp.outHeight,
pp.outWidth,
(frameOrientation - 90 + 360) % 360,
cameraView.previewRect
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()
@@ -525,6 +611,7 @@ class CameraActivity : AppCompatActivity() {
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"
}
}
@@ -11,7 +11,7 @@ import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.colorSystemAndToolBars
import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.setFragment
import de.markusfisch.android.binaryeye.data.Scan
import de.markusfisch.android.binaryeye.database.Scan
import de.markusfisch.android.binaryeye.fragment.DecodeFragment
import de.markusfisch.android.binaryeye.fragment.EncodeFragment
import de.markusfisch.android.binaryeye.fragment.HistoryFragment
@@ -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,34 +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)
@@ -8,7 +8,7 @@ import android.view.ViewGroup
import android.widget.CursorAdapter
import android.widget.TextView
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.database.Database
class ScansAdapter(context: Context, cursor: Cursor) :
CursorAdapter(context, cursor, false) {
@@ -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,
@@ -2,7 +2,7 @@ package de.markusfisch.android.binaryeye.app
import android.app.Application
import android.support.v8.renderscript.RenderScript
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.database.Database
import de.markusfisch.android.binaryeye.preference.Preferences
val db = Database()
@@ -11,12 +11,11 @@ val prefs = Preferences()
class BinaryEyeApp : Application() {
override fun onCreate() {
super.onCreate()
db.open(this)
prefs.init(this)
if (prefs.forceCompat) {
RenderScript.forceCompat()
}
db.open(this)
}
}
@@ -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) {
@@ -1,4 +1,4 @@
package de.markusfisch.android.binaryeye.data
package de.markusfisch.android.binaryeye.database
import android.content.Context
import android.database.Cursor
@@ -1,4 +1,4 @@
package de.markusfisch.android.binaryeye.data
package de.markusfisch.android.binaryeye.database
import android.content.ContentValues
import android.content.Context
@@ -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) {
@@ -1,4 +1,4 @@
package de.markusfisch.android.binaryeye.data
package de.markusfisch.android.binaryeye.database
import android.app.Activity
import android.os.Environment
@@ -1,4 +1,4 @@
package de.markusfisch.android.binaryeye.data
package de.markusfisch.android.binaryeye.database
import android.content.Context
import android.database.Cursor
@@ -1,4 +1,4 @@
package de.markusfisch.android.binaryeye.data
package de.markusfisch.android.binaryeye.database
import android.os.Parcel
import android.os.Parcelable
@@ -15,7 +15,7 @@ import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.ActionRegistry
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
import de.markusfisch.android.binaryeye.app.*
import de.markusfisch.android.binaryeye.data.Scan
import de.markusfisch.android.binaryeye.database.Scan
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
import de.markusfisch.android.binaryeye.widget.toast
import kotlinx.coroutines.CoroutineScope
@@ -21,10 +21,10 @@ import android.widget.ListView
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.adapter.ScansAdapter
import de.markusfisch.android.binaryeye.app.*
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.data.exportCsv
import de.markusfisch.android.binaryeye.data.exportDatabase
import de.markusfisch.android.binaryeye.data.exportJson
import de.markusfisch.android.binaryeye.database.Database
import de.markusfisch.android.binaryeye.database.exportCsv
import de.markusfisch.android.binaryeye.database.exportDatabase
import de.markusfisch.android.binaryeye.database.exportJson
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
import de.markusfisch.android.binaryeye.view.useVisibility
import de.markusfisch.android.binaryeye.widget.toast
@@ -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,6 +12,7 @@ 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
@@ -36,6 +37,11 @@ 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()
@@ -93,6 +99,7 @@ class PreferencesFragment : Fragment() {
override fun onStop() {
super.onStop()
prefs.showCropHandle = showCropHandleSwitch.isChecked
prefs.zoomBySwiping = zoomBySwipingSwitch.isChecked
prefs.autoRotate = autoResizeSwitch.isChecked
prefs.tryHarder = tryHarderSwitch.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
}
@@ -36,7 +36,7 @@ fun frameToView(
viewRect.top
)
inline fun isPortrait(orientation: Int) = orientation == 90 || orientation == 270
fun isPortrait(orientation: Int) = orientation == 90 || orientation == 270
data class Mapping(
val frameWidth: Int,
@@ -7,6 +7,11 @@ 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)
@@ -14,12 +19,16 @@ class Preferences {
}
var autoRotate = false
set(value) {
apply(AUTO_ROTATE, 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
@@ -76,6 +85,7 @@ 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)
@@ -121,6 +131,7 @@ 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"
@@ -1,8 +1,10 @@
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.max
import kotlin.math.roundToInt
private const val SCALE_FACTOR = .75f
@@ -10,7 +12,8 @@ private const val SCALE_FACTOR = .75f
class Preprocessor(
context: Context,
width: Int,
height: Int
height: Int,
private val roi: Rect?
) {
var outWidth = 0
var outHeight = 0
@@ -21,6 +24,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
@@ -39,8 +44,36 @@ class Preprocessor(
Allocation.USAGE_SCRIPT
)
outWidth = (width * SCALE_FACTOR).roundToInt()
outHeight = (height * SCALE_FACTOR).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
// make sure the dimensions are always greater than 4
outWidth = max(4, outWidth)
outHeight = max(4, outHeight)
} else {
outWidth = (width * SCALE_FACTOR).roundToInt()
outHeight = (height * SCALE_FACTOR).roundToInt()
}
resizedType = Type.createXY(
rs,
@@ -72,6 +105,10 @@ class Preprocessor(
yuvType = null
yuvAlloc?.destroy()
yuvAlloc = null
roiType?.destroy()
roiType = null
roiAlloc?.destroy()
roiAlloc = null
resizedType?.destroy()
resizedType = null
resizedAlloc?.destroy()
@@ -105,7 +142,19 @@ class Preprocessor(
private fun resize(frame: ByteArray) {
yuvAlloc?.copyFrom(frame)
resizeScript.setInput(yuvAlloc)
resizeScript.setInput(
if (roi != null) {
roiAlloc?.copy2DRangeFrom(
0, 0,
roi.width(), roi.height(),
yuvAlloc,
roi.left, roi.top
)
roiAlloc
} else {
yuvAlloc
}
)
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,66 @@
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 onRoiChange: (() -> Unit)? = null
var onRoiChanged: (() -> 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 +75,257 @@ 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
if (handleGrabbed) {
onRoiChange?.invoke()
}
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)
}
onRoiChanged?.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
roi.set(0, 0, 0, 0)
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()
}
}
+12
View File
@@ -0,0 +1,12 @@
<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="M4 32a24 24 0 1 1 48 0 24 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="M4 28a24 24 0 1 1 48 0 24 24 0 1 1-48 0" android:fillColor="#B6D46F"/>
<path android:pathData="M33 31h2v-8c0-1.1-0.9-2-2-2h-8v2h8v8zm-10 2V17h-2v4h-4v2h4v10c0 1.1 0.9 2 2 2h10v4h2v-4h4v-2H23z" android:fillColor="#FFFFFF"/>
</vector>
@@ -10,6 +10,12 @@
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"
@@ -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"
+3
View File
@@ -38,6 +38,7 @@
<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>
@@ -50,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>
+3
View File
@@ -38,6 +38,7 @@
<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>
@@ -50,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>
+3
View File
@@ -38,6 +38,7 @@
<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>
@@ -50,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 -1
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,8 +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">Recognize 1D barcodes vertically</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>
@@ -49,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>
+3
View File
@@ -38,6 +38,7 @@
<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>
@@ -50,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>
+3
View File
@@ -38,6 +38,7 @@
<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>
@@ -49,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>
+95
View File
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="open_url">Otwórz url</string>
<string name="content">Zawartość</string>
<string name="copied_to_clipboard">Skopiowano do schowka</string>
<string name="copy_to_clipboard">Skopiuj do schowka</string>
<string name="info">Informacje</string>
<string name="vcard_add">Dodaj do kontaktów</string>
<string name="vevent_add">Dodaj do kalendarza</string>
<string name="shortcut_preferences">Ustawienia</string>
<string name="share">Udostępnij</string>
<string name="error_saving_file">Nie można zapisać pliku</string>
<string name="clear_history">Wyczyść historię</string>
<string name="compose_barcode">Utwórz kod kreskowy</string>
<string name="history">Historia</string>
<string name="format">Format</string>
<string name="export_to_file">Eksportuj do pliku</string>
<string name="file_name">Nazwa pliku</string>
<string name="export_json">Eksportuj jako JSON</string>
<string name="pick_search_engine">Wybierz wyszukiwarkę</string>
<string name="pick_file">Wybierz plik obrazu</string>
<string name="pick_code_to_scan">Wybierz kod do zeskanowania</string>
<string name="sms_send">Wyślij SMS</string>
<string name="mail_send">Wyślij e-mail</string>
<string name="saved_in_downloads">Zapisano w pobranych</string>
<string name="separator_ruler">Linijka</string>
<string name="scan_code">Skanuj kod</string>
<string name="error_correction_level">Poziom korekcji błędów</string>
<string name="export_csv_comma">CSV z przecinkami</string>
<string name="export_csv_semicolon">CSV z średnikami</string>
<string name="shortcut_encode">Stwórz kod kreskowy</string>
<string name="error_file_exists">Plik już istnieje</string>
<string name="no_barcode_found">Nie znaleziono kodu kreskowego</string>
<string name="no_camera_no_fun">Ta aplikacja nie ma żadnego sensu bez dostępu do kamery. Do widzenia.</string>
<string name="tel_dial">Wybierz numer</string>
<string name="connect_to_wifi">Połącz z Wi-Fi</string>
<string name="vcard_failed">Nie można dodać do kontaktów</string>
<string name="tel_error">Nie można wybrać numeru</string>
<string name="sms_error">Nie można wysłać SMS-a</string>
<string name="mail_error">Nie można wysłać e-maila</string>
<string name="rotate_image_cw">Obróć obraz zgodnie z ruchem wskazówek zegara</string>
<string name="edit_scan">Edytuj etykietę</string>
<string name="enter_name_hint">Nazwa skanu</string>
<string name="camera_error">Nie można uzyskać dostępu do kamery, spróbuj ponownie</string>
<string name="copy_scan">Skopiuj skan</string>
<string name="vevent_failed">Nie można dodać do kalendarza</string>
<string name="wifi_config_failed">Nie można skonfigurować Wi-Fi</string>
<string name="size">Rozmiar w pikselach</string>
<string name="export_as">Eksportuj jako?</string>
<string name="really_remove_all_scans">Czy naprawdę usunąć wszystkie skany?</string>
<string name="really_remove_scan">Czy naprawdę usunąć skan?</string>
<string name="really_remove_selected_scans">Czy naprawdę usunąć wybrane skany?</string>
<string name="binary_data">(dane binarne)</string>
<string name="show_hex_dump">Pokazuj zrzut szesnastkowy</string>
<string name="show_meta_data">Pokazuj metadane</string>
<string name="switch_camera">Przełącz aparat</string>
<string name="toggle_flash">Przełącz lampę błyskową</string>
<string name="wifi_added">Wi-Fi dodane</string>
<string name="zoom_by_swiping">Przybliżaj kamerę, przesuwając w górę/dół</string>
<string name="view_barcode">Wyświetl kod kreskowy</string>
<string name="orientation">Orientacja</string>
<string name="error_encoding_barcode">Nie można utworzyć kodu kreskowego</string>
<string name="otpauth_add">Dodaj 2FA</string>
<string name="other_meta_data">Metadane</string>
<string name="remove_scan">Usuń skan</string>
<string name="separator_line_break">Przerwanie linii</string>
<string name="shortcut_decode">Zeskanuj kod kreskowy</string>
<string name="use_history">Zapisuj historię skanowania</string>
<string name="vibrate">Wibruj po wykryciu</string>
<string name="open_immediately">Pomiń kontrolę i natychmiast otwórz zawartość</string>
<string name="show_crop_handle">Pokazuj ogranicznik przycinania</string>
<string name="search_web">Szukaj w sieci</string>
<string name="search_scan">Szukaj skanu</string>
<string name="export_database">Eksportuj do bazy danych SQLite</string>
<string name="ignore_consecutive_duplicates">Nie zapisuj kolejnych duplikatów</string>
<string name="open_with_url">Otwieraj nieznane dane za pomocą adresu URL</string>
<string name="copied_password_to_clipboard">Hasło skopiowane do schowka</string>
<string name="pdf417_extra_metadata">Metadane PDF417</string>
<string name="suggested_price">Sugerowana cena</string>
<string name="preferences">Ustawienia</string>
<string name="decode_barcode">Dekoduj kod kreskowy</string>
<string name="upc_ean_extension">Dodatek UPC EAN</string>
<string name="possible_country">Możliwy kraj produkcji</string>
<string name="encode">ZAKODUJ</string>
<string name="error_no_content">Brak zawartości</string>
<string name="try_harder">Zoptymalizuj czytnik pod kątem dokładności, a nie prędkości</string>
<string name="cannot_resolve_action">Żadna aplikacja nie może tego otworzyć</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Wprowadź zawartość tutaj</string>
<string name="pick_list_separator">Jak oddzielić elementy listy?</string>
<string name="enter_name">Wprowadź nazwę opisującą skan</string>
<string name="auto_rotate">Rozpoznawaj kody kreskowe 1D pionowo</string>
<string name="save_as_file_name">Jak zapisać plik w pobranych?</string>
<string name="issue_number">Numer wydania</string>
</resources>
@@ -38,6 +38,7 @@
<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>
@@ -50,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,6 +42,7 @@
<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>
@@ -54,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,6 +37,7 @@
<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>
@@ -49,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>
+3
View File
@@ -36,6 +36,7 @@
<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>
@@ -47,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>
+3
View File
@@ -38,6 +38,7 @@
<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>
@@ -50,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