Compare commits

..
39 Commits
Author SHA1 Message Date
Markus Fisch 429af04714 Advance version number to 1.4.2 2019-03-18 22:22:11 +01:00
Markus Fisch f134987572 Remove unused system bar methods 2019-03-18 22:17:51 +01:00
Markus Fisch d8a9043f94 Put findViewById() on one line
Because Android Studio can't stand the line break.
2019-03-18 22:15:07 +01:00
Markus Fisch 3782fa8360 Make sure time and format won't overlap in row 2019-03-18 22:14:40 +01:00
Markus Fisch 023e32f796 Use a plural resource for barcode length 2019-03-18 22:09:41 +01:00
Markus Fisch 82f3abdc86 Use string resource for width by height string 2019-03-18 22:01:40 +01:00
Markus Fisch 1345d31a6d Use property access syntax where possible
And clean up other syntax deviations.
2019-03-18 21:57:05 +01:00
Markus Fisch c56fa39c15 Show barcode type and content length
After a barcode was decoded.
2019-03-18 20:31:08 +01:00
Markus Fisch 7c8997ae9a Update Error Prone version 2019-03-18 20:21:23 +01:00
Markus Fisch 584343c4e9 Add main sources to Gradle source sets
Otherwise Android Studio won't find them.
2019-03-18 20:19:31 +01:00
Markus Fisch 5754f8eb7c Advance version number to 1.4.1 2019-03-17 21:03:29 +01:00
Markus Fisch 15a076026c Only show torch mode FAB if camera supports it 2019-03-17 20:56:31 +01:00
Markus Fisch 1c90451cb9 Catch RuntimeException when enabling torch mode
In case the chosen camera doesn't support torch mode.
2019-03-17 20:41:06 +01:00
Markus Fisch a4939f2f95 Advance version number to 1.4.0 2019-03-17 18:50:48 +01:00
Markus Fisch b19d2b1894 Add scanning from a shared image 2019-03-17 18:45:00 +01:00
Markus Fisch 242421a765 Share generated barcodes 2019-03-07 17:33:36 +01:00
Markus Fisch 4cf12b71ac Apply auto format 2019-03-07 15:43:30 +01:00
Markus Fisch b8cf3a6fe9 Save scanned codes and show a history listing
If the user chooses to keep a history.
There's a switch to enable/disable this feature.
By default, the app won't save anything.
2019-03-07 12:34:42 +01:00
Markus Fisch 333e163624 Update tools version 2019-03-05 12:44:59 +01:00
Markus Fisch dfe1518676 Update CameraView library to latest version 2019-03-04 18:06:18 +01:00
Markus Fisch 8097c685e8 Use Thread.isInterrupted() instead of private flag
And clean up joining threads.
2019-02-28 20:16:33 +01:00
Markus Fisch 810754d833 Fix Hungarian translation for switch camera 2019-02-26 21:58:35 +01:00
Balázs ÚrandMarkus Fisch a0377cb53a Add F-Droid badge 2019-02-26 10:01:46 +01:00
Markus Fisch 6ddcc31a8a Add hungarian string for switch camera 2019-02-26 10:00:29 +01:00
Balázs ÚrandMarkus Fisch b61fc06688 Added Hungarian translation 2019-02-26 09:58:23 +01:00
Markus Fisch f29f9e134f Add menu item to switch camera 2019-02-26 09:23:49 +01:00
Markus Fisch a2b264708d Update tools and Kotlin version 2019-02-24 15:13:07 +01:00
Heimen StoffelsandMarkus Fisch 2ae859511f Added Dutch translation 2019-02-24 14:35:47 +01:00
Markus Fisch b554e7f47c Advance version number to 1.3.8 2019-02-19 22:04:38 +01:00
Markus Fisch 1085b63d25 Correct a spelling mistake in README 2019-02-14 19:55:16 +01:00
Markus Fisch ac900ec855 Remove pre-built libraries and use gradle instead
Because F-Droid won't accept apps that contain pre-built libraries
that aren't fetched from a white-listed Maven repository, see
https://github.com/markusfisch/BinaryEye/issues/1
2019-02-14 19:46:26 +01:00
Markus Fisch 8f68e66047 Update Kotlin version 2019-01-21 10:04:07 +01:00
Markus Fisch faf300db3e Advance version number to 1.3.7 2019-01-18 18:40:36 +01:00
Markus Fisch 2fbdd03225 Normalize URL scheme for resolveActivity()
Because Intent.resolveActivity() fails to resolve URLs
with uppercase schemes. Uri.normalizeScheme() will make
the scheme lowercase.
2019-01-18 18:28:08 +01:00
Markus Fisch 9f825f9d5d Update build tools and gradle wrapper 2019-01-18 18:27:50 +01:00
Markus Fisch 5ff1904148 Update build tools and Kotlin versions 2018-11-21 17:33:14 +01:00
Markus Fisch 16e80ff80e Update camera view library
setTapToFocus() is now available in the library.
2018-10-26 21:31:24 +02:00
Markus Fisch 1dcd1845a7 Update build tools and gradle wrapper 2018-10-09 10:00:33 +02:00
Markus Fisch 955f507023 Update tools version 2018-10-04 22:28:40 +02:00
38 changed files with 967 additions and 234 deletions
+19
View File
@@ -1,5 +1,24 @@
# Change Log
## 1.4.2
* Show barcode type and content length after decoding
## 1.4.1
* Toggle availability of torch mode button according to camera features
## 1.4.0
* Save scanned codes and show a history listing
* Share generated barcodes
* Add scanning from a shared image
* Add menu item to switch between camera front/back camera
* Add dutch and hungarian translation
## 1.3.8
* Qualify for F-Droid
## 1.3.7
* Normalize URL schemes
## 1.3.6
* Fix handling of camera scene modes
+2 -2
View File
@@ -9,9 +9,9 @@ Unlike many others it works in portrait and landscape orientation, can read
inverted codes, uses Material Design and doesn't do anything else but decoding
a barcode.
<a href="https://play.google.com/store/apps/details?id=de.markusfisch.android.binaryeye"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge.png" height="45px"/></a>
<a href="https://f-droid.org/en/packages/de.markusfisch.android.binaryeye/"><img alt="Get it on F-Droid" src="https://f-droid.org/badge/get-it-on.png" height="80"/></a> <a href="https://play.google.com/store/apps/details?id=de.markusfisch.android.binaryeye"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" height="80"/></a>
And it's written in [Kotlin][kotlin] because I've wanted to explore that a
And it's written in [Kotlin][kotlin] because I wanted to explore that a
bit more.
[play]: https://play.google.com/store/search?q=barcode%20scanner&c=apps
+7 -6
View File
@@ -14,8 +14,8 @@ android {
minSdkVersion 9
targetSdkVersion sdk_version
versionCode 12
versionName '1.3.6'
versionCode 17
versionName '1.4.2'
// it's recommended to set this value to the lowest API level
// able to provide all the functionality
@@ -26,6 +26,7 @@ android {
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
androidTest.java.srcDirs += 'src/androidTest/kotlin'
}
@@ -35,9 +36,7 @@ android {
}
release {
proguardFiles \
getDefaultProguardFile('proguard-android.txt'), \
'proguard-rules.pro'
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
minifyEnabled true
shrinkResources true
}
@@ -60,8 +59,10 @@ dependencies {
}
testImplementation 'junit:junit:4.12'
implementation fileTree(dir: 'libs', include: '*')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1"
implementation "com.android.support:appcompat-v7:$support_version"
implementation "com.android.support:design:$support_version"
implementation 'com.google.zxing:core:3.3.3'
implementation 'com.github.markusfisch:CameraView:1.8.2'
}
Binary file not shown.
Binary file not shown.
+16
View File
@@ -7,10 +7,12 @@
android:xlargeScreens="true"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<application
tools:ignore="UnusedAttribute"
android:name=".app.BinaryEyeApp"
android:allowBackup="true"
android:fullBackupContent="true"
android:supportsRtl="true"
@@ -34,6 +36,11 @@
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
<intent-filter android:label="@string/decode_barcode">
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
</intent-filter>
<intent-filter>
<action android:name="com.google.zxing.client.android.SCAN"/>
<category android:name="android.intent.category.DEFAULT"/>
@@ -51,5 +58,14 @@
<activity
android:name=".activity.MainActivity"
android:label="@string/app_name"/>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
</application>
</manifest>
@@ -4,7 +4,9 @@ import com.google.zxing.Result
import de.markusfisch.android.cameraview.widget.CameraView
import de.markusfisch.android.binaryeye.app.db
import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.rs.Preprocessor
import de.markusfisch.android.binaryeye.zxing.Zxing
import de.markusfisch.android.binaryeye.R
@@ -12,42 +14,45 @@ import de.markusfisch.android.binaryeye.R
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.hardware.Camera
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.os.Vibrator
import android.preference.PreferenceManager
import android.provider.MediaStore
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.widget.SeekBar
import android.widget.Toast
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.IOException
class CameraActivity : AppCompatActivity() {
private val zxing = Zxing()
private val decodingRunnable = Runnable {
while (decoding) {
while (!Thread.currentThread().isInterrupted) {
val result = decodeFrame()
if (result != null) {
cameraView.post { found(result) }
decoding = false
break
}
}
}
private lateinit var preferences: SharedPreferences
private lateinit var vibrator: Vibrator
private lateinit var cameraView: CameraView
private lateinit var zoomBar: SeekBar
private lateinit var flashFab: View
private var decoding = false
private var decodingThread: Thread? = null
private var preprocessor: Preprocessor? = null
private var frameData: ByteArray? = null
@@ -57,6 +62,7 @@ class CameraActivity : AppCompatActivity() {
private var invert = false
private var flash = false
private var returnResult = false
private var frontFacing = false
override fun onRequestPermissionsResult(
requestCode: Int,
@@ -83,17 +89,24 @@ class CameraActivity : AppCompatActivity() {
initSystemBars(this)
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
preferences = PreferenceManager.getDefaultSharedPreferences(this)
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
cameraView = findViewById(R.id.camera_view) as CameraView
zoomBar = findViewById(R.id.zoom) as SeekBar
flashFab = findViewById(R.id.flash)
flashFab.setOnClickListener { _ -> toggleTorchMode() }
initCameraView()
setTapToFocus()
initZoomBar()
restoreZoom()
initFlashFab(findViewById(R.id.flash))
if (intent?.action == Intent.ACTION_SEND) {
if ("text/plain" == intent.type) {
handleSendText(intent)
} else if (intent.type?.startsWith("image/") == true) {
handleSendImage(intent)
}
}
}
override fun onDestroy() {
@@ -102,29 +115,35 @@ class CameraActivity : AppCompatActivity() {
saveZoom()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleSendText(intent)
}
override fun onResume() {
super.onResume()
System.gc()
returnResult = "com.google.zxing.client.android.SCAN".equals(
intent.action
)
handleSendText(intent)
if (hasCameraPermission()) {
cameraView.openAsync(
CameraView.findCameraId(
Camera.CameraInfo.CAMERA_FACING_BACK
)
)
openCamera()
}
}
private fun openCamera() {
cameraView.openAsync(
CameraView.findCameraId(
if (frontFacing) {
Camera.CameraInfo.CAMERA_FACING_FRONT
} else {
Camera.CameraInfo.CAMERA_FACING_BACK
}
)
)
}
override fun onPause() {
super.onPause()
closeCamera()
}
private fun closeCamera() {
cancelDecoding()
cameraView.close()
}
@@ -152,6 +171,14 @@ class CameraActivity : AppCompatActivity() {
startActivity(MainActivity.getEncodeIntent(this))
true
}
R.id.history -> {
startActivity(MainActivity.getHistoryIntent(this))
true
}
R.id.switch_camera -> {
switchCamera()
true
}
R.id.info -> {
openReadme()
true
@@ -160,6 +187,12 @@ class CameraActivity : AppCompatActivity() {
}
}
private fun switchCamera() {
closeCamera()
frontFacing = frontFacing xor true
openCamera()
}
private fun openReadme() {
startActivity(
Intent(
@@ -171,20 +204,39 @@ class CameraActivity : AppCompatActivity() {
}
private fun handleSendText(intent: Intent) {
if (!Intent.ACTION_SEND.equals(intent.getAction()) ||
!"text/plain".equals(intent.getType())) {
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
if (text?.isEmpty() == true) {
startActivity(MainActivity.getEncodeIntent(this, text, true))
finish()
}
}
private fun handleSendImage(intent: Intent) {
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri ?: return
val bitmap = try {
MediaStore.Images.Media.getBitmap(contentResolver, uri)
} catch (e: IOException) {
null
}
if (bitmap == null) {
Toast.makeText(
this,
R.string.error_no_content,
Toast.LENGTH_SHORT
).show()
return
}
var text = intent.getStringExtra(Intent.EXTRA_TEXT)
if (text == null || text.isEmpty()) {
val result = zxing.decode(downsizeIfBigger(bitmap, 1024))
if (result != null) {
showResult(result)
finish()
return
}
// consume this intent
intent.setAction(null)
startActivity(MainActivity.getEncodeIntent(this, text, true))
Toast.makeText(
this,
R.string.no_barcode_found,
Toast.LENGTH_SHORT
).show()
finish()
}
@@ -205,31 +257,33 @@ class CameraActivity : AppCompatActivity() {
private fun initCameraView() {
cameraView.setUseOrientationListener(true)
cameraView.setTapToFocus()
cameraView.setOnCameraListener(object : CameraView.OnCameraListener {
override fun onConfigureParameters(
parameters: Camera.Parameters
) {
if (parameters.isZoomSupported()) {
val max = parameters.getMaxZoom()
if (parameters.isZoomSupported) {
val max = parameters.maxZoom
if (zoomBar.max != max) {
zoomBar.max = max
zoomBar.progress = max / 10
saveZoom()
}
parameters.setZoom(zoomBar.progress)
parameters.zoom = zoomBar.progress
} else {
zoomBar.visibility = View.GONE
}
val sceneModes = parameters.supportedSceneModes
sceneModes?.let {
for (mode in sceneModes) {
if (mode.equals(Camera.Parameters.SCENE_MODE_BARCODE)) {
if (mode == Camera.Parameters.SCENE_MODE_BARCODE) {
parameters.sceneMode = mode
break
}
}
}
CameraView.setAutoFocus(parameters)
updateFlashFab(parameters.flashMode == null)
}
override fun onCameraError() {
@@ -259,31 +313,6 @@ class CameraActivity : AppCompatActivity() {
})
}
private fun setTapToFocus() {
val runnable = Runnable {
cameraView.setFocusArea(null)
}
cameraView.setOnTouchListener({ v: View, event: MotionEvent ->
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
val camera = cameraView.camera
camera?.cancelAutoFocus()
cameraView.setFocusArea(
cameraView.calculateFocusRect(
event.x,
event.y,
100
)
)
camera?.autoFocus({ _: Boolean, _: Camera ->
cameraView.removeCallbacks(runnable)
cameraView.postDelayed(runnable, 3000)
})
v.performClick()
}
true
})
}
private fun initZoomBar() {
zoomBar.setOnSeekBarChangeListener(object :
SeekBar.OnSeekBarChangeListener {
@@ -302,12 +331,12 @@ class CameraActivity : AppCompatActivity() {
}
private fun setZoom(zoom: Int) {
val camera: Camera? = cameraView.getCamera()
val camera: Camera? = cameraView.camera
camera?.let {
try {
val params = camera.getParameters()
params.setZoom(zoom)
camera.setParameters(params)
val params = camera.parameters
params.zoom = zoom
camera.parameters = params
} catch (e: RuntimeException) {
// ignore; there's nothing we can do
}
@@ -315,68 +344,66 @@ class CameraActivity : AppCompatActivity() {
}
private fun saveZoom() {
val editor = preferences.edit()
val editor = prefs.preferences.edit()
editor.putInt(ZOOM_MAX, zoomBar.max)
editor.putInt(ZOOM_LEVEL, zoomBar.progress)
editor.apply()
}
private fun restoreZoom() {
zoomBar.max = preferences.getInt(ZOOM_MAX, zoomBar.max)
zoomBar.progress = preferences.getInt(ZOOM_LEVEL, zoomBar.progress)
zoomBar.max = prefs.preferences.getInt(ZOOM_MAX, zoomBar.max)
zoomBar.progress = prefs.preferences.getInt(
ZOOM_LEVEL,
zoomBar.progress
)
}
private fun initFlashFab(fab: View) {
if (!packageManager.hasSystemFeature(
PackageManager.FEATURE_CAMERA_FLASH
)) {
fab.visibility = View.GONE
private fun updateFlashFab(available: Boolean) {
flashFab.visibility = if (available) {
View.GONE
} else {
fab.setOnClickListener { _ ->
toggleTorchMode()
}
View.VISIBLE
}
}
private fun toggleTorchMode() {
val camera = cameraView.getCamera()
val parameters = camera?.getParameters()
parameters?.setFlashMode(
if (flash) {
Camera.Parameters.FLASH_MODE_OFF
} else {
Camera.Parameters.FLASH_MODE_TORCH
val camera = cameraView.camera
val parameters = camera?.parameters
parameters?.flashMode = if (flash) {
Camera.Parameters.FLASH_MODE_OFF
} else {
Camera.Parameters.FLASH_MODE_TORCH
}
parameters?.let {
try {
camera.parameters = parameters
flash = flash xor true
} catch (e: RuntimeException) {
// ignore; there's nothing we can do
}
)
flash = flash xor true
parameters?.let { camera.setParameters(parameters) }
}
}
private fun startDecoding() {
frameData = null
decoding = true
decodingThread = Thread(decodingRunnable)
decodingThread?.start()
}
private fun cancelDecoding() {
decoding = false
if (decodingThread != null) {
var retry = 100
while (retry-- > 0) {
try {
decodingThread?.join()
break
} catch (e: InterruptedException) {
decodingThread?.interrupt()
}
decodingThread?.interrupt()
try {
decodingThread?.join()
} catch (e: InterruptedException) {
// parent thread was interrupted
}
decodingThread = null
}
}
private fun decodeFrame(): Result? {
var fd = frameData
val fd = frameData
fd ?: return null
if (preprocessor == null) {
preprocessor = Preprocessor(
@@ -386,7 +413,7 @@ class CameraActivity : AppCompatActivity() {
frameOrientation
)
}
var pp = preprocessor
val pp = preprocessor
pp ?: return null
pp.process(fd)
invert = invert xor true
@@ -401,6 +428,19 @@ class CameraActivity : AppCompatActivity() {
private fun found(result: Result) {
cancelDecoding()
vibrator.vibrate(100)
showResult(result)
}
private fun showResult(result: Result) {
if (prefs.useHistory) {
GlobalScope.launch {
db.insertScan(
System.currentTimeMillis(),
result.text,
result.barcodeFormat.toString()
)
}
}
if (returnResult) {
val resultIntent = Intent()
@@ -425,3 +465,24 @@ class CameraActivity : AppCompatActivity() {
private const val ZOOM_LEVEL = "zoom_level"
}
}
fun downsizeIfBigger(bitmap: Bitmap, max: Int): Bitmap {
return if (Math.max(bitmap.width, bitmap.height) > max) {
var width: Int = max
var height: Int = max
if (bitmap.width > bitmap.height) {
height = Math.round(
width.toFloat() / bitmap.width.toFloat() *
bitmap.height.toFloat()
)
} else {
width = Math.round(
height.toFloat() / bitmap.height.toFloat() *
bitmap.width.toFloat()
)
}
Bitmap.createScaledBitmap(bitmap, width, height, true)
} else {
bitmap
}
}
@@ -6,6 +6,7 @@ import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.setFragment
import de.markusfisch.android.binaryeye.fragment.DecodeFragment
import de.markusfisch.android.binaryeye.fragment.EncodeFragment
import de.markusfisch.android.binaryeye.fragment.HistoryFragment
import de.markusfisch.android.binaryeye.R
import android.content.Context
@@ -35,30 +36,36 @@ class MainActivity : AppCompatActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (state == null) {
var fragment: Fragment
if (intent?.hasExtra(ENCODE) == true) {
fragment = EncodeFragment.newInstance(
val fragment: Fragment
when {
intent?.hasExtra(HISTORY) == true -> fragment = HistoryFragment()
intent?.hasExtra(ENCODE) == true -> fragment = EncodeFragment.newInstance(
intent.getStringExtra(ENCODE)
)
} else if (intent?.hasExtra(DECODE) == true) {
fragment = DecodeFragment.newInstance(
intent?.hasExtra(DECODE) == true -> fragment = DecodeFragment.newInstance(
intent.getStringExtra(DECODE),
intent.getSerializableExtra(
DECODE_FORMAT
) as BarcodeFormat
)
} else {
fragment = DecodeFragment()
else -> fragment = DecodeFragment()
}
setFragment(supportFragmentManager, fragment)
}
}
companion object {
private const val HISTORY = "history"
private const val ENCODE = "encode"
private const val DECODE = "decode"
private const val DECODE_FORMAT = "decode_format"
fun getHistoryIntent(context: Context): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(HISTORY, true)
return intent
}
fun getEncodeIntent(
context: Context,
text: String? = "",
@@ -69,8 +76,8 @@ class MainActivity : AppCompatActivity() {
if (isExternal) {
intent.addFlags(
android.content.Intent.FLAG_ACTIVITY_NO_HISTORY or
android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK or
android.content.Intent.FLAG_ACTIVITY_NEW_TASK
android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK or
android.content.Intent.FLAG_ACTIVITY_NEW_TASK
)
}
return intent
@@ -0,0 +1,59 @@
package de.markusfisch.android.binaryeye.adapter
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.R
import android.content.Context
import android.database.Cursor
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CursorAdapter
import android.widget.TextView
class ScansAdapter(context: Context, cursor: Cursor) :
CursorAdapter(context, cursor, false) {
private val timeIndex = cursor.getColumnIndex(Database.SCANS_DATETIME)
private val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
private val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
override fun newView(
context: Context,
cursor: Cursor,
parent: ViewGroup
): View {
return LayoutInflater.from(parent.context).inflate(
R.layout.row_scan, parent, false
)
}
override fun bindView(
view: View,
context: Context,
cursor: Cursor
) {
val holder = getViewHolder(view)
holder.timeView.text = cursor.getString(timeIndex)
holder.contentView.text = cursor.getString(contentIndex)
holder.formatView.text = cursor.getString(formatIndex)
}
private fun getViewHolder(view: View): ViewHolder {
var holder = view.tag as ViewHolder?
if (holder == null) {
holder = ViewHolder(
view.findViewById(R.id.time),
view.findViewById(R.id.content),
view.findViewById(R.id.format)
)
view.tag = holder
}
return holder
}
private data class ViewHolder(
val timeView: TextView,
val contentView: TextView,
val formatView: TextView
)
}
@@ -0,0 +1,17 @@
package de.markusfisch.android.binaryeye.app
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.preference.Preferences
import android.app.Application
val db = Database()
val prefs = Preferences()
class BinaryEyeApp : Application() {
override fun onCreate() {
super.onCreate()
db.open(this)
prefs.init(this)
}
}
@@ -2,6 +2,7 @@ package de.markusfisch.android.binaryeye.app
import de.markusfisch.android.binaryeye.R
import android.annotation.SuppressLint
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentTransaction
@@ -14,6 +15,7 @@ fun addFragment(fm: FragmentManager?, fragment: Fragment) {
fm?.let { getTransaction(fm, fragment).addToBackStack(null).commit() }
}
@SuppressLint("CommitTransaction")
private fun getTransaction(
fm: FragmentManager,
fragment: Fragment
@@ -2,26 +2,24 @@ package de.markusfisch.android.binaryeye.app
import de.markusfisch.android.binaryeye.R
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Point
import android.os.Build
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.util.TypedValue
import android.view.View
import android.view.Window
fun initSystemBars(activity: AppCompatActivity?) {
val view = activity?.findViewById(R.id.main_layout)
if (view != null && setSystemBarColor(
if (view != null &&
setSystemBarColor(
activity.window,
ContextCompat.getColor(
activity,
R.color.primary_dark_translucent
)
)) {
)
) {
view.setPadding(0, getStatusBarHeight(activity.resources), 0, 0)
}
}
@@ -34,64 +32,17 @@ fun setSystemBarColor(window: Window, color: Int): Boolean {
window.statusBarColor = color
window.navigationBarColor = color
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
return true
}
fun getStatusAndToolBarHeight(context: Context): Int {
return getStatusBarHeight(context.resources) + getToolBarHeight(context)
}
fun getStatusBarHeight(res: Resources): Int {
return getIdentifierDimen(res, "status_bar_height")
}
fun getToolBarHeight(context: Context): Int {
val tv = TypedValue()
return if (context.theme.resolveAttribute(
android.R.attr.actionBarSize,
tv,
true
))
TypedValue.complexToDimensionPixelSize(
tv.data,
context.resources.displayMetrics
)
else
0
}
fun getNavigationBarSize(res: Resources): Point {
val size = Point(0, 0)
if (!getIdentifierBoolean(res, "config_showNavigationBar")) {
return size
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
val conf = res.getConfiguration()
if (conf.orientation == Configuration.ORIENTATION_LANDSCAPE &&
// according to https://developer.android.com/training/multiscreen/screensizes.html#TaskUseSWQuali
// only a screen < 600 dp is considered to be a phone
// and can move its navigation bar to the side
conf.smallestScreenWidthDp < 600) {
size.x = getIdentifierDimen(
res,
"navigation_bar_height_landscape"
)
return size
}
}
size.y = getIdentifierDimen(res, "navigation_bar_height")
return size
}
private fun getIdentifierBoolean(res: Resources, name: String): Boolean {
val id = res.getIdentifier(name, "bool", "android")
return id > 0 && res.getBoolean(id)
}
private fun getIdentifierDimen(res: Resources, name: String): Int {
val id = res.getIdentifier(name, "dimen", "android")
return if (id > 0) res.getDimensionPixelSize(id) else 0
@@ -0,0 +1,92 @@
package de.markusfisch.android.binaryeye.data
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.text.format.DateFormat
class Database {
private lateinit var db: SQLiteDatabase
fun open(context: Context) {
db = OpenHelper(context).writableDatabase
}
fun getScans(): Cursor? = db.rawQuery(
"""SELECT
$SCANS_ID,
$SCANS_DATETIME,
$SCANS_CONTENT,
$SCANS_FORMAT
FROM $SCANS
ORDER BY $SCANS_DATETIME DESC
""", null
)
fun getScan(id: Long): Cursor? = db.rawQuery(
"""SELECT
$SCANS_DATETIME,
$SCANS_CONTENT,
$SCANS_FORMAT
FROM $SCANS
WHERE $SCANS_ID = ?
""", arrayOf("$id")
)
fun insertScan(timestamp: Long, name: String, code: String): Long {
val cv = ContentValues()
cv.put(
SCANS_DATETIME, DateFormat.format(
"yyyy-MM-dd HH:mm:ss",
timestamp
).toString()
)
cv.put(SCANS_CONTENT, name)
cv.put(SCANS_FORMAT, code)
return db.insert(SCANS, null, cv)
}
fun removeScan(id: Long) {
db.delete(SCANS, "$SCANS_ID = ?", arrayOf("$id"))
}
fun removeScans() {
db.delete(SCANS, null, null)
}
private class OpenHelper(context: Context) :
SQLiteOpenHelper(context, "history.db", null, 1) {
override fun onCreate(db: SQLiteDatabase) {
createScans(db)
}
override fun onUpgrade(
db: SQLiteDatabase,
oldVersion: Int,
newVersion: Int
) {
}
}
companion object {
const val SCANS = "scans"
const val SCANS_ID = "_id"
const val SCANS_DATETIME = "_datetime"
const val SCANS_CONTENT = "content"
const val SCANS_FORMAT = "format"
private fun createScans(db: SQLiteDatabase) {
db.execSQL("DROP TABLE IF EXISTS $SCANS")
db.execSQL(
"""CREATE TABLE $SCANS (
$SCANS_ID INTEGER PRIMARY KEY AUTOINCREMENT,
$SCANS_DATETIME DATETIME NOT NULL,
$SCANS_CONTENT TEXT NOT NULL,
$SCANS_FORMAT TEXT NOT NULL
)"""
)
}
}
}
@@ -3,17 +3,30 @@ package de.markusfisch.android.binaryeye.fragment
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.zxing.Zxing
import de.markusfisch.android.binaryeye.BuildConfig
import de.markusfisch.android.binaryeye.R
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.graphics.Bitmap
import android.support.v4.app.Fragment
import android.support.v4.content.FileProvider
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class BarcodeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
@@ -28,31 +41,83 @@ class BarcodeFragment : Fragment() {
false
)
val args = getArguments()
val args = arguments
args?.let {
val size = args.getInt(SIZE)
val bitmap: Bitmap?
try {
bitmap = Zxing.encodeAsBitmap(
args.getString(CONTENT),
args.getSerializable(FORMAT) as BarcodeFormat,
size,
size
)
} catch (e: Exception) {
Toast.makeText(
activity, e.message,
Toast.LENGTH_SHORT
).show()
fragmentManager.popBackStack()
return null
val content = args.getString(CONTENT)
val format = args.getSerializable(FORMAT) as BarcodeFormat?
if (content != null && format != null) {
val size = args.getInt(SIZE)
val bitmap: Bitmap?
try {
bitmap = Zxing.encodeAsBitmap(
content,
format,
size,
size
)
} catch (e: Exception) {
Toast.makeText(
activity,
e.message,
Toast.LENGTH_SHORT
).show()
fragmentManager.popBackStack()
return null
}
view.findViewById<ImageView>(R.id.barcode).setImageBitmap(bitmap)
view.findViewById<View>(R.id.share).setOnClickListener {
bitmap?.let {
share(bitmap)
}
}
}
view.findViewById<ImageView>(R.id.barcode).setImageBitmap(bitmap)
}
return view
}
private fun share(bitmap: Bitmap) {
GlobalScope.launch {
val file = saveBitmap(bitmap)
GlobalScope.launch(Main) {
file?.let {
val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_STREAM, getUriForFile(file))
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = "image/png"
startActivity(intent)
}
}
}
}
private fun saveBitmap(bitmap: Bitmap): File? {
return try {
val file = File(
context.externalCacheDir,
"shared_barcode.png"
)
val fos = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos)
fos.close()
file
} catch (e: IOException) {
null
}
}
private fun getUriForFile(file: File): Uri {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
Uri.fromFile(file)
} else {
FileProvider.getUriForFile(
context,
BuildConfig.APPLICATION_ID + ".provider",
file
)
}
}
companion object {
private const val CONTENT = "content"
private const val FORMAT = "format"
@@ -68,7 +133,7 @@ class BarcodeFragment : Fragment() {
args.putSerializable(FORMAT, format)
args.putInt(SIZE, size)
val fragment = BarcodeFragment()
fragment.setArguments(args)
fragment.arguments = args
return fragment
}
}
@@ -8,6 +8,7 @@ import de.markusfisch.android.binaryeye.R
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import android.text.ClipboardManager
@@ -18,10 +19,12 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
class DecodeFragment : Fragment() {
private lateinit var contentView: EditText
private lateinit var formatView: TextView
private lateinit var format: BarcodeFormat
override fun onCreate(state: Bundle?) {
@@ -45,10 +48,17 @@ class DecodeFragment : Fragment() {
val content = arguments?.getString(CONTENT) ?: ""
format = arguments?.getSerializable(FORMAT) as BarcodeFormat? ?: BarcodeFormat.QR_CODE
contentView = view.findViewById<EditText>(R.id.content)
contentView = view.findViewById(R.id.content)
contentView.setText(content)
formatView = view.findViewById(R.id.format)
formatView.text = resources.getQuantityString(
R.plurals.barcode_info,
content.length,
format.toString(),
content.length
)
view.findViewById<View>(R.id.share).setOnClickListener { _ ->
view.findViewById<View>(R.id.share).setOnClickListener {
share(getContent())
}
@@ -81,7 +91,7 @@ class DecodeFragment : Fragment() {
}
private fun getContent(): String {
return contentView.getText().toString()
return contentView.text.toString()
}
private fun copyToClipboard(text: String) {
@@ -90,7 +100,7 @@ class DecodeFragment : Fragment() {
val cm = activity.getSystemService(
Context.CLIPBOARD_SERVICE
) as ClipboardManager
cm.setText(text)
cm.text = text
Toast.makeText(
activity,
R.string.put_into_clipboard,
@@ -102,8 +112,12 @@ class DecodeFragment : Fragment() {
if (activity == null || url.isEmpty()) {
return
}
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
if (intent.resolveActivity(activity.getPackageManager()) != null) {
var uri = Uri.parse(url)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
uri = uri.normalizeScheme()
}
val intent = Intent(Intent.ACTION_VIEW, uri)
if (intent.resolveActivity(activity.packageManager) != null) {
startActivity(intent)
} else {
Toast.makeText(
@@ -117,7 +131,7 @@ class DecodeFragment : Fragment() {
private fun share(text: String) {
val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_TEXT, text)
intent.setType("text/plain")
intent.type = "text/plain"
startActivity(intent)
}
@@ -50,21 +50,20 @@ class EncodeFragment : Fragment() {
false
)
formatView = view.findViewById<Spinner>(R.id.format)
formatView.setAdapter(
ArrayAdapter<String>(
activity,
android.R.layout.simple_list_item_1,
writers.map { it -> it.name })
formatView = view.findViewById(R.id.format)
formatView.adapter = ArrayAdapter<String>(
activity,
android.R.layout.simple_list_item_1,
writers.map { it.name }
)
sizeView = view.findViewById<TextView>(R.id.size_display)
sizeBarView = view.findViewById<SeekBar>(R.id.size_bar)
sizeView = view.findViewById(R.id.size_display)
sizeBarView = view.findViewById(R.id.size_bar)
initSizeBar()
val contentView = view.findViewById<EditText>(R.id.content)
val args = getArguments()
val args = arguments
args?.let {
contentView.setText(args.getString(CONTENT))
formatView.setSelection(
@@ -75,12 +74,13 @@ class EncodeFragment : Fragment() {
}
view.findViewById<View>(R.id.encode).setOnClickListener { v ->
val format = writers.get(formatView.getSelectedItemPosition())
var size = getSize(sizeBarView.getProgress())
val content = contentView.getText().toString()
val format = writers[formatView.selectedItemPosition]
val size = getSize(sizeBarView.progress)
val content = contentView.text.toString()
if (content.isEmpty()) {
Toast.makeText(
v.context, R.string.error_no_content,
v.context,
R.string.error_no_content,
Toast.LENGTH_SHORT
).show()
} else {
@@ -99,7 +99,7 @@ class EncodeFragment : Fragment() {
}
private fun initSizeBar() {
updateSize(sizeBarView.getProgress())
updateSize(sizeBarView.progress)
sizeBarView.setOnSeekBarChangeListener(
object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(
@@ -118,7 +118,7 @@ class EncodeFragment : Fragment() {
private fun updateSize(power: Int) {
val size = getSize(power)
sizeView.setText("${size}x$size")
sizeView.text = getString(R.string.width_by_height, size, size)
}
private fun getSize(power: Int) = 128 * (power + 1)
@@ -128,7 +128,7 @@ class EncodeFragment : Fragment() {
Context.INPUT_METHOD_SERVICE
) as InputMethodManager?
im?.let {
im.hideSoftInputFromWindow(view.getWindowToken(), 0)
im.hideSoftInputFromWindow(view.windowToken, 0)
}
}
@@ -144,7 +144,7 @@ class EncodeFragment : Fragment() {
args.putString(CONTENT, content)
args.putSerializable(FORMAT, format)
val fragment = EncodeFragment()
fragment.setArguments(args)
fragment.arguments = args
return fragment
}
}
@@ -0,0 +1,148 @@
package de.markusfisch.android.binaryeye.fragment
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.adapter.ScansAdapter
import de.markusfisch.android.binaryeye.app.db
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.R
import android.app.AlertDialog
import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.SwitchCompat
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ListView
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class HistoryFragment : Fragment() {
private lateinit var listView: ListView
private lateinit var fab: View
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?
): View {
activity.setTitle(R.string.history)
val view = inflater.inflate(
R.layout.fragment_history,
container,
false
)
val useHistorySwitch = view.findViewById(R.id.use_history) as SwitchCompat
initHistorySwitch(useHistorySwitch)
listView = view.findViewById(R.id.scans)
listView.emptyView = useHistorySwitch
listView.setOnItemClickListener { _, _, _, id ->
showScan(id)
}
listView.setOnItemLongClickListener { _, v, _, id ->
askToRemoveScan(v.context, id)
true
}
fab = view.findViewById(R.id.clear)
fab.setOnClickListener { v ->
askToRemoveAllScans(v.context)
}
return view
}
override fun onDestroy() {
super.onDestroy()
(listView.adapter as ScansAdapter?)?.changeCursor(null)
}
override fun onResume() {
super.onResume()
update(context)
}
private fun initHistorySwitch(switchView: SwitchCompat) {
switchView.setOnCheckedChangeListener { _, isChecked ->
prefs.useHistory = isChecked
}
if (prefs.useHistory) {
switchView.toggle()
}
}
private fun update(context: Context) {
GlobalScope.launch {
val cursor = db.getScans()
GlobalScope.launch(Main) {
fab.visibility = if (cursor != null && cursor.count > 0) {
View.VISIBLE
} else {
View.GONE
}
cursor?.let {
if (listView.adapter == null) {
listView.adapter = ScansAdapter(context, cursor)
} else {
val adapter = listView.adapter as ScansAdapter
adapter.changeCursor(cursor)
adapter.notifyDataSetChanged()
}
}
}
}
}
private fun showScan(id: Long) {
val cursor = db.getScan(id)
if (cursor != null && cursor.moveToFirst()) {
val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
try {
addFragment(
fragmentManager,
DecodeFragment.newInstance(
cursor.getString(contentIndex),
BarcodeFormat.valueOf(cursor.getString(formatIndex))
)
)
} catch (e: IllegalArgumentException) {
// shouldn't ever happen
}
}
cursor?.close()
}
private fun askToRemoveScan(context: Context, id: Long) {
AlertDialog.Builder(context)
.setMessage(R.string.really_remove_scan)
.setPositiveButton(android.R.string.ok) { _, _ ->
db.removeScan(id)
update(context)
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
}
.show()
}
private fun askToRemoveAllScans(context: Context) {
AlertDialog.Builder(context)
.setMessage(R.string.really_remove_all_scans)
.setPositiveButton(android.R.string.ok) { _, _ ->
db.removeScans()
update(context)
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
}
.show()
}
}
@@ -0,0 +1,34 @@
package de.markusfisch.android.binaryeye.preference
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
class Preferences {
lateinit var preferences: SharedPreferences
var useHistory = false
set(value) {
setBoolean(USE_HISTORY, value)
field = value
}
fun init(context: Context) {
preferences = PreferenceManager.getDefaultSharedPreferences(context)
update()
}
fun update() {
useHistory = preferences.getBoolean(USE_HISTORY, useHistory)
}
private fun setBoolean(label: String, value: Boolean) {
val editor = preferences.edit()
editor.putBoolean(label, value)
editor.apply()
}
companion object {
const val USE_HISTORY = "use_history"
}
}
@@ -13,7 +13,7 @@ class Preprocessor(
context: Context,
width: Int,
height: Int,
val orientation: Int
private val orientation: Int
) {
var outWidth = 0
var outHeight = 0
@@ -42,7 +42,7 @@ class Preprocessor(
Allocation.USAGE_SCRIPT
)
var f = .75f
val f = .75f
var w = Math.round(width * f)
var h = Math.round(height * f)
@@ -7,6 +7,7 @@ import com.google.zxing.LuminanceSource
import com.google.zxing.MultiFormatReader
import com.google.zxing.MultiFormatWriter
import com.google.zxing.PlanarYUVLuminanceSource
import com.google.zxing.RGBLuminanceSource
import com.google.zxing.ReaderException
import com.google.zxing.Result
import com.google.zxing.common.HybridBinarizer
@@ -49,7 +50,7 @@ class Zxing {
)
val hints = EnumMap<DecodeHintType, Any>(DecodeHintType::class.java)
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats)
hints[DecodeHintType.POSSIBLE_FORMATS] = decodeFormats
multiFormatReader.setHints(hints)
}
@@ -79,6 +80,23 @@ class Zxing {
)
}
fun decode(bitmap: Bitmap): Result? {
val width = bitmap.width
val height = bitmap.height
val pixels = IntArray(width * height)
if (bitmap.config != Bitmap.Config.ARGB_8888) {
bitmap.copy(Bitmap.Config.ARGB_8888, true)
} else {
bitmap
}.getPixels(pixels, 0, width, 0, 0, width, height)
val source = RGBLuminanceSource(width, height, pixels)
val result = decodeLuminanceSource(source)
if (result != null) {
return result
}
return decodeLuminanceSource(source.invert())
}
private fun decodeLuminanceSource(source: LuminanceSource): Result? {
val bitmap = BinaryBitmap(HybridBinarizer(source))
return try {
@@ -104,12 +122,12 @@ class Zxing {
text, format,
width, height, null
)
val w = result.getWidth()
val h = result.getHeight()
val w = result.width
val h = result.height
val pixels = IntArray(w * h)
var offset = 0
for (y in 0..h - 1) {
for (x in 0..w - 1) {
for (y in 0 until h) {
for (x in 0 until w) {
pixels[offset + x] = if (result.get(x, y))
BLACK
else
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8.46,11.88l1.41,-1.41L12,12.59l2.12,-2.12 1.41,1.41L13.41,14l2.12,2.12 -1.41,1.41L12,15.41l-2.12,2.12 -1.41,-1.41L10.59,14l-2.13,-2.12zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z"/>
</vector>
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
</vector>
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13L7,13v-2h10v2z"/>
</vector>
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M20,4h-3.17L15,2L9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM15,15.5L15,13L9,13v2.5L5.5,12 9,8.5L9,11h6L15,8.5l3.5,3.5 -3.5,3.5z"/>
</vector>
+21 -4
View File
@@ -1,6 +1,23 @@
<ImageView
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/barcode"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/view_barcode"/>
android:layout_height="match_parent">
<ImageView
android:id="@+id/barcode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/view_barcode"/>
<android.support.design.widget.FloatingActionButton
tools:ignore="UnusedAttribute"
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:src="@drawable/ic_action_share"
app:elevation="4dp"/>
</RelativeLayout>
+15 -1
View File
@@ -5,6 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
tools:targetApi="o"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -13,7 +14,20 @@
android:imeOptions="flagNoExtractUi"
android:inputType="textMultiLine"
android:typeface="monospace"
android:hint="@string/content"/>
android:hint="@string/content"
android:importantForAutofill="no"/>
<TextView
android:id="@+id/format"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:gravity="end|top"
android:textSize="14sp"/>
<android.support.design.widget.FloatingActionButton
tools:ignore="UnusedAttribute"
android:id="@+id/share"
+3 -1
View File
@@ -43,6 +43,7 @@
android:layout_marginBottom="8dp"
android:text="@string/content"/>
<EditText
tools:targetApi="o"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -51,7 +52,8 @@
android:typeface="monospace"
android:imeOptions="flagNoExtractUi"
android:inputType="textMultiLine"
android:hint="@string/input_content_here"/>
android:hint="@string/input_content_here"
android:importantForAutofill="no"/>
<Button
style="@style/Button"
android:id="@+id/encode"
@@ -0,0 +1,34 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/scans"
android:choiceMode="singleChoice"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@android:color/transparent"
android:dividerHeight="1px"/>
<android.support.v7.widget.SwitchCompat
android:visibility="gone"
android:id="@+id/use_history"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:padding="16dp"
android:text="@string/use_history"/>
<android.support.design.widget.FloatingActionButton
tools:ignore="UnusedAttribute"
android:visibility="gone"
android:id="@+id/clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:src="@drawable/ic_action_clear"
app:elevation="4dp"/>
</RelativeLayout>
+32
View File
@@ -0,0 +1,32 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/format"
android:layout_toStartOf="@+id/format"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"/>
<TextView
android:id="@+id/format"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:layout_toRightOf="@+id/time"
android:layout_toEndOf="@+id/time"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"/>
<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/time"
android:layout_marginTop="4dp"
android:typeface="monospace"/>
</RelativeLayout>
+13 -3
View File
@@ -2,13 +2,23 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:material="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/info"
android:title="@string/info"
android:icon="@drawable/ic_action_info"
android:id="@+id/history"
android:title="@string/history"
android:icon="@drawable/ic_action_history"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/create"
android:title="@string/compose_barcode"
android:icon="@drawable/ic_action_create"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/switch_camera"
android:title="@string/switch_camera"
android:icon="@drawable/ic_action_switch_camera"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/info"
android:title="@string/info"
android:icon="@drawable/ic_action_info"
material:showAsAction="ifRoom"/>
</menu>
@@ -0,0 +1,9 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:material="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/remove_scan"
android:title="@string/remove_scan"
android:icon="@drawable/ic_action_remove"
material:showAsAction="ifRoom"/>
</menu>
+13
View File
@@ -3,16 +3,29 @@
<string name="camera_error">Auf die Kamera kann nicht zugegriffen werden, bitte versuche es nochmal</string>
<string name="scan_code">Code scannen</string>
<string name="compose_barcode">Barcode erstellen</string>
<string name="decode_barcode">Barcode dekodieren</string>
<string name="content">Inhalt</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d Zeichen</item>
<item quantity="other">%1$s\n%2$d Zeichen</item>
</plurals>
<string name="copy_to_clipboard">In die Zwischenablage kopieren</string>
<string name="put_into_clipboard">In die Zwischenablage legen</string>
<string name="open_url">URL öffnen</string>
<string name="cannot_resolve_action">Keine App kann das öffnen</string>
<string name="format">Format</string>
<string name="size">Größe in Pixeln</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Gib den Inhalt hier ein</string>
<string name="encode">KODIEREN</string>
<string name="error_no_content">Fehlender Inhalt</string>
<string name="view_barcode">Barcode ansehen</string>
<string name="info">Info</string>
<string name="switch_camera">Kamera wechseln</string>
<string name="history">Gespeicherte Codes</string>
<string name="use_history">Codes speichern</string>
<string name="remove_scan">Code entfernen</string>
<string name="really_remove_scan">Code wirklich entfernen?</string>
<string name="really_remove_all_scans">Alle Codes entfernen?</string>
<string name="no_barcode_found">Kein Barcode gefunden</string>
</resources>
+31
View File
@@ -0,0 +1,31 @@
<resources>
<string name="no_camera_no_fun">Ennek az alkalmazásnak nincs semmi értelme kamera-hozzáférés nélkül. Viszlát!</string>
<string name="camera_error">Nem lehet hozzáférni a kamerához, próbálja meg újra</string>
<string name="scan_code">Kód beolvasása</string>
<string name="compose_barcode">Vonalkód létrehozása</string>
<string name="decode_barcode">Decode barcode</string>
<string name="content">Tartalom</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d character</item>
<item quantity="other">%1$s\n%2$d characters</item>
</plurals>
<string name="copy_to_clipboard">Másolás a vágólapra</string>
<string name="put_into_clipboard">Vágólapra helyezés</string>
<string name="open_url">URL megnyitása</string>
<string name="cannot_resolve_action">Nincs alkalmazás, amely meg tudná nyitni azt</string>
<string name="format">Formátum</string>
<string name="size">Méret képpontban</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Itt adja meg a tartalmat</string>
<string name="encode">KÓDOLÁS</string>
<string name="error_no_content">Hiányzó tartalom</string>
<string name="view_barcode">Vonalkód megtekintése</string>
<string name="info">Információ</string>
<string name="switch_camera">Kamera átkapcsolása</string>
<string name="history">History</string>
<string name="use_history">Save scan history</string>
<string name="remove_scan">Remove scan</string>
<string name="really_remove_scan">Really remove scan?</string>
<string name="really_remove_all_scans">Really remove all scans?</string>
<string name="no_barcode_found">No barcode found</string>
</resources>
+31
View File
@@ -0,0 +1,31 @@
<resources>
<string name="no_camera_no_fun">Deze app kan niet werken zonder cameratoegang. Tot ziens.</string>
<string name="camera_error">Kan camera niet openen; probeer het opnieuw.</string>
<string name="scan_code">Scan een scode</string>
<string name="compose_barcode">Scan een barcode</string>
<string name="decode_barcode">Decode barcode</string>
<string name="content">Inhoud</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d character</item>
<item quantity="other">%1$s\n%2$d characters</item>
</plurals>
<string name="copy_to_clipboard">Kopiëren naar klembord</string>
<string name="put_into_clipboard">Kopieer de inhoud naar het klembord</string>
<string name="open_url">URL openen</string>
<string name="cannot_resolve_action">Er is geen app aangetroffen die dit kan openen</string>
<string name="format">Formaat</string>
<string name="size">Grootte, in pixels</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Voer hier de inhoud in</string>
<string name="encode">CODEREN</string>
<string name="error_no_content">Inhoud ontbreekt</string>
<string name="view_barcode">Barcode bekijken</string>
<string name="info">Informatie</string>
<string name="switch_camera">Camera wisselen</string>
<string name="history">History</string>
<string name="use_history">Save scan history</string>
<string name="remove_scan">Remove scan</string>
<string name="really_remove_scan">Really remove scan?</string>
<string name="really_remove_all_scans">Really remove all scans?</string>
<string name="no_barcode_found">No barcode found</string>
</resources>
+13
View File
@@ -3,16 +3,29 @@
<string name="camera_error">Can\'t access camera, please try again</string>
<string name="scan_code">Scan code</string>
<string name="compose_barcode">Compose barcode</string>
<string name="decode_barcode">Decode barcode</string>
<string name="content">Content</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d character</item>
<item quantity="other">%1$s\n%2$d characters</item>
</plurals>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="put_into_clipboard">Put into clipboard</string>
<string name="open_url">Open url</string>
<string name="cannot_resolve_action">No application can open that</string>
<string name="format">Format</string>
<string name="size">Size in pixels</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Input content here</string>
<string name="encode">ENCODE</string>
<string name="error_no_content">Missing content</string>
<string name="view_barcode">View barcode</string>
<string name="info">Info</string>
<string name="switch_camera">Switch camera</string>
<string name="history">History</string>
<string name="use_history">Save scan history</string>
<string name="remove_scan">Remove scan</string>
<string name="really_remove_scan">Really remove scan?</string>
<string name="really_remove_all_scans">Really remove all scans?</string>
<string name="no_barcode_found">No barcode found</string>
</resources>
+3
View File
@@ -0,0 +1,3 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
+4 -4
View File
@@ -1,7 +1,7 @@
buildscript {
ext.kotlin_version = '1.2.51'
ext.tools_version = '3.1.3'
ext.build_tools_version = '27.0.3'
ext.kotlin_version = '1.3.21'
ext.tools_version = '3.3.2'
ext.build_tools_version = '28.0.3'
ext.sdk_version = 28
ext.support_version = '25.3.1'
@@ -16,7 +16,7 @@ buildscript {
dependencies {
classpath "com.android.tools.build:gradle:$tools_version"
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.10'
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
+1 -1
View File
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip