Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
694ebf83ed | ||
|
|
78bb03fabf | ||
|
|
902215f064 | ||
|
|
eaf38729bd | ||
|
|
bdd89ca539 | ||
|
|
39ecd31455 | ||
|
|
c96a2cf3ec | ||
|
|
ea9e29b98c | ||
|
|
96e1ef5a52 | ||
|
|
ed4b0a75dd | ||
|
|
37354338cb | ||
|
|
3cd2eecc22 | ||
|
|
0e40d3f7c3 | ||
|
|
c8fa01116d | ||
|
|
04539b4c37 | ||
|
|
b9e8f4cfca | ||
|
|
ad06c2bf9e | ||
|
|
2c7ec74b1b | ||
|
|
b34f29c95f | ||
|
|
e11518c3f5 | ||
|
|
57c4fad307 | ||
|
|
856b8e37e1 | ||
|
|
6b8dd8e864 | ||
|
|
c993cd53f1 | ||
|
|
934983f668 | ||
|
|
ccf425faad | ||
|
|
a244763109 | ||
|
|
7ab712e040 | ||
|
|
cb7219b290 | ||
|
|
7f4ba50ee4 | ||
|
|
b1f00f0592 | ||
|
|
80f8f908d2 | ||
|
|
506308ece9 | ||
|
|
baecded9d5 | ||
|
|
ceefee397f | ||
|
|
e45804be31 | ||
|
|
edef1acbfb | ||
|
|
742939182c | ||
|
|
a21bfcdea6 | ||
|
|
bd281e449b | ||
|
|
0e1464a15f | ||
|
|
8a6441ddf3 | ||
|
|
94874c0a9e | ||
|
|
b9a6410959 | ||
|
|
512f5e00dd | ||
|
|
91c2364958 | ||
|
|
1e3f1c553c | ||
|
|
80c65a48a0 | ||
|
|
4c04ffb7db | ||
|
|
cc3949fa71 | ||
|
|
904a0ecbdd | ||
|
|
9155dd026d | ||
|
|
7286a06985 | ||
|
|
a71552a406 | ||
|
|
5857e8c394 | ||
|
|
429af04714 | ||
|
|
f134987572 | ||
|
|
d8a9043f94 | ||
|
|
3782fa8360 | ||
|
|
023e32f796 | ||
|
|
82f3abdc86 | ||
|
|
1345d31a6d | ||
|
|
c56fa39c15 | ||
|
|
7c8997ae9a | ||
|
|
584343c4e9 | ||
|
|
5754f8eb7c | ||
|
|
15a076026c | ||
|
|
1c90451cb9 | ||
|
|
a4939f2f95 | ||
|
|
b19d2b1894 | ||
|
|
242421a765 | ||
|
|
4cf12b71ac | ||
|
|
b8cf3a6fe9 | ||
|
|
333e163624 | ||
|
|
dfe1518676 | ||
|
|
8097c685e8 | ||
|
|
810754d833 | ||
|
|
a0377cb53a | ||
|
|
6ddcc31a8a | ||
|
|
b61fc06688 | ||
|
|
f29f9e134f | ||
|
|
a2b264708d | ||
|
|
2ae859511f | ||
|
|
b554e7f47c | ||
|
|
1085b63d25 | ||
|
|
ac900ec855 | ||
|
|
8f68e66047 | ||
|
|
faf300db3e | ||
|
|
2fbdd03225 | ||
|
|
9f825f9d5d | ||
|
|
5ff1904148 | ||
|
|
16e80ff80e | ||
|
|
1dcd1845a7 | ||
|
|
955f507023 | ||
|
|
47a3224b27 | ||
|
|
d2bf09d321 | ||
|
|
deee39b367 | ||
|
|
4829433725 | ||
|
|
9efd43e496 | ||
|
|
5bd904ba4f | ||
|
|
3244f0775f | ||
|
|
f4d625a048 | ||
|
|
c838486ca1 | ||
|
|
d4a9ea1a4b | ||
|
|
4123905853 | ||
|
|
720811bf3b | ||
|
|
375fb4aa46 | ||
|
|
aa4b79d13d | ||
|
|
1e9c303646 | ||
|
|
d3ab28f2b3 | ||
|
|
7b67778a29 | ||
|
|
d2c8ef719b | ||
|
|
7bed192300 | ||
|
|
331f65f3a4 | ||
|
|
e17c732b6a | ||
|
|
2e60b2b997 | ||
|
|
70d9b8b99a | ||
|
|
d0cc7e0a68 | ||
|
|
264e7ad098 | ||
|
|
8c33e9bd57 | ||
|
|
a8fd37e4e4 | ||
|
|
17f11ea2de | ||
|
|
f6a582e4f7 | ||
|
|
6e645a567a | ||
|
|
e87dc35372 | ||
|
|
75f4f3eadc | ||
|
|
64e1d722d5 | ||
|
|
05a206384c | ||
|
|
04790e2b57 | ||
|
|
aa3c4a17e9 | ||
|
|
5968ae01eb | ||
|
|
57500b7700 | ||
|
|
4e17a21e5f | ||
|
|
605dc51d50 | ||
|
|
b17d29ba28 | ||
|
|
9afc7fcc62 | ||
|
|
d5543d0c39 | ||
|
|
96fea1f343 | ||
|
|
bc4d9f7148 | ||
|
|
4abacf030b | ||
|
|
279c988464 | ||
|
|
a6449b50e9 | ||
|
|
b4e6449dac | ||
|
|
c3dfadd9e6 | ||
|
|
bad5225024 | ||
|
|
aa9b4927b0 | ||
|
|
69d12910cd | ||
|
|
ee0f7c4529 | ||
|
|
1b296ff83e | ||
|
|
1a635af7b7 | ||
|
|
568530d470 | ||
|
|
6248b46fc8 | ||
|
|
3083d2b625 | ||
|
|
e02420d446 | ||
|
|
e63fe0d601 | ||
|
|
0803fdcac8 | ||
|
|
e64dd2e972 | ||
|
|
418edd34a5 | ||
|
|
af2a1dc940 | ||
|
|
e0c420c29f | ||
|
|
0ba41ca863 | ||
|
|
ed1826679e | ||
|
|
bd75b5b71d | ||
|
|
bbdf724bbb | ||
|
|
0ace08c8b4 | ||
|
|
41164c31f1 | ||
|
|
9f3288e7a8 | ||
|
|
a61fc63fa6 | ||
|
|
26a19a9657 | ||
|
|
a0d157551a | ||
|
|
53fe8b3689 | ||
|
|
0da9e34e55 | ||
|
|
db276372b7 | ||
|
|
c148635c26 | ||
|
|
5c99309125 | ||
|
|
2443eb066a | ||
|
|
d7281266e4 | ||
|
|
e8c676ab8c | ||
|
|
6c1353596d | ||
|
|
ff14130c91 | ||
|
|
e0147df76c | ||
|
|
cbc2b6b8db | ||
|
|
bc727ab45f | ||
|
|
9e8153e8f6 |
@@ -1,10 +1,106 @@
|
||||
# Change Log
|
||||
|
||||
## 1.9.0
|
||||
* Try Google when opening content as URL fails
|
||||
* Show generic error message if barcode generation fails
|
||||
|
||||
## 1.8.1
|
||||
* Distribute APK instead of App Bundle
|
||||
|
||||
## 1.8.0
|
||||
* Save binary data into external file
|
||||
* Fix storage of binary data
|
||||
* Remove horizontal orientation line from scanner
|
||||
|
||||
## 1.7.3
|
||||
* Fix crashes on Lineage and remove RenderScript fallback
|
||||
* Improve handling of binary data
|
||||
|
||||
## 1.7.2
|
||||
* Add a fallback for RenderScript
|
||||
* Add italian translation
|
||||
|
||||
## 1.7.1
|
||||
* Fix support for 64 bit architectures
|
||||
|
||||
## 1.7.0
|
||||
* Pick list separator on sharing history listing
|
||||
* Update Hungarian translation
|
||||
|
||||
## 1.6.0
|
||||
* Share history listing as plain text
|
||||
* Add separator line between history items
|
||||
* Add content description for floating action buttons
|
||||
|
||||
## 1.5.1
|
||||
* Fix generating hex dump
|
||||
|
||||
## 1.5.0
|
||||
* Show a hex dump of decoded contents
|
||||
* Fix processing text from share menu
|
||||
* Add French translation
|
||||
|
||||
## 1.4.3
|
||||
* Fix broken history listing
|
||||
|
||||
## 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
|
||||
|
||||
## 1.3.5
|
||||
* Add german translation
|
||||
* Fix handling of data intents
|
||||
* Respond to ZXing' SCAN intents
|
||||
|
||||
## 1.3.4
|
||||
* Fix pre-processing for Android 7+
|
||||
* Fix adaptive icons for Android 8+
|
||||
|
||||
## 1.3.3
|
||||
* Improved pre-processing
|
||||
* Added adaptive icon for Android 8+
|
||||
* Decrease default magnification to 10%
|
||||
|
||||
## 1.3.2
|
||||
* Fixed crash when going back very fast
|
||||
|
||||
## 1.3.1
|
||||
* Changed primary color
|
||||
|
||||
## 1.3.0
|
||||
* Support tap to focus
|
||||
* Save chose zoom level permanently
|
||||
* Update ZXing to latest version (3.3.2)
|
||||
|
||||
## 1.2.0
|
||||
* Added a camera zoom slider
|
||||
* Added an unobtrusive wallpaper background
|
||||
* Replaced lock marks with just a horizontal line
|
||||
* Put toggling torch mode into Floating Action Button
|
||||
|
||||
## 1.1.1
|
||||
* Brought back inverting every second frame
|
||||
|
||||
## 1.1.0
|
||||
* Create barcodes now too
|
||||
* Create barcodes
|
||||
* Streamlined video pre-processing
|
||||
* Decoded content is editable now
|
||||
* Fixed position of lock marks on tablets
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Contribution Guidelines
|
||||
|
||||
Please try to keep things in good shape and comply to what's there.
|
||||
|
||||
This project follows Android [best practices][android_best_practices]
|
||||
so please have a look if you've never heard of them.
|
||||
|
||||
The code is formatted according to Android Studio's standard, with the
|
||||
exception of indent being tabs instead of spaces.
|
||||
|
||||
Use the feature branch workflow to add new features and make sure
|
||||
to squash when merging into master:
|
||||
|
||||
$ git merge cool_feature --squash
|
||||
|
||||
Then write a [good commit messages][commit_messages] to keep the history
|
||||
meaningful and useful. One feature, one commit.
|
||||
|
||||
[android_best_practices]: https://developer.android.com/distribute/best-practices/develop/
|
||||
[commit_messages]: https://juffalow.com/other/write-good-git-commit-message
|
||||
@@ -1,5 +1,4 @@
|
||||
PACKAGE = de.markusfisch.android.binaryeye
|
||||
APK = app/build/outputs/apk/app-debug.apk
|
||||
|
||||
all: debug install start
|
||||
|
||||
@@ -7,20 +6,25 @@ debug:
|
||||
./gradlew assembleDebug
|
||||
|
||||
release: lint
|
||||
@./gradlew assembleRelease \
|
||||
-Pandroid.injected.signing.store.file=$(ANDROID_KEYFILE) \
|
||||
-Pandroid.injected.signing.store.password=$(ANDROID_STORE_PASSWORD) \
|
||||
-Pandroid.injected.signing.key.alias=$(ANDROID_KEY_ALIAS) \
|
||||
-Pandroid.injected.signing.key.password=$(ANDROID_KEY_PASSWORD)
|
||||
./gradlew assembleRelease
|
||||
|
||||
bundle: lint
|
||||
./gradlew bundleRelease
|
||||
|
||||
lint:
|
||||
./gradlew lintDebug
|
||||
|
||||
sonarqube:
|
||||
./gradlew sonarqube
|
||||
|
||||
infer: clean
|
||||
infer -- ./gradlew assembleDebug
|
||||
|
||||
test:
|
||||
./gradlew cAT
|
||||
|
||||
install:
|
||||
adb $(TARGET) install -r $(APK)
|
||||
adb $(TARGET) install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
start:
|
||||
adb $(TARGET) shell 'am start -n \
|
||||
|
||||
@@ -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://fdroid.gitlab.io/artwork/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
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'net.ltgt.errorprone'
|
||||
|
||||
configurations.errorprone {
|
||||
resolutionStrategy.force 'com.google.errorprone:error_prone_core:2.0.19'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion sdk_version
|
||||
@@ -14,11 +9,30 @@ android {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion sdk_version
|
||||
|
||||
versionCode 4
|
||||
versionName '1.1.1'
|
||||
versionCode 28
|
||||
versionName '1.9.0'
|
||||
|
||||
// it's recommended to set this value to the lowest API level
|
||||
// able to provide all the functionality
|
||||
renderscriptTargetApi 24
|
||||
renderscriptSupportModeEnabled true
|
||||
|
||||
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
config {
|
||||
keyAlias System.getenv('ANDROID_KEY_ALIAS')
|
||||
keyPassword System.getenv('ANDROID_KEY_PASSWORD')
|
||||
storePassword System.getenv('ANDROID_STORE_PASSWORD')
|
||||
def filePath = System.getenv('ANDROID_KEYFILE')
|
||||
storeFile filePath ? file(filePath) : null
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
androidTest.java.srcDirs += 'src/androidTest/kotlin'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -27,11 +41,10 @@ android {
|
||||
}
|
||||
|
||||
release {
|
||||
proguardFiles \
|
||||
getDefaultProguardFile('proguard-android.txt'), \
|
||||
'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
signingConfig signingConfigs.config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +57,18 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: '*')
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||
compile "com.android.support:appcompat-v7:$support_version"
|
||||
compile "com.android.support:design:$support_version"
|
||||
compile project(':cameraview')
|
||||
androidTestImplementation ('com.android.support.test:runner:1.0.2') {
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
androidTestImplementation ('com.android.support.test:rules:1.0.2') {
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
testImplementation 'junit:junit:4.12'
|
||||
|
||||
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.3'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package de.markusfisch.android.binaryeye
|
||||
|
||||
import android.support.test.InstrumentationRegistry
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
|
||||
import de.markusfisch.android.binaryeye.rs.Preprocessor
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PreprocessorTest {
|
||||
@Test
|
||||
fun processSamples() {
|
||||
val zxing = Zxing()
|
||||
val assets = InstrumentationRegistry.getInstrumentation()
|
||||
.context.assets
|
||||
val pattern = Pattern.compile(
|
||||
"[0-9]+-([0-9]+)x([0-9]+)-([0-9]+)deg.yuv"
|
||||
)
|
||||
|
||||
for (file in assets.list("yuv")) {
|
||||
val m = pattern.matcher(file)
|
||||
if (!m.find() || m.groupCount() < 3) {
|
||||
continue
|
||||
}
|
||||
|
||||
val frameWidth = m.group(1).toInt()
|
||||
val frameHeight = m.group(2).toInt()
|
||||
val frameOrientation = m.group(3).toInt()
|
||||
val frameData = assets.open("yuv/$file").readBytes()
|
||||
val preprocessor = Preprocessor(
|
||||
InstrumentationRegistry.getTargetContext(),
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation
|
||||
)
|
||||
preprocessor.process(frameData)
|
||||
val result = zxing.decode(
|
||||
frameData,
|
||||
preprocessor.outWidth,
|
||||
preprocessor.outHeight
|
||||
)
|
||||
preprocessor.destroy()
|
||||
|
||||
checkNotNull(result) { "no barcode found in $file" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M32.002,-24.974h3.127v7.656h-3.127z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:pathData="m54.826,27.893c-6.851,0 -13.701,2.581 -18.924,7.745 -10.445,10.328 -10.413,27.062 0,37.423l36.218,36.036 37.879,0.902 -0.07,-38.363 -36.18,-35.998c-5.207,-5.18 -12.073,-7.745 -18.924,-7.745z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="8.60725021"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"
|
||||
android:fillAlpha="0.1254902"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M28.001,54.118a26.525,26.227 0,1 0,53.049 0a26.525,26.227 0,1 0,-53.049 0z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M39.26,54.988a14.509,14.346 0,1 0,29.019 0a14.509,14.346 0,1 0,-29.019 0z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M54.034,44.513a9.535,9.428 0,1 0,19.07 0a9.535,9.428 0,1 0,-19.07 0z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="10"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:pathData="m62.745,24.783c-2.055,1.011 -2.87,3.415 -1.847,5.446 0.588,1.168 1.642,1.916 2.839,2.168 -0.564,0.884 -0.835,1.972 -0.925,3.156 -1.782,-0.45 -3.735,-0.191 -5.25,0.639a0.799,0.808 90,0 0,-0.132 0.172,0.799 0.808,90 0,0 -0.132,0.172 0.799,0.808 90,0 0,-0.132 0.39,0.799 0.808,90 0,0 0.044,0.304 0.799,0.808 90,0 0,0.437 0.433,0.799 0.808,90 0,0 0.131,0.043 0.799,0.808 90,0 0,0.131 0.043,0.799 0.808,90 0,0 0.218,-0 0.799,0.808 90,0 0,0.263 -0.129c1.29,-0.707 2.97,-0.892 4.375,-0.425 0.096,0.882 0.341,1.782 0.695,2.683 -0.456,0.072 -0.936,0.177 -1.357,0.343 -1.207,0.48 -2.245,1.302 -3.022,2.244a0.799,0.808 90,0 0,-0.044 0.129,0.799 0.808,90 0,0 -0.088,0.258 0.799,0.808 90,0 0,0.044 0.304,0.799 0.808,90 0,0 0.393,0.563 0.799,0.808 90,0 0,0.394 0.131,0.799 0.808,90 0,0 0.175,-0.086 0.799,0.808 90,0 0,0.175 -0.086,0.799 0.808,90 0,0 0.219,-0.215c0.922,-1.115 2.409,-1.925 3.897,-2.025 0.494,0.845 1.016,1.575 1.658,2.209 -1.232,1.149 -2.043,2.664 -2.239,4.234a0.799,0.808 90,0 0,-0.001 0.215,0.799 0.808,90 0,0 0.174,0.346 0.799,0.808 90,0 0,0.043 0.086,0.799 0.808,90 0,0 0.219,0.217 0.799,0.808 90,0 0,0.131 0.043,0.799 0.808,90 0,0 0.131,0.043 0.799,0.808 90,0 0,0.218 -0,0.799 0.808,90 0,0 0.218,-0 0.799,0.808 90,0 0,0.132 -0.172,0.799 0.808,90 0,0 0.175,-0.086 0.799,0.808 90,0 0,0.044 -0.129,0.799 0.808,90 0,0 0.088,-0.258 0.799,0.808 90,0 0,0.044 -0.129c0.159,-1.272 0.816,-2.493 1.799,-3.371 1.966,1.394 4.252,1.797 6.122,0.876 1.892,-0.931 2.903,-3.018 2.941,-5.444 1.327,-0.259 2.813,0.014 3.936,0.656a0.799,0.808 90,0 0,0.174 0.129,0.799 0.808,90 0,0 0.131,0.043 0.799,0.808 90,0 0,0.088 -0.043,0.799 0.808,90 0,0 0.218,-0 0.799,0.808 90,0 0,0.087 -0.043,0.799 0.808,90 0,0 0.175,-0.086 0.799,0.808 90,0 0,0.132 -0.172,0.799 0.808,90 0,0 0.132,-0.172 0.799,0.808 90,0 0,0.045 -0.562,0.799 0.808,90 0,0 -0.043,-0.086 0.799,0.808 90,0 0,-0.262 -0.303,0.799 0.808,90 0,0 -0.131,-0.043c-1.386,-0.792 -3.097,-1.066 -4.766,-0.787 -0.125,-0.89 -0.309,-1.783 -0.695,-2.683 0.982,-1.073 2.47,-1.756 3.896,-1.81a0.799,0.808 90,0 0,0.131 0.043,0.799 0.808,90 0,0 0.087,-0.043 0.799,0.808 90,0 0,0.482 -0.345,0.799 0.808,90 0,0 0.044,-0.129 0.799,0.808 90,0 0,0.088 -0.476,0.799 0.808,90 0,0 -0.043,-0.086 0.799,0.808 90,0 0,-0.262 -0.303,0.799 0.808,90 0,0 -0.043,-0.086 0.799,0.808 90,0 0,-0.394 -0.131,0.799 0.808,90 0,0 -0.131,-0.043c-1.227,0.048 -2.518,0.369 -3.633,1.031 -0.388,0.233 -0.762,0.548 -1.095,0.864 -0.513,-0.821 -1.093,-1.559 -1.746,-2.166 0.47,-1.39 1.632,-2.605 2.98,-3.195a0.799,0.808 90,0 0,0.175 -0.086,0.799 0.808,90 0,0 0.132,-0.172 0.799,0.808 90,0 0,0.132 -0.172,0.799 0.808,90 0,0 0.132,-0.39 0.799,0.808 90,0 0,-0.043 -0.086,0.799 0.808,90 0,0 -0.174,-0.346 0.799,0.808 90,0 0,-0.043 -0.086,0.799 0.808,90 0,0 -0.437,-0.217 0.799,0.808 90,0 0,-0.175 0.086,0.799 0.808,90 0,0 -0.131,-0.043 0.799,0.808 90,0 0,-0.087 0.043,0.799 0.808,90 0,0 -0.131,-0.043c-1.584,0.694 -2.97,2.079 -3.681,3.756 -1.012,-0.644 -2.048,-1.087 -3.104,-1.173 0.53,-1.101 0.642,-2.414 0.05,-3.59 -1.023,-2.031 -3.541,-2.795 -5.596,-1.783z"
|
||||
android:fillColor="#ff2a2a"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"/>
|
||||
<path
|
||||
android:pathData="m66.624,32.489c0.281,-0.138 1.896,2.465 3.62,5.889 1.724,3.424 2.893,6.354 2.613,6.492 -0.281,0.138 -1.94,-2.551 -3.663,-5.975 -1.724,-3.424 -2.85,-6.268 -2.569,-6.406z"
|
||||
android:fillColor="#d40000"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"/>
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
@@ -3,20 +3,19 @@
|
||||
package="de.markusfisch.android.binaryeye"
|
||||
android:installLocation="auto">
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:resizeable="true"
|
||||
android:smallScreens="true"
|
||||
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"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:theme="@style/AppTheme">
|
||||
@@ -30,13 +29,22 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:name=".activity.CameraActivity"
|
||||
android:label="@string/scan_code">
|
||||
<intent-filter>
|
||||
<intent-filter android:label="@string/compose_barcode">
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<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"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
@@ -47,5 +55,17 @@
|
||||
android:scheme="http"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<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>
|
||||
|
||||
@@ -0,0 +1,466 @@
|
||||
package de.markusfisch.android.binaryeye.activity
|
||||
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.ResultMetadataType
|
||||
|
||||
import de.markusfisch.android.cameraview.widget.CameraView
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.db
|
||||
import de.markusfisch.android.binaryeye.app.hasCameraPermission
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
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.provider.MediaStore
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
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 lateinit var vibrator: Vibrator
|
||||
private lateinit var cameraView: CameraView
|
||||
private lateinit var zoomBar: SeekBar
|
||||
private lateinit var flashFab: View
|
||||
|
||||
private var preprocessor: Preprocessor? = null
|
||||
private var invert = false
|
||||
private var flash = false
|
||||
private var returnResult = false
|
||||
private var frontFacing = false
|
||||
private var fallbackBuffer: IntArray? = null
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
when (requestCode) {
|
||||
REQUEST_CAMERA -> if (grantResults.isNotEmpty() &&
|
||||
grantResults[0] != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
R.string.no_camera_no_fun,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setContentView(R.layout.activity_camera)
|
||||
|
||||
initSystemBars(this)
|
||||
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
|
||||
|
||||
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()
|
||||
initZoomBar()
|
||||
restoreZoom()
|
||||
|
||||
if (intent?.action == Intent.ACTION_SEND) {
|
||||
if (intent.type == "text/plain") {
|
||||
handleSendText(intent)
|
||||
} else if (intent.type?.startsWith("image/") == true) {
|
||||
handleSendImage(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
preprocessor?.destroy()
|
||||
fallbackBuffer = null
|
||||
saveZoom()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
System.gc()
|
||||
returnResult = "com.google.zxing.client.android.SCAN" == intent.action
|
||||
if (hasCameraPermission(this, REQUEST_CAMERA)) {
|
||||
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() {
|
||||
cameraView.close()
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedState: Bundle) {
|
||||
super.onRestoreInstanceState(savedState)
|
||||
zoomBar.max = savedState.getInt(ZOOM_MAX)
|
||||
zoomBar.progress = savedState.getInt(ZOOM_LEVEL)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putInt(ZOOM_MAX, zoomBar.max)
|
||||
outState.putInt(ZOOM_LEVEL, zoomBar.progress)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.activity_camera, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.create -> {
|
||||
startActivity(MainActivity.getEncodeIntent(this))
|
||||
true
|
||||
}
|
||||
R.id.history -> {
|
||||
startActivity(MainActivity.getHistoryIntent(this))
|
||||
true
|
||||
}
|
||||
R.id.switch_camera -> {
|
||||
switchCamera()
|
||||
true
|
||||
}
|
||||
R.id.info -> {
|
||||
openReadme()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchCamera() {
|
||||
closeCamera()
|
||||
frontFacing = frontFacing xor true
|
||||
openCamera()
|
||||
}
|
||||
|
||||
private fun openReadme() {
|
||||
startActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW, Uri.parse(
|
||||
"https://github.com/markusfisch/BinaryEye/blob/master/README.md"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleSendText(intent: Intent) {
|
||||
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
if (text?.isEmpty() == false) {
|
||||
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
|
||||
}
|
||||
val result = zxing.decodePositiveNegative(downsizeIfBigger(bitmap, 1024))
|
||||
if (result != null) {
|
||||
showResult(result)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
Toast.makeText(
|
||||
this,
|
||||
R.string.no_barcode_found,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
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.maxZoom
|
||||
if (zoomBar.max != max) {
|
||||
zoomBar.max = max
|
||||
zoomBar.progress = max / 10
|
||||
saveZoom()
|
||||
}
|
||||
parameters.zoom = zoomBar.progress
|
||||
} else {
|
||||
zoomBar.visibility = View.GONE
|
||||
}
|
||||
val sceneModes = parameters.supportedSceneModes
|
||||
sceneModes?.let {
|
||||
for (mode in sceneModes) {
|
||||
if (mode == Camera.Parameters.SCENE_MODE_BARCODE) {
|
||||
parameters.sceneMode = mode
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
CameraView.setAutoFocus(parameters)
|
||||
updateFlashFab(parameters.flashMode == null)
|
||||
}
|
||||
|
||||
override fun onCameraError() {
|
||||
Toast.makeText(
|
||||
this@CameraActivity,
|
||||
R.string.camera_error,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onCameraReady(camera: Camera) {
|
||||
val frameWidth = cameraView.frameWidth
|
||||
val frameHeight = cameraView.frameHeight
|
||||
val frameOrientation = cameraView.frameOrientation
|
||||
var decoding = true
|
||||
camera.setPreviewCallback { frameData, _ ->
|
||||
if (decoding) {
|
||||
val result = decodeFrame(
|
||||
frameData,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation
|
||||
)
|
||||
result?.let {
|
||||
cameraView.post {
|
||||
vibrator.vibrate(100)
|
||||
showResult(result)
|
||||
}
|
||||
decoding = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreviewStarted(camera: Camera) {
|
||||
}
|
||||
|
||||
override fun onCameraStopping(camera: Camera) {
|
||||
camera.setPreviewCallback(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun initZoomBar() {
|
||||
zoomBar.setOnSeekBarChangeListener(object :
|
||||
SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(
|
||||
seekBar: SeekBar,
|
||||
progress: Int,
|
||||
fromUser: Boolean
|
||||
) {
|
||||
setZoom(progress)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setZoom(zoom: Int) {
|
||||
val camera: Camera? = cameraView.camera
|
||||
camera?.let {
|
||||
try {
|
||||
val params = camera.parameters
|
||||
params.zoom = zoom
|
||||
camera.parameters = params
|
||||
} catch (e: RuntimeException) {
|
||||
// ignore; there's nothing we can do
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveZoom() {
|
||||
val editor = prefs.preferences.edit()
|
||||
editor.putInt(ZOOM_MAX, zoomBar.max)
|
||||
editor.putInt(ZOOM_LEVEL, zoomBar.progress)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun restoreZoom() {
|
||||
zoomBar.max = prefs.preferences.getInt(ZOOM_MAX, zoomBar.max)
|
||||
zoomBar.progress = prefs.preferences.getInt(
|
||||
ZOOM_LEVEL,
|
||||
zoomBar.progress
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateFlashFab(available: Boolean) {
|
||||
flashFab.visibility = if (available) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleTorchMode() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeFrame(
|
||||
frameData: ByteArray?,
|
||||
frameWidth: Int,
|
||||
frameHeight: Int,
|
||||
frameOrientation: Int
|
||||
): Result? {
|
||||
frameData ?: return null
|
||||
invert = invert xor true
|
||||
val pp = preprocessor ?: Preprocessor(
|
||||
this,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation
|
||||
)
|
||||
pp.process(frameData)
|
||||
preprocessor = pp
|
||||
return zxing.decode(
|
||||
frameData,
|
||||
pp.outWidth,
|
||||
pp.outHeight,
|
||||
invert
|
||||
)
|
||||
}
|
||||
|
||||
private fun showResult(result: Result) {
|
||||
val rawBytes = getRawBytes(result)
|
||||
|
||||
if (prefs.useHistory) {
|
||||
GlobalScope.launch {
|
||||
db.insertScan(
|
||||
System.currentTimeMillis(),
|
||||
result.text,
|
||||
rawBytes,
|
||||
result.barcodeFormat.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (returnResult) {
|
||||
val resultIntent = Intent()
|
||||
resultIntent.putExtra("SCAN_RESULT", result.text)
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
startActivity(
|
||||
MainActivity.getDecodeIntent(
|
||||
this,
|
||||
result.text,
|
||||
result.barcodeFormat,
|
||||
rawBytes
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REQUEST_CAMERA = 1
|
||||
private const val ZOOM_MAX = "zoom_max"
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
fun getRawBytes(result: Result): ByteArray? {
|
||||
val metadata = result.resultMetadata ?: return null
|
||||
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
|
||||
var bytes = ByteArray(0)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
for (seg in segments as Iterable<ByteArray>) {
|
||||
bytes += seg
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
@@ -1,46 +1,21 @@
|
||||
package de.markusfisch.android.binaryeye.activity
|
||||
|
||||
import de.markusfisch.android.cameraview.widget.CameraView
|
||||
import com.google.zxing.BarcodeFormat
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.addFragment
|
||||
import de.markusfisch.android.binaryeye.app.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.app.setFragment
|
||||
import de.markusfisch.android.binaryeye.fragment.CameraFragment
|
||||
import de.markusfisch.android.binaryeye.fragment.DecodeFragment
|
||||
import de.markusfisch.android.binaryeye.fragment.EncodeFragment
|
||||
import de.markusfisch.android.binaryeye.view.SystemBarMetrics
|
||||
import de.markusfisch.android.binaryeye.widget.LockOnView
|
||||
import de.markusfisch.android.binaryeye.fragment.HistoryFragment
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
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.View
|
||||
import android.view.Window
|
||||
import android.widget.Toast
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
lateinit var cameraView: CameraView
|
||||
lateinit var lockOnView: LockOnView
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray) {
|
||||
when (requestCode) {
|
||||
REQUEST_CAMERA -> if (grantResults.size > 0 &&
|
||||
grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
Toast.makeText(this,
|
||||
R.string.no_camera_no_fun,
|
||||
Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
val fm = supportFragmentManager
|
||||
if (fm != null && fm.backStackEntryCount > 0) {
|
||||
@@ -56,90 +31,70 @@ class MainActivity : AppCompatActivity() {
|
||||
setContentView(R.layout.activity_main)
|
||||
initSystemBars(this)
|
||||
|
||||
cameraView = findViewById(R.id.camera_view) as CameraView
|
||||
lockOnView = findViewById(R.id.lock_on) as LockOnView
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
|
||||
setUpListener()
|
||||
checkPermissions()
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
if (state == null) {
|
||||
setFragment(supportFragmentManager, CameraFragment())
|
||||
handleSendText(intent)
|
||||
setFragment(supportFragmentManager, when {
|
||||
intent?.hasExtra(HISTORY) == true -> HistoryFragment()
|
||||
intent?.hasExtra(ENCODE) == true -> EncodeFragment.newInstance(
|
||||
intent.getStringExtra(ENCODE)
|
||||
)
|
||||
intent?.hasExtra(DECODED_TEXT) == true -> DecodeFragment.newInstance(
|
||||
intent.getStringExtra(DECODED_TEXT),
|
||||
intent.getSerializableExtra(
|
||||
DECODED_FORMAT
|
||||
) as BarcodeFormat,
|
||||
intent.getByteArrayExtra(DECODED_RAW)
|
||||
)
|
||||
else -> DecodeFragment()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override protected fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
handleSendText(intent)
|
||||
}
|
||||
|
||||
private fun checkPermissions() {
|
||||
val permission = android.Manifest.permission.CAMERA
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this, permission) !=
|
||||
PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(permission),
|
||||
REQUEST_CAMERA)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpListener() {
|
||||
supportFragmentManager.addOnBackStackChangedListener { canBack() }
|
||||
canBack()
|
||||
}
|
||||
|
||||
private fun canBack() {
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(
|
||||
supportFragmentManager.backStackEntryCount > 0)
|
||||
}
|
||||
|
||||
private fun handleSendText(intent: Intent) {
|
||||
val type = intent.getType()
|
||||
var text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
if (!Intent.ACTION_SEND.equals(intent.getAction()) ||
|
||||
!"text/plain".equals(type) ||
|
||||
text.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
// consume this intent
|
||||
intent.setAction(null)
|
||||
|
||||
addFragment(supportFragmentManager,
|
||||
EncodeFragment.newInstance(text))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val REQUEST_CAMERA = 1
|
||||
private const val HISTORY = "history"
|
||||
private const val ENCODE = "encode"
|
||||
private const val DECODED_TEXT = "decoded_text"
|
||||
private const val DECODED_FORMAT = "decoded_format"
|
||||
private const val DECODED_RAW = "decoded_raw"
|
||||
|
||||
private fun initSystemBars(activity: AppCompatActivity?) {
|
||||
val view = activity?.findViewById(R.id.main_layout)
|
||||
if (view != null && setSystemBarColor(activity.window,
|
||||
ContextCompat.getColor(activity,
|
||||
R.color.primary_dark_translucent))) {
|
||||
view.setPadding(
|
||||
0,
|
||||
SystemBarMetrics.getStatusBarHeight(
|
||||
activity.resources),
|
||||
0,
|
||||
0)
|
||||
}
|
||||
fun getHistoryIntent(context: Context): Intent {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.putExtra(HISTORY, true)
|
||||
return intent
|
||||
}
|
||||
|
||||
private fun setSystemBarColor(window: Window, color: Int): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return false
|
||||
fun getEncodeIntent(
|
||||
context: Context,
|
||||
text: String? = "",
|
||||
isExternal: Boolean = false
|
||||
): Intent {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.putExtra(ENCODE, text)
|
||||
if (isExternal) {
|
||||
intent.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NO_HISTORY or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TASK or
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
)
|
||||
}
|
||||
return intent
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
return true
|
||||
fun getDecodeIntent(
|
||||
context: Context,
|
||||
text: String,
|
||||
format: BarcodeFormat,
|
||||
raw: ByteArray? = null
|
||||
): Intent {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.putExtra(DECODED_TEXT, text)
|
||||
intent.putExtra(DECODED_FORMAT, format)
|
||||
if (raw != null) {
|
||||
intent.putExtra(DECODED_RAW, raw)
|
||||
}
|
||||
return intent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class SplashActivity : AppCompatActivity() {
|
||||
// because that would happen after the app is fully
|
||||
// initialized what is too late
|
||||
|
||||
startActivity(Intent(applicationContext, MainActivity::class.java))
|
||||
startActivity(Intent(applicationContext, CameraActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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)
|
||||
val content = cursor.getString(contentIndex)
|
||||
holder.timeView.text = cursor.getString(timeIndex)
|
||||
holder.contentView.text = if (content.isEmpty()) {
|
||||
context.getString(R.string.binary_data)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
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,23 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import de.markusfisch.android.binaryeye.data.Database
|
||||
import de.markusfisch.android.binaryeye.preference.Preferences
|
||||
|
||||
import android.app.Application
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
|
||||
val db = Database()
|
||||
val prefs = Preferences()
|
||||
|
||||
class BinaryEyeApp : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// required to make RenderScript work for Lineage 16.0
|
||||
// possibly because of a system/device bug
|
||||
RenderScript.forceCompat()
|
||||
|
||||
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,8 +15,10 @@ fun addFragment(fm: FragmentManager?, fragment: Fragment) {
|
||||
fm?.let { getTransaction(fm, fragment).addToBackStack(null).commit() }
|
||||
}
|
||||
|
||||
@SuppressLint("CommitTransaction")
|
||||
private fun getTransaction(
|
||||
fm: FragmentManager,
|
||||
fragment: Fragment): FragmentTransaction {
|
||||
fm: FragmentManager,
|
||||
fragment: Fragment
|
||||
): FragmentTransaction {
|
||||
return fm.beginTransaction().replace(R.id.content_frame, fragment)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
private val nonPrintable = Pattern.compile("[\\x00-\\x08\\x0e-\\x1f]")
|
||||
|
||||
fun hasNonPrintableCharacters(s: String) = nonPrintable.matcher(s).find()
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.pm.PackageManager
|
||||
import android.support.v4.app.ActivityCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.Manifest
|
||||
|
||||
fun hasCameraPermission(activity: Activity, requestCode: Int): Boolean {
|
||||
return hasPermission(activity, Manifest.permission.CAMERA, requestCode)
|
||||
}
|
||||
|
||||
fun hasWritePermission(activity: Activity, requestCode: Int = 1): Boolean {
|
||||
return hasPermission(
|
||||
activity,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
requestCode
|
||||
)
|
||||
}
|
||||
|
||||
fun hasPermission(
|
||||
activity: Activity,
|
||||
permission: String,
|
||||
requestCode: Int
|
||||
): Boolean {
|
||||
return if (ContextCompat.checkSelfPermission(activity, permission) !=
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
arrayOf(permission),
|
||||
requestCode
|
||||
)
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import de.markusfisch.android.binaryeye.BuildConfig
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.support.v4.content.FileProvider
|
||||
import android.net.Uri
|
||||
|
||||
import java.io.File
|
||||
|
||||
fun shareText(context: Context, text: String, type: String = "text/plain") {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.putExtra(Intent.EXTRA_TEXT, text)
|
||||
intent.type = type
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
fun shareUri(context: Context, uri: Uri, type: String) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.type = type
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
fun shareFile(context: Context, file: File, type: String) {
|
||||
shareUri(context, getUriForFile(context, file), type)
|
||||
}
|
||||
|
||||
fun getUriForFile(context: Context, 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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
|
||||
fun initSystemBars(activity: AppCompatActivity?) {
|
||||
val view = activity?.findViewById(R.id.main_layout)
|
||||
if (view != null &&
|
||||
setSystemBarColor(
|
||||
activity.window,
|
||||
ContextCompat.getColor(
|
||||
activity,
|
||||
R.color.primary_dark_translucent
|
||||
)
|
||||
)
|
||||
) {
|
||||
view.setPadding(0, getStatusBarHeight(activity.resources), 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSystemBarColor(window: Window, color: Int): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun getStatusBarHeight(res: Resources): Int {
|
||||
return getIdentifierDimen(res, "status_bar_height")
|
||||
}
|
||||
|
||||
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,114 @@
|
||||
package de.markusfisch.android.binaryeye.data
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.hasNonPrintableCharacters
|
||||
|
||||
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_RAW,
|
||||
$SCANS_FORMAT
|
||||
FROM $SCANS
|
||||
WHERE $SCANS_ID = ?
|
||||
""", arrayOf("$id")
|
||||
)
|
||||
|
||||
fun insertScan(
|
||||
timestamp: Long,
|
||||
content: String,
|
||||
raw: ByteArray?,
|
||||
format: String
|
||||
): Long {
|
||||
val cv = ContentValues()
|
||||
cv.put(
|
||||
SCANS_DATETIME, DateFormat.format(
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
timestamp
|
||||
).toString()
|
||||
)
|
||||
if (hasNonPrintableCharacters(content) && raw != null) {
|
||||
cv.put(SCANS_CONTENT, "")
|
||||
cv.put(SCANS_RAW, raw)
|
||||
} else {
|
||||
cv.put(SCANS_CONTENT, content)
|
||||
}
|
||||
cv.put(SCANS_FORMAT, format)
|
||||
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, 2) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
createScans(db)
|
||||
}
|
||||
|
||||
override fun onUpgrade(
|
||||
db: SQLiteDatabase,
|
||||
oldVersion: Int,
|
||||
newVersion: Int
|
||||
) {
|
||||
if (oldVersion < 2) {
|
||||
addRawColumn(db)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SCANS = "scans"
|
||||
const val SCANS_ID = "_id"
|
||||
const val SCANS_DATETIME = "_datetime"
|
||||
const val SCANS_CONTENT = "content"
|
||||
const val SCANS_RAW = "raw"
|
||||
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_RAW BLOB,
|
||||
$SCANS_FORMAT TEXT NOT NULL
|
||||
)"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun addRawColumn(db: SQLiteDatabase) {
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_RAW BLOB")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import com.google.zxing.BarcodeFormat
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.shareFile
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
@@ -14,56 +15,110 @@ 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,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View? {
|
||||
activity.setTitle(R.string.view_barcode)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_barcode,
|
||||
container,
|
||||
false
|
||||
)
|
||||
|
||||
val args = arguments
|
||||
args?.let {
|
||||
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) {
|
||||
var message = e.message
|
||||
if (message == null || message.isEmpty()) {
|
||||
message = getString(R.string.error_encoding_barcode)
|
||||
}
|
||||
Toast.makeText(
|
||||
activity,
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun share(bitmap: Bitmap) {
|
||||
GlobalScope.launch {
|
||||
val file = saveBitmap(bitmap)
|
||||
GlobalScope.launch(Main) {
|
||||
file?.let {
|
||||
shareFile(context, file, "image/png")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val CONTENT = "content"
|
||||
private val FORMAT = "format"
|
||||
private val SIZE = "size"
|
||||
private const val CONTENT = "content"
|
||||
private const val FORMAT = "format"
|
||||
private const val SIZE = "size"
|
||||
|
||||
fun newInstance(
|
||||
content: String,
|
||||
format: BarcodeFormat,
|
||||
size: Int): Fragment {
|
||||
content: String,
|
||||
format: BarcodeFormat,
|
||||
size: Int
|
||||
): Fragment {
|
||||
val args = Bundle()
|
||||
args.putString(CONTENT, content)
|
||||
args.putSerializable(FORMAT, format)
|
||||
args.putInt(SIZE, size)
|
||||
val fragment = BarcodeFragment()
|
||||
fragment.setArguments(args)
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?): View? {
|
||||
activity.setTitle(R.string.view_barcode)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_barcode,
|
||||
container,
|
||||
false)
|
||||
|
||||
val args = getArguments()
|
||||
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
|
||||
}
|
||||
view.findViewById<ImageView>(R.id.barcode).setImageBitmap(bitmap)
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,242 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.ResultPoint
|
||||
|
||||
import de.markusfisch.android.cameraview.widget.CameraView
|
||||
|
||||
import de.markusfisch.android.binaryeye.activity.MainActivity
|
||||
import de.markusfisch.android.binaryeye.app.addFragment
|
||||
import de.markusfisch.android.binaryeye.rs.YuvToGray
|
||||
import de.markusfisch.android.binaryeye.widget.LockOnView
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Rect
|
||||
import android.hardware.Camera
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Vibrator
|
||||
import android.support.v4.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
class CameraFragment : Fragment() {
|
||||
private val zxing = Zxing()
|
||||
private val decodingRunnable = Runnable {
|
||||
while (decoding) {
|
||||
val result = decodeFrame()
|
||||
if (result != null) {
|
||||
cameraView.post { found(result) }
|
||||
decoding = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var vibrator: Vibrator
|
||||
private lateinit var cameraView: CameraView
|
||||
private lateinit var lockOnView: LockOnView
|
||||
private lateinit var yuvToGray: YuvToGray
|
||||
|
||||
private var decoding = false
|
||||
private var decodingThread: Thread? = null
|
||||
private var frameData: ByteArray? = null
|
||||
private var frameWidth: Int = 0
|
||||
private var frameHeight: Int = 0
|
||||
private var frameOrientation: Int = 0
|
||||
private var odd = false
|
||||
private var flash = false
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?): View? {
|
||||
activity.setTitle(R.string.scan_code)
|
||||
vibrator = activity.getSystemService(
|
||||
Context.VIBRATOR_SERVICE) as Vibrator
|
||||
|
||||
yuvToGray = YuvToGray(context)
|
||||
|
||||
cameraView = (activity as MainActivity).cameraView
|
||||
cameraView.setOnCameraListener(object : CameraView.OnCameraListener {
|
||||
override fun onConfigureParameters(
|
||||
parameters: Camera.Parameters) {}
|
||||
|
||||
override fun onCameraStarted(camera: Camera) {
|
||||
frameWidth = cameraView.frameWidth
|
||||
frameHeight = cameraView.frameHeight
|
||||
frameOrientation = cameraView.frameOrientation
|
||||
camera.setPreviewCallback { data, _ -> frameData = data }
|
||||
}
|
||||
|
||||
override fun onCameraStopping(camera: Camera) {
|
||||
camera.setPreviewCallback(null)
|
||||
}
|
||||
})
|
||||
|
||||
lockOnView = (activity as MainActivity).lockOnView
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_camera,
|
||||
container,
|
||||
false)
|
||||
|
||||
view.findViewById<View>(R.id.create).setOnClickListener { _ ->
|
||||
addFragment(fragmentManager, EncodeFragment())
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
yuvToGray.destroy()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
System.gc()
|
||||
lockOnView.visibility = View.VISIBLE
|
||||
cameraView.visibility = View.VISIBLE
|
||||
cameraView.openAsync(CameraView.findCameraId(
|
||||
Camera.CameraInfo.CAMERA_FACING_BACK))
|
||||
startDecoding()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
cancelDecoding()
|
||||
cameraView.close()
|
||||
cameraView.visibility = View.GONE
|
||||
lockOnView.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.fragment_camera, menu)
|
||||
if (!context.packageManager.hasSystemFeature(
|
||||
PackageManager.FEATURE_CAMERA_FLASH)) {
|
||||
menu.findItem(R.id.flash).setVisible(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.flash -> {
|
||||
toggleTorchMode()
|
||||
true
|
||||
}
|
||||
R.id.info -> {
|
||||
openReadme()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
flash = flash xor true
|
||||
parameters?.let { camera.setParameters(parameters) }
|
||||
}
|
||||
|
||||
private fun openReadme() {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(
|
||||
"https://github.com/markusfisch/BinaryEye/blob/master/README.md")))
|
||||
}
|
||||
|
||||
private fun startDecoding() {
|
||||
activity ?: return
|
||||
|
||||
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) {
|
||||
// try again
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeFrame(): Result? {
|
||||
frameData ?: return null
|
||||
return zxing.decodeBitmap(yuvToGray.convert(
|
||||
frameData!!, frameWidth, frameHeight, frameOrientation))
|
||||
}
|
||||
|
||||
private fun found(result: Result) {
|
||||
cancelDecoding()
|
||||
vibrator.vibrate(100)
|
||||
lockOnView.lock(getRelativeBounds(result.resultPoints), 250)
|
||||
lockOnView.postDelayed({
|
||||
addFragment(fragmentManager, DecodeFragment.newInstance(
|
||||
result.text,
|
||||
result.getBarcodeFormat()))
|
||||
}, 500)
|
||||
}
|
||||
|
||||
private fun getRelativeBounds(resultPoints: Array<ResultPoint>): Rect {
|
||||
val fw: Float
|
||||
val fh: Float
|
||||
if (frameOrientation == 90 || frameOrientation == 270) {
|
||||
fw = frameHeight.toFloat()
|
||||
fh = frameWidth.toFloat()
|
||||
} else {
|
||||
fw = frameWidth.toFloat()
|
||||
fh = frameHeight.toFloat()
|
||||
}
|
||||
val viewRect = cameraView.previewRect
|
||||
val xf = fw / viewRect.width()
|
||||
val yf = fh / viewRect.height()
|
||||
val bounds = getBounds(resultPoints)
|
||||
bounds.set(
|
||||
Math.round(bounds.left / xf),
|
||||
Math.round(bounds.top / yf),
|
||||
Math.round(bounds.right / xf),
|
||||
Math.round(bounds.bottom / yf))
|
||||
bounds.offset(viewRect.left, viewRect.top)
|
||||
return bounds
|
||||
}
|
||||
|
||||
private fun getBounds(resultPoints: Array<ResultPoint>): Rect {
|
||||
val bounds = Rect(0xffffff, 0xffffff, 0, 0)
|
||||
for (resultPoint in resultPoints) {
|
||||
val x = Math.round(resultPoint.x)
|
||||
val y = Math.round(resultPoint.y)
|
||||
bounds.set(
|
||||
Math.min(bounds.left, x),
|
||||
Math.min(bounds.top, y),
|
||||
Math.max(bounds.right, x),
|
||||
Math.max(bounds.bottom, y))
|
||||
}
|
||||
return bounds
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,22 @@ package de.markusfisch.android.binaryeye.fragment
|
||||
import com.google.zxing.BarcodeFormat
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.addFragment
|
||||
import de.markusfisch.android.binaryeye.app.hasNonPrintableCharacters
|
||||
import de.markusfisch.android.binaryeye.app.hasWritePermission
|
||||
import de.markusfisch.android.binaryeye.app.shareText
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.support.v4.app.Fragment
|
||||
import android.text.ClipboardManager
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
@@ -18,58 +26,108 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URLEncoder
|
||||
|
||||
class DecodeFragment : Fragment() {
|
||||
companion object {
|
||||
private val CONTENT = "content"
|
||||
private val FORMAT = "format"
|
||||
|
||||
fun newInstance(content: String, format: BarcodeFormat): Fragment {
|
||||
val args = Bundle()
|
||||
args.putString(CONTENT, content)
|
||||
args.putSerializable(FORMAT, format)
|
||||
val fragment = DecodeFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var contentView: EditText
|
||||
private lateinit var formatView: TextView
|
||||
private lateinit var hexView: TextView
|
||||
private lateinit var format: BarcodeFormat
|
||||
|
||||
private var isBinary = false
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?): View {
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View {
|
||||
activity.setTitle(R.string.content)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_decode,
|
||||
container,
|
||||
false)
|
||||
R.layout.fragment_decode,
|
||||
container,
|
||||
false
|
||||
)
|
||||
|
||||
val content = arguments?.getString(CONTENT) ?: ""
|
||||
format = arguments?.getSerializable(FORMAT) as BarcodeFormat? ?:
|
||||
BarcodeFormat.QR_CODE
|
||||
isBinary = hasNonPrintableCharacters(content) or content.isEmpty()
|
||||
val raw = arguments?.getByteArray(RAW) ?: content.toByteArray()
|
||||
format = arguments?.getSerializable(FORMAT) as BarcodeFormat? ?: BarcodeFormat.QR_CODE
|
||||
|
||||
contentView = view.findViewById<EditText>(R.id.content)
|
||||
contentView.setText(content)
|
||||
contentView = view.findViewById(R.id.content) as EditText
|
||||
val shareFab = view.findViewById<View>(R.id.share)
|
||||
|
||||
view.findViewById<View>(R.id.share).setOnClickListener { _ ->
|
||||
share(getContent())
|
||||
if (!isBinary) {
|
||||
contentView.setText(content)
|
||||
contentView.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
updateFormatAndHex(getContent().toByteArray())
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
before: Int,
|
||||
count: Int
|
||||
) {
|
||||
}
|
||||
})
|
||||
shareFab.setOnClickListener { v ->
|
||||
shareText(v.context, getContent())
|
||||
}
|
||||
} else {
|
||||
contentView.setText(R.string.binary_data)
|
||||
contentView.isEnabled = false
|
||||
(shareFab as ImageView).setImageResource(R.drawable.ic_action_save)
|
||||
shareFab.setOnClickListener {
|
||||
askForFileNameAndSave(raw)
|
||||
}
|
||||
}
|
||||
|
||||
formatView = view.findViewById(R.id.format)
|
||||
hexView = view.findViewById(R.id.hex)
|
||||
|
||||
updateFormatAndHex(raw)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun updateFormatAndHex(bytes: ByteArray) {
|
||||
formatView.text = resources.getQuantityString(
|
||||
R.plurals.barcode_info,
|
||||
bytes.size,
|
||||
format.toString(),
|
||||
bytes.size
|
||||
)
|
||||
hexView.text = hexDump(bytes, 33)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.fragment_decode, menu)
|
||||
if (isBinary) {
|
||||
menu.findItem(R.id.copy_to_clipboard).isVisible = false
|
||||
menu.findItem(R.id.open_url).isVisible = false
|
||||
menu.findItem(R.id.create).isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
@@ -83,8 +141,10 @@ class DecodeFragment : Fragment() {
|
||||
true
|
||||
}
|
||||
R.id.create -> {
|
||||
addFragment(fragmentManager,
|
||||
EncodeFragment.newInstance(getContent(), format))
|
||||
addFragment(
|
||||
fragmentManager,
|
||||
EncodeFragment.newInstance(getContent(), format)
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
@@ -92,38 +152,150 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun getContent(): String {
|
||||
return contentView.getText().toString()
|
||||
return contentView.text.toString()
|
||||
}
|
||||
|
||||
private fun copyToClipboard(text: String) {
|
||||
activity ?: return
|
||||
|
||||
val cm = activity.getSystemService(
|
||||
Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
cm.setText(text)
|
||||
Toast.makeText(activity,
|
||||
R.string.put_into_clipboard,
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Context.CLIPBOARD_SERVICE
|
||||
) as ClipboardManager
|
||||
cm.text = text
|
||||
Toast.makeText(
|
||||
activity,
|
||||
R.string.put_into_clipboard,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun openUrl(url: String) {
|
||||
private fun openUrl(url: String, googleIfNoUrl: Boolean = true) {
|
||||
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 if (googleIfNoUrl) {
|
||||
openUrl(
|
||||
"https://www.google.com/search?q=" + URLEncoder.encode(
|
||||
url,
|
||||
"utf-8"
|
||||
),
|
||||
false
|
||||
)
|
||||
} else {
|
||||
Toast.makeText(activity,
|
||||
R.string.cannot_resolve_action,
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(
|
||||
activity,
|
||||
R.string.cannot_resolve_action,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun share(text: String) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.putExtra(Intent.EXTRA_TEXT, text)
|
||||
intent.setType("text/plain")
|
||||
startActivity(intent)
|
||||
private fun askForFileNameAndSave(raw: ByteArray) {
|
||||
val ac = activity
|
||||
ac ?: return
|
||||
val view = ac.layoutInflater.inflate(R.layout.dialog_save_file, null)
|
||||
val editText = view.findViewById(R.id.file_name) as EditText
|
||||
AlertDialog.Builder(ac)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (hasWritePermission(ac)) {
|
||||
val messageId = saveByteArray(
|
||||
editText.text.toString(),
|
||||
raw
|
||||
)
|
||||
if (messageId > 0) {
|
||||
Toast.makeText(ac, messageId, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONTENT = "content"
|
||||
private const val FORMAT = "format"
|
||||
private const val RAW = "raw"
|
||||
|
||||
fun newInstance(
|
||||
content: String,
|
||||
format: BarcodeFormat,
|
||||
raw: ByteArray? = null
|
||||
): Fragment {
|
||||
val args = Bundle()
|
||||
args.putString(CONTENT, content)
|
||||
args.putSerializable(FORMAT, format)
|
||||
if (raw != null) {
|
||||
args.putByteArray(RAW, raw)
|
||||
}
|
||||
val fragment = DecodeFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hexDump(bytes: ByteArray, charsPerLine: Int): String {
|
||||
if (charsPerLine < 4 || bytes.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
val dump = StringBuilder()
|
||||
val hex = StringBuilder()
|
||||
val ascii = StringBuilder()
|
||||
val itemsPerLine = (charsPerLine - 1) / 4
|
||||
val len = bytes.size
|
||||
var i = 0
|
||||
while (true) {
|
||||
val ord = bytes[i]
|
||||
hex.append(String.format("%02X ", ord))
|
||||
ascii.append(if (ord > 31) ord.toChar() else " ")
|
||||
|
||||
++i
|
||||
|
||||
val posInLine = i % itemsPerLine
|
||||
val atEnd = i >= len
|
||||
var atLineEnd = posInLine == 0
|
||||
if (atEnd && !atLineEnd) {
|
||||
for (j in posInLine until itemsPerLine) {
|
||||
hex.append(" ")
|
||||
}
|
||||
atLineEnd = true
|
||||
}
|
||||
if (atLineEnd) {
|
||||
dump.append(hex.toString())
|
||||
dump.append(" ")
|
||||
dump.append(ascii.toString())
|
||||
dump.append("\n")
|
||||
hex.setLength(0)
|
||||
ascii.setLength(0)
|
||||
}
|
||||
if (atEnd) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return dump.toString()
|
||||
}
|
||||
|
||||
fun saveByteArray(name: String, raw: ByteArray): Int {
|
||||
return try {
|
||||
val file = File(
|
||||
Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS
|
||||
),
|
||||
name
|
||||
)
|
||||
if (file.exists()) {
|
||||
return R.string.error_file_exists
|
||||
}
|
||||
file.writeBytes(raw)
|
||||
0
|
||||
} catch (e: IOException) {
|
||||
R.string.error_saving_binary_data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,82 +20,78 @@ import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
|
||||
class EncodeFragment : Fragment() {
|
||||
companion object {
|
||||
private val CONTENT = "content"
|
||||
private val FORMAT = "format"
|
||||
|
||||
fun newInstance(
|
||||
content: String,
|
||||
format: BarcodeFormat = BarcodeFormat.AZTEC): Fragment {
|
||||
val args = Bundle()
|
||||
args.putString(CONTENT, content)
|
||||
args.putSerializable(FORMAT, format)
|
||||
val fragment = EncodeFragment()
|
||||
fragment.setArguments(args)
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var formatView: Spinner
|
||||
private lateinit var sizeView: TextView
|
||||
private lateinit var sizeBarView: SeekBar
|
||||
private val writers = arrayListOf(
|
||||
BarcodeFormat.AZTEC,
|
||||
BarcodeFormat.CODABAR,
|
||||
BarcodeFormat.CODE_39,
|
||||
BarcodeFormat.CODE_128,
|
||||
BarcodeFormat.DATA_MATRIX,
|
||||
BarcodeFormat.EAN_8,
|
||||
BarcodeFormat.EAN_13,
|
||||
BarcodeFormat.ITF,
|
||||
BarcodeFormat.PDF_417,
|
||||
BarcodeFormat.QR_CODE,
|
||||
BarcodeFormat.UPC_A
|
||||
BarcodeFormat.AZTEC,
|
||||
BarcodeFormat.CODABAR,
|
||||
BarcodeFormat.CODE_39,
|
||||
BarcodeFormat.CODE_128,
|
||||
BarcodeFormat.DATA_MATRIX,
|
||||
BarcodeFormat.EAN_8,
|
||||
BarcodeFormat.EAN_13,
|
||||
BarcodeFormat.ITF,
|
||||
BarcodeFormat.PDF_417,
|
||||
BarcodeFormat.QR_CODE,
|
||||
BarcodeFormat.UPC_A
|
||||
)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?): View {
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View {
|
||||
activity.setTitle(R.string.compose_barcode)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_encode,
|
||||
container,
|
||||
false)
|
||||
R.layout.fragment_encode,
|
||||
container,
|
||||
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(writers.indexOf(
|
||||
args.getSerializable(FORMAT) as BarcodeFormat?))
|
||||
formatView.setSelection(
|
||||
writers.indexOf(
|
||||
args.getSerializable(FORMAT) as BarcodeFormat?
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(
|
||||
v.context,
|
||||
R.string.error_no_content,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
hideSoftKeyboard(contentView)
|
||||
addFragment(fragmentManager, BarcodeFragment.newInstance(
|
||||
addFragment(
|
||||
fragmentManager, BarcodeFragment.newInstance(
|
||||
content,
|
||||
format,
|
||||
size))
|
||||
size
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,34 +99,53 @@ class EncodeFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun initSizeBar() {
|
||||
updateSize(sizeBarView.getProgress())
|
||||
updateSize(sizeBarView.progress)
|
||||
sizeBarView.setOnSeekBarChangeListener(
|
||||
object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(
|
||||
object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(
|
||||
seekBar: SeekBar,
|
||||
progressValue: Int,
|
||||
fromUser: Boolean) {
|
||||
updateSize(progressValue)
|
||||
}
|
||||
fromUser: Boolean
|
||||
) {
|
||||
updateSize(progressValue)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
private fun hideSoftKeyboard(view: View) {
|
||||
val im = activity?.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE) as InputMethodManager?
|
||||
Context.INPUT_METHOD_SERVICE
|
||||
) as InputMethodManager?
|
||||
im?.let {
|
||||
im.hideSoftInputFromWindow(view.getWindowToken(), 0)
|
||||
im.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONTENT = "content"
|
||||
private const val FORMAT = "format"
|
||||
|
||||
fun newInstance(
|
||||
content: String,
|
||||
format: BarcodeFormat = BarcodeFormat.AZTEC
|
||||
): Fragment {
|
||||
val args = Bundle()
|
||||
args.putString(CONTENT, content)
|
||||
args.putSerializable(FORMAT, format)
|
||||
val fragment = EncodeFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import com.google.zxing.BarcodeFormat
|
||||
|
||||
import de.markusfisch.android.binaryeye.adapter.ScansAdapter
|
||||
import de.markusfisch.android.binaryeye.app.addFragment
|
||||
import de.markusfisch.android.binaryeye.app.db
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.app.shareText
|
||||
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.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
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
|
||||
private lateinit var progressView: View
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
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.share)
|
||||
fab.setOnClickListener { v ->
|
||||
pickListSeparatorAndShare(v.context)
|
||||
}
|
||||
|
||||
progressView = view.findViewById(R.id.progress_view)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
(listView.adapter as ScansAdapter?)?.changeCursor(null)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
update(context)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.fragment_history, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.clear -> {
|
||||
askToRemoveAllScans(context)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
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 rawIndex = cursor.getColumnIndex(Database.SCANS_RAW)
|
||||
val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
|
||||
try {
|
||||
addFragment(
|
||||
fragmentManager,
|
||||
DecodeFragment.newInstance(
|
||||
cursor.getString(contentIndex),
|
||||
BarcodeFormat.valueOf(cursor.getString(formatIndex)),
|
||||
cursor.getBlob(rawIndex)
|
||||
)
|
||||
)
|
||||
} 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()
|
||||
}
|
||||
|
||||
private fun pickListSeparatorAndShare(context: Context) {
|
||||
val separators = context.resources.getStringArray(
|
||||
R.array.list_separators_values
|
||||
)
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(R.string.pick_list_separator)
|
||||
.setItems(R.array.list_separators_names) { _, which ->
|
||||
shareScans(separators[which])
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun shareScans(separator: String) {
|
||||
if (progressView.visibility == View.VISIBLE) {
|
||||
return
|
||||
}
|
||||
progressView.visibility = View.VISIBLE
|
||||
GlobalScope.launch {
|
||||
val cursor = db.getScans()
|
||||
cursor?.let {
|
||||
val sb = StringBuilder()
|
||||
if (cursor.moveToFirst()) {
|
||||
val contentIndex = cursor.getColumnIndex(
|
||||
Database.SCANS_CONTENT
|
||||
)
|
||||
do {
|
||||
sb.append(cursor.getString(contentIndex))
|
||||
sb.append(separator)
|
||||
} while (cursor.moveToNext())
|
||||
}
|
||||
cursor.close()
|
||||
val text = sb.toString()
|
||||
if (text.isNotEmpty()) {
|
||||
GlobalScope.launch(Main) {
|
||||
progressView.visibility = View.GONE
|
||||
shareText(context, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package de.markusfisch.android.binaryeye.rs
|
||||
|
||||
import de.markusfisch.android.binaryeye.renderscript.ScriptC_rotator
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v8.renderscript.Allocation
|
||||
import android.support.v8.renderscript.Element
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
import android.support.v8.renderscript.ScriptIntrinsicResize
|
||||
import android.support.v8.renderscript.Type
|
||||
|
||||
class Preprocessor(
|
||||
context: Context,
|
||||
width: Int,
|
||||
height: Int,
|
||||
private val orientation: Int
|
||||
) {
|
||||
var outWidth = 0
|
||||
var outHeight = 0
|
||||
|
||||
private val rs = RenderScript.create(context)
|
||||
private val resizeScript = ScriptIntrinsicResize.create(rs)
|
||||
private val rotatorScript = ScriptC_rotator(rs)
|
||||
|
||||
private var yuvType: Type? = null
|
||||
private var yuvAlloc: Allocation? = null
|
||||
private var resizedType: Type? = null
|
||||
private var resizedAlloc: Allocation? = null
|
||||
private var rotatedType: Type? = null
|
||||
private var rotatedAlloc: Allocation? = null
|
||||
|
||||
init {
|
||||
yuvType = Type.createXY(
|
||||
rs,
|
||||
Element.U8(rs),
|
||||
width,
|
||||
height // use only grayscale part
|
||||
)
|
||||
yuvAlloc = Allocation.createTyped(
|
||||
rs,
|
||||
yuvType,
|
||||
Allocation.USAGE_SCRIPT
|
||||
)
|
||||
|
||||
val f = .75f
|
||||
var w = Math.round(width * f)
|
||||
var h = Math.round(height * f)
|
||||
|
||||
resizedType = Type.createXY(
|
||||
rs,
|
||||
Element.U8(rs),
|
||||
w,
|
||||
h
|
||||
)
|
||||
resizedAlloc = Allocation.createTyped(
|
||||
rs,
|
||||
resizedType,
|
||||
Allocation.USAGE_SCRIPT
|
||||
)
|
||||
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
val tmp = w
|
||||
w = h
|
||||
h = tmp
|
||||
}
|
||||
|
||||
outWidth = w
|
||||
outHeight = h
|
||||
|
||||
if (orientation != 0) {
|
||||
rotatedType = Type.createXY(
|
||||
rs,
|
||||
Element.U8(rs),
|
||||
w,
|
||||
h
|
||||
)
|
||||
rotatedAlloc = Allocation.createTyped(
|
||||
rs,
|
||||
rotatedType,
|
||||
Allocation.USAGE_SCRIPT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
yuvType?.destroy()
|
||||
yuvType = null
|
||||
yuvAlloc?.destroy()
|
||||
yuvAlloc = null
|
||||
resizedType?.destroy()
|
||||
resizedType = null
|
||||
resizedAlloc?.destroy()
|
||||
resizedAlloc = null
|
||||
rotatedType?.destroy()
|
||||
rotatedType = null
|
||||
rotatedAlloc?.destroy()
|
||||
rotatedAlloc = null
|
||||
resizeScript.destroy()
|
||||
rotatorScript.destroy()
|
||||
rs.destroy()
|
||||
}
|
||||
|
||||
fun process(frame: ByteArray) {
|
||||
yuvAlloc?.copyFrom(frame)
|
||||
|
||||
resizeScript.setInput(yuvAlloc)
|
||||
resizeScript.forEach_bicubic(resizedAlloc)
|
||||
|
||||
val t = resizedType
|
||||
if (t == null || orientation == 0) {
|
||||
resizedAlloc?.copyTo(frame)
|
||||
} else {
|
||||
rotatorScript._inImage = resizedAlloc
|
||||
rotatorScript._inWidth = t.x
|
||||
rotatorScript._inHeight = t.y
|
||||
when (orientation) {
|
||||
90 -> rotatorScript.forEach_rotate90(
|
||||
rotatedAlloc,
|
||||
rotatedAlloc
|
||||
)
|
||||
180 -> rotatorScript.forEach_rotate180(
|
||||
rotatedAlloc,
|
||||
rotatedAlloc
|
||||
)
|
||||
270 -> rotatorScript.forEach_rotate270(
|
||||
rotatedAlloc,
|
||||
rotatedAlloc
|
||||
)
|
||||
}
|
||||
rotatedAlloc?.copyTo(frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.rs
|
||||
|
||||
import de.markusfisch.android.binaryeye.renderscript.ScriptC_rotator
|
||||
import de.markusfisch.android.binaryeye.renderscript.ScriptC_yuv2gray
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.support.v8.renderscript.Allocation
|
||||
import android.support.v8.renderscript.Element
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
import android.support.v8.renderscript.Type
|
||||
|
||||
class YuvToGray(context: Context) {
|
||||
private val rs: RenderScript = RenderScript.create(context)
|
||||
private val rotatorScript: ScriptC_rotator = ScriptC_rotator(rs)
|
||||
private val yuv2grayScript: ScriptC_yuv2gray = ScriptC_yuv2gray(rs)
|
||||
|
||||
private var yuvType: Type? = null
|
||||
private var rgbaType: Type? = null
|
||||
private var yuvAlloc: Allocation? = null
|
||||
private var rgbaAlloc: Allocation? = null
|
||||
private var destAlloc: Allocation? = null
|
||||
private var dest: Bitmap? = null
|
||||
private var odd = true
|
||||
|
||||
fun destroy() {
|
||||
yuvType?.destroy()
|
||||
yuvType = null
|
||||
rgbaType?.destroy()
|
||||
rgbaType = null
|
||||
yuvAlloc?.destroy()
|
||||
yuvAlloc = null
|
||||
rgbaAlloc?.destroy()
|
||||
rgbaAlloc = null
|
||||
destAlloc?.destroy()
|
||||
destAlloc = null
|
||||
dest?.recycle()
|
||||
dest = null
|
||||
rotatorScript.destroy()
|
||||
yuv2grayScript.destroy()
|
||||
rs.destroy()
|
||||
}
|
||||
|
||||
fun convert(
|
||||
data: ByteArray,
|
||||
width: Int,
|
||||
height: Int,
|
||||
orientation: Int): Bitmap {
|
||||
if (dest == null) {
|
||||
yuvType = Type.createXY(rs, Element.U8(rs), width, height * 3 / 2)
|
||||
yuvAlloc = Allocation.createTyped(rs, yuvType,
|
||||
Allocation.USAGE_SCRIPT)
|
||||
rgbaType = Type.createXY(rs, Element.RGBA_8888(rs), width, height)
|
||||
rgbaAlloc = Allocation.createTyped(rs, rgbaType,
|
||||
Allocation.USAGE_SCRIPT)
|
||||
|
||||
var w = width
|
||||
var h = height
|
||||
when (orientation) {
|
||||
90, 270 -> {
|
||||
val tmp = w
|
||||
w = h
|
||||
h = tmp
|
||||
}
|
||||
}
|
||||
|
||||
dest = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||
destAlloc = Allocation.createFromBitmap(
|
||||
rs,
|
||||
dest,
|
||||
Allocation.MipmapControl.MIPMAP_NONE,
|
||||
Allocation.USAGE_SCRIPT)
|
||||
}
|
||||
|
||||
yuvAlloc?.copyFrom(data)
|
||||
yuv2grayScript._inYUV = yuvAlloc
|
||||
// width/height are uint_32 but Kotlin wants toLong()
|
||||
yuv2grayScript._inWidth = width.toLong()
|
||||
yuv2grayScript._inHeight = height.toLong()
|
||||
|
||||
// invert every second frame to also read inverted barcodes
|
||||
if (odd) {
|
||||
yuv2grayScript.forEach_yuv2gray(rgbaAlloc)
|
||||
} else {
|
||||
yuv2grayScript.forEach_yuv2inverted(rgbaAlloc)
|
||||
}
|
||||
odd = odd xor true
|
||||
|
||||
rotatorScript._inImage = rgbaAlloc
|
||||
rotatorScript._inWidth = width
|
||||
rotatorScript._inHeight = height
|
||||
|
||||
when (orientation) {
|
||||
0 -> destAlloc?.copyFrom(rgbaAlloc)
|
||||
90 -> rotatorScript.forEach_rotate90(destAlloc, destAlloc)
|
||||
180 -> rotatorScript.forEach_rotate180(destAlloc, destAlloc)
|
||||
270 -> rotatorScript.forEach_rotate270(destAlloc, destAlloc)
|
||||
}
|
||||
|
||||
destAlloc?.copyTo(dest)
|
||||
|
||||
return dest!!
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.view
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Point
|
||||
import android.os.Build
|
||||
import android.util.TypedValue
|
||||
|
||||
object SystemBarMetrics {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.widget
|
||||
|
||||
import de.markusfisch.android.binaryeye.view.SystemBarMetrics
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.graphics.Rect
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.util.AttributeSet
|
||||
import android.view.SurfaceView
|
||||
import android.view.SurfaceHolder
|
||||
|
||||
class LockOnView : SurfaceView {
|
||||
private val markerPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val restRect = Rect()
|
||||
private val startRect = Rect()
|
||||
private val endRect = Rect()
|
||||
private val currentRect = Rect()
|
||||
private val animationRunnable = Runnable {
|
||||
setAnimationStart()
|
||||
while (running) {
|
||||
val now = System.currentTimeMillis() - animationStart
|
||||
if (now > animationDuration) {
|
||||
break
|
||||
}
|
||||
animateCurrentRect(now)
|
||||
lockCanvasAndDraw()
|
||||
}
|
||||
lockCanvasAndDraw()
|
||||
running = false
|
||||
}
|
||||
|
||||
@Volatile private var running = false
|
||||
|
||||
private lateinit var markerTopLeft: Bitmap
|
||||
private lateinit var markerTopRight: Bitmap
|
||||
private lateinit var markerBottomLeft: Bitmap
|
||||
private lateinit var markerBottomRight: Bitmap
|
||||
|
||||
private var markerSize: Int = 0
|
||||
private var minSize: Int = 0
|
||||
private var thread: Thread? = null
|
||||
private var animationStart: Long = 0
|
||||
private var animationDuration: Long = 0
|
||||
|
||||
init {
|
||||
loadMarkers(context.resources)
|
||||
initSurfaceHolder(context)
|
||||
setZOrderOnTop(true)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) :
|
||||
super(context, attrs) {
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
|
||||
super(context, attrs, defStyleAttr) {
|
||||
}
|
||||
|
||||
fun lock(rect: Rect, ms: Int) {
|
||||
startRect.set(currentRect)
|
||||
if (running) {
|
||||
tintMarkers()
|
||||
fitRect(rect)
|
||||
endRect.set(rect)
|
||||
setAnimationStart()
|
||||
} else {
|
||||
startAnimation(rect, ms)
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
untintMarkers()
|
||||
startRect.set(restRect)
|
||||
currentRect.set(startRect)
|
||||
lockCanvasAndDraw()
|
||||
}
|
||||
|
||||
private fun loadMarkers(res: Resources) {
|
||||
val dp = res.displayMetrics.density
|
||||
markerTopLeft = BitmapFactory.decodeResource(res, R.drawable.marker)
|
||||
val width = markerTopLeft.width
|
||||
val height = markerTopLeft.height
|
||||
markerSize = Math.max(width, height)
|
||||
minSize = markerSize * 2 + Math.round(dp * 8f)
|
||||
val matrix = Matrix()
|
||||
matrix.setScale(-1f, 1f)
|
||||
markerTopRight = Bitmap.createBitmap(markerTopLeft,
|
||||
0, 0, width, height, matrix, false)
|
||||
matrix.setScale(1f, -1f)
|
||||
markerBottomLeft = Bitmap.createBitmap(markerTopLeft,
|
||||
0, 0, width, height, matrix, false)
|
||||
matrix.setScale(-1f, -1f)
|
||||
markerBottomRight = Bitmap.createBitmap(markerTopLeft,
|
||||
0, 0, width, height, matrix, false)
|
||||
}
|
||||
|
||||
private fun initSurfaceHolder(context: Context) {
|
||||
holder.setFormat(PixelFormat.TRANSPARENT)
|
||||
holder.addCallback(object : SurfaceHolder.Callback {
|
||||
override fun surfaceChanged(
|
||||
holder: SurfaceHolder,
|
||||
format: Int,
|
||||
width: Int,
|
||||
height: Int) {
|
||||
initRestRect(context, width, height)
|
||||
reset()
|
||||
}
|
||||
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {}
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
cancelAnimation()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun cancelAnimation() {
|
||||
running = false
|
||||
if (thread != null) {
|
||||
var retry = 100
|
||||
while (retry-- > 0) {
|
||||
try {
|
||||
thread?.join()
|
||||
retry = 0
|
||||
} catch (e: InterruptedException) {
|
||||
// try again
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAnimation(rect: Rect, ms: Int) {
|
||||
if (running) {
|
||||
return
|
||||
}
|
||||
if (rect == restRect) {
|
||||
untintMarkers()
|
||||
} else {
|
||||
tintMarkers()
|
||||
}
|
||||
running = true
|
||||
fitRect(rect)
|
||||
endRect.set(rect)
|
||||
animationDuration = ms.toLong()
|
||||
thread = Thread(animationRunnable)
|
||||
thread?.start()
|
||||
}
|
||||
|
||||
private fun initRestRect(context: Context, width: Int, height: Int) {
|
||||
val res = context.resources
|
||||
val dp = res.displayMetrics.density
|
||||
val padding = Math.round(16f * dp)
|
||||
var left = padding
|
||||
var top = padding + if (width > height)
|
||||
SystemBarMetrics.getStatusAndToolBarHeight(context)
|
||||
else
|
||||
0
|
||||
val navBar = SystemBarMetrics.getNavigationBarSize(res)
|
||||
val paddingLeftRight = padding * 2 + navBar.x
|
||||
val paddingTopBottom = top + padding + navBar.y
|
||||
val innerWidth = width - paddingLeftRight
|
||||
val innerHeight = height - paddingTopBottom
|
||||
val size = Math.min(innerWidth, innerHeight)
|
||||
left += (innerWidth - size) / 2
|
||||
top += (innerHeight - size) / 2
|
||||
restRect.set(left, top, left + size, top + size)
|
||||
}
|
||||
|
||||
private fun fitRect(rect: Rect) {
|
||||
val width = rect.width()
|
||||
val height = rect.height()
|
||||
if (width < minSize) {
|
||||
val pad = (minSize - width) / 2
|
||||
rect.left -= pad
|
||||
rect.right += pad
|
||||
}
|
||||
if (height < minSize) {
|
||||
val pad = (minSize - height) / 2
|
||||
rect.top -= pad
|
||||
rect.bottom += pad
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAnimationStart() {
|
||||
animationStart = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
private fun animateCurrentRect(now: Long) {
|
||||
currentRect.set(
|
||||
linear(now,
|
||||
startRect.left,
|
||||
endRect.left - startRect.left,
|
||||
animationDuration),
|
||||
linear(now,
|
||||
startRect.top,
|
||||
endRect.top - startRect.top,
|
||||
animationDuration),
|
||||
linear(now,
|
||||
startRect.right,
|
||||
endRect.right - startRect.right,
|
||||
animationDuration),
|
||||
linear(now,
|
||||
startRect.bottom,
|
||||
endRect.bottom - startRect.bottom,
|
||||
animationDuration))
|
||||
}
|
||||
|
||||
private fun linear(
|
||||
time: Long,
|
||||
begin: Int,
|
||||
change: Int,
|
||||
duration: Long): Int = Math.round(
|
||||
change.toFloat() * time / duration + begin)
|
||||
|
||||
private fun lockCanvasAndDraw() {
|
||||
val canvas = holder.lockCanvas()
|
||||
if (canvas != null) {
|
||||
drawMarkers(canvas, currentRect)
|
||||
holder.unlockCanvasAndPost(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
private fun untintMarkers() {
|
||||
markerPaint.colorFilter = null
|
||||
}
|
||||
|
||||
private fun tintMarkers() {
|
||||
markerPaint.colorFilter = PorterDuffColorFilter(
|
||||
ContextCompat.getColor(context, R.color.accent),
|
||||
PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
|
||||
private fun drawMarkers(canvas: Canvas, rect: Rect) {
|
||||
canvas.drawColor(0, PorterDuff.Mode.CLEAR)
|
||||
canvas.drawBitmap(markerTopLeft,
|
||||
rect.left.toFloat(),
|
||||
rect.top.toFloat(),
|
||||
markerPaint)
|
||||
canvas.drawBitmap(markerTopRight,
|
||||
(rect.right - markerSize).toFloat(),
|
||||
rect.top.toFloat(),
|
||||
markerPaint)
|
||||
canvas.drawBitmap(markerBottomLeft,
|
||||
rect.left.toFloat(),
|
||||
(rect.bottom - markerSize).toFloat(),
|
||||
markerPaint)
|
||||
canvas.drawBitmap(markerBottomRight,
|
||||
(rect.right - markerSize).toFloat(),
|
||||
(rect.bottom - markerSize).toFloat(),
|
||||
markerPaint)
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,10 @@ import com.google.zxing.DecodeHintType
|
||||
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.RGBLuminanceSource
|
||||
import com.google.zxing.common.HybridBinarizer
|
||||
|
||||
import android.graphics.Bitmap
|
||||
@@ -18,73 +19,101 @@ import java.util.EnumMap
|
||||
import java.util.EnumSet
|
||||
|
||||
class Zxing {
|
||||
companion object {
|
||||
private val black = 0xff000000.toInt()
|
||||
private val white = 0xffffffff.toInt()
|
||||
|
||||
fun encodeAsBitmap(
|
||||
text: String,
|
||||
format: BarcodeFormat,
|
||||
width: Int,
|
||||
height: Int): Bitmap? {
|
||||
val result = MultiFormatWriter().encode(text, format,
|
||||
width, height, null)
|
||||
val w = result.getWidth()
|
||||
val h = result.getHeight()
|
||||
val pixels = IntArray(w * h)
|
||||
var offset = 0
|
||||
for (y in 0..h - 1) {
|
||||
for (x in 0..w - 1) {
|
||||
pixels[offset + x] = if (result.get(x, y))
|
||||
black
|
||||
else
|
||||
white
|
||||
}
|
||||
offset += w
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||
bitmap.setPixels(pixels, 0, width, 0, 0, w, h)
|
||||
return bitmap
|
||||
}
|
||||
}
|
||||
|
||||
private val multiFormatReader: MultiFormatReader = MultiFormatReader()
|
||||
|
||||
init {
|
||||
val decodeFormats = EnumSet.noneOf<BarcodeFormat>(
|
||||
BarcodeFormat::class.java)
|
||||
decodeFormats.addAll(EnumSet.copyOf(Arrays.asList(
|
||||
BarcodeFormat.AZTEC,
|
||||
BarcodeFormat.CODABAR,
|
||||
BarcodeFormat.CODE_39,
|
||||
BarcodeFormat.CODE_93,
|
||||
BarcodeFormat.CODE_128,
|
||||
BarcodeFormat.DATA_MATRIX,
|
||||
BarcodeFormat.EAN_8,
|
||||
BarcodeFormat.EAN_13,
|
||||
BarcodeFormat.ITF,
|
||||
BarcodeFormat.MAXICODE,
|
||||
BarcodeFormat.PDF_417,
|
||||
BarcodeFormat.QR_CODE,
|
||||
BarcodeFormat.RSS_14,
|
||||
BarcodeFormat.RSS_EXPANDED,
|
||||
BarcodeFormat.UPC_A,
|
||||
BarcodeFormat.UPC_E,
|
||||
BarcodeFormat.UPC_EAN_EXTENSION
|
||||
)))
|
||||
BarcodeFormat::class.java
|
||||
)
|
||||
decodeFormats.addAll(
|
||||
EnumSet.copyOf(
|
||||
Arrays.asList(
|
||||
BarcodeFormat.AZTEC,
|
||||
BarcodeFormat.CODABAR,
|
||||
BarcodeFormat.CODE_39,
|
||||
BarcodeFormat.CODE_93,
|
||||
BarcodeFormat.CODE_128,
|
||||
BarcodeFormat.DATA_MATRIX,
|
||||
BarcodeFormat.EAN_8,
|
||||
BarcodeFormat.EAN_13,
|
||||
BarcodeFormat.ITF,
|
||||
BarcodeFormat.MAXICODE,
|
||||
BarcodeFormat.PDF_417,
|
||||
BarcodeFormat.QR_CODE,
|
||||
BarcodeFormat.RSS_14,
|
||||
BarcodeFormat.RSS_EXPANDED,
|
||||
BarcodeFormat.UPC_A,
|
||||
BarcodeFormat.UPC_E,
|
||||
BarcodeFormat.UPC_EAN_EXTENSION
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val hints = EnumMap<DecodeHintType, Any>(DecodeHintType::class.java)
|
||||
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats)
|
||||
hints[DecodeHintType.POSSIBLE_FORMATS] = decodeFormats
|
||||
|
||||
multiFormatReader.setHints(hints)
|
||||
}
|
||||
|
||||
fun decodeBitmap(bitmap: Bitmap): Result? {
|
||||
fun decode(
|
||||
yuvData: ByteArray,
|
||||
width: Int,
|
||||
height: Int,
|
||||
invert: Boolean = false
|
||||
): Result? {
|
||||
val source = PlanarYUVLuminanceSource(
|
||||
yuvData,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
false
|
||||
)
|
||||
return decodeLuminanceSource(source, invert)
|
||||
}
|
||||
|
||||
fun decodePositiveNegative(bitmap: Bitmap): Result? {
|
||||
val result = decode(bitmap, false)
|
||||
if (result != null) {
|
||||
return result
|
||||
}
|
||||
return decode(bitmap, true)
|
||||
}
|
||||
|
||||
fun decode(bitmap: Bitmap, invert: Boolean = false): Result? {
|
||||
val pixels = IntArray(bitmap.width * bitmap.height)
|
||||
return decode(pixels, bitmap, invert)
|
||||
}
|
||||
|
||||
fun decode(
|
||||
pixels: IntArray,
|
||||
bitmap: Bitmap,
|
||||
invert: Boolean = false
|
||||
): Result? {
|
||||
val width = bitmap.width
|
||||
val height = bitmap.height
|
||||
val data = IntArray(width * height)
|
||||
bitmap.getPixels(data, 0, width, 0, 0, width, height)
|
||||
return decodeLuminanceSource(RGBLuminanceSource(width, height, data))
|
||||
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)
|
||||
return decodeLuminanceSource(source, invert)
|
||||
}
|
||||
|
||||
private fun decodeLuminanceSource(
|
||||
source: LuminanceSource,
|
||||
invert: Boolean
|
||||
): Result? {
|
||||
return decodeLuminanceSource(
|
||||
if (invert) {
|
||||
source.invert()
|
||||
} else {
|
||||
source
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun decodeLuminanceSource(source: LuminanceSource): Result? {
|
||||
@@ -97,4 +126,41 @@ class Zxing {
|
||||
multiFormatReader.reset()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BLACK = 0xff000000.toInt()
|
||||
private const val WHITE = 0xffffffff.toInt()
|
||||
|
||||
fun encodeAsBitmap(
|
||||
text: String,
|
||||
format: BarcodeFormat,
|
||||
width: Int,
|
||||
height: Int
|
||||
): Bitmap? {
|
||||
val result = MultiFormatWriter().encode(
|
||||
text,
|
||||
format,
|
||||
width,
|
||||
height,
|
||||
null
|
||||
)
|
||||
val w = result.width
|
||||
val h = result.height
|
||||
val pixels = IntArray(w * h)
|
||||
var offset = 0
|
||||
for (y in 0 until h) {
|
||||
for (x in 0 until w) {
|
||||
pixels[offset + x] = if (result.get(x, y)) {
|
||||
BLACK
|
||||
} else {
|
||||
WHITE
|
||||
}
|
||||
}
|
||||
offset += w
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||
bitmap.setPixels(pixels, 0, width, 0, 0, w, h)
|
||||
return bitmap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 499 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 315 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 597 B |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 799 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 32 KiB |
@@ -3,6 +3,6 @@
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/ic_launcher"/>
|
||||
android:src="@drawable/logo"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/background_color"/>
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="top|right"
|
||||
android:src="@drawable/wallpaper"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -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="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
|
||||
</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>
|
||||
@@ -0,0 +1,3 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#b6d46f"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,70 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M32.002,-24.974h3.127v7.656h-3.127z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:pathData="m54.826,27.893c-6.851,0 -13.701,2.581 -18.924,7.745 -10.445,10.328 -10.413,27.062 0,37.423l36.218,36.036 37.879,0.902 -0.07,-38.363 -36.18,-35.998c-5.207,-5.18 -12.073,-7.745 -18.924,-7.745z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="8.60725021"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillType="nonZero"
|
||||
android:fillAlpha="0.1254902"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M28.001,54.118a26.525,26.227 0,1 0,53.049 0a26.525,26.227 0,1 0,-53.049 0z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M39.26,54.988a14.509,14.346 0,1 0,29.019 0a14.509,14.346 0,1 0,-29.019 0z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M54.034,44.513a9.535,9.428 0,1 0,19.07 0a9.535,9.428 0,1 0,-19.07 0z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="10"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,39 @@
|
||||
<merge 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">
|
||||
<de.markusfisch.android.cameraview.widget.CameraView
|
||||
android:id="@+id/camera_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
<RelativeLayout
|
||||
android:id="@+id/main_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:fitsSystemWindows="true">
|
||||
<include layout="@layout/toolbar"/>
|
||||
<SeekBar
|
||||
android:id="@+id/zoom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginRight="80dp"
|
||||
android:layout_marginEnd="80dp"
|
||||
android:padding="16dp"/>
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
tools:ignore="UnusedAttribute"
|
||||
android:id="@+id/flash"
|
||||
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:contentDescription="@string/toggle_flash"
|
||||
android:src="@drawable/ic_action_flash"
|
||||
app:elevation="4dp"/>
|
||||
</RelativeLayout>
|
||||
</merge>
|
||||
@@ -1,14 +1,4 @@
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<de.markusfisch.android.cameraview.widget.CameraView
|
||||
android:id="@+id/camera_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"/>
|
||||
<de.markusfisch.android.binaryeye.widget.LockOnView
|
||||
android:id="@+id/lock_on"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/main_layout"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/save_as_file_name"/>
|
||||
<EditText
|
||||
android:id="@+id/file_name"
|
||||
android:inputType="text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/file_name" />
|
||||
</LinearLayout>
|
||||
@@ -1,6 +1,24 @@
|
||||
<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:contentDescription="@string/share"
|
||||
android:src="@drawable/ic_action_share"
|
||||
app:elevation="4dp"/>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<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">
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
tools:ignore="UnusedAttribute"
|
||||
android:id="@+id/create"
|
||||
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_create"
|
||||
app:elevation="4dp"/>
|
||||
</RelativeLayout>
|
||||
@@ -4,15 +4,50 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<EditText
|
||||
android:id="@+id/content"
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:gravity="start|top"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:inputType="textMultiLine"
|
||||
android:typeface="monospace"/>
|
||||
android:layout_height="match_parent">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<EditText
|
||||
tools:targetApi="o"
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:gravity="start|top"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:inputType="textMultiLine"
|
||||
android:typeface="monospace"
|
||||
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"/>
|
||||
<TextView
|
||||
android:id="@+id/hex"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/format"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:typeface="monospace"
|
||||
android:textSize="12sp"/>
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
tools:ignore="UnusedAttribute"
|
||||
android:id="@+id/share"
|
||||
@@ -22,6 +57,7 @@
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_margin="20dp"
|
||||
android:contentDescription="@string/share"
|
||||
android:src="@drawable/ic_action_share"
|
||||
app:elevation="4dp"/>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -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,36 @@
|
||||
<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="@color/separator"
|
||||
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/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:contentDescription="@string/share"
|
||||
android:src="@drawable/ic_action_share"
|
||||
app:elevation="4dp"/>
|
||||
<include layout="@layout/progress_view"/>
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,16 @@
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/progress_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background_progress"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:visibility="gone">
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"/>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,30 @@
|
||||
<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_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>
|
||||
@@ -0,0 +1,24 @@
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:material="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
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>
|
||||
@@ -1,14 +0,0 @@
|
||||
<menu
|
||||
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"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/flash"
|
||||
android:title="@string/flash"
|
||||
android:icon="@drawable/ic_action_flash"
|
||||
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/clear"
|
||||
android:title="@string/clear_history"
|
||||
android:icon="@drawable/ic_action_clear"
|
||||
material:showAsAction="ifRoom"/>
|
||||
</menu>
|
||||
@@ -0,0 +1,4 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
@@ -0,0 +1,42 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Diese App kann ohne Kamerazugriff nicht funktionieren. Tschüss!</string>
|
||||
<string name="camera_error">Auf die Kamera kann nicht zugegriffen werden, bitte versuche es nochmal</string>
|
||||
<string name="scan_code">Code scannen</string>
|
||||
<string name="compose_barcode">Barcode erstellen</string>
|
||||
<string name="decode_barcode">Barcode dekodieren</string>
|
||||
<string name="content">Inhalt</string>
|
||||
<string name="binary_data">(Binäre Daten)</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="toggle_flash">Licht an/ausschalten</string>
|
||||
<string name="share">Teilen</string>
|
||||
<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="error_encoding_barcode">Barcode kann nicht generiert werden</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="really_remove_scan">Code wirklich entfernen?</string>
|
||||
<string name="really_remove_all_scans">Alle Codes entfernen?</string>
|
||||
<string name="clear_history">Gespeicherte Codes löschen</string>
|
||||
<string name="no_barcode_found">Kein Barcode gefunden</string>
|
||||
<string name="pick_list_separator">Wie sollen die Einträge getrennt werden?</string>
|
||||
<string name="separator_line_break">Zeilenumbruch</string>
|
||||
<string name="separator_ruler">Trennstrich</string>
|
||||
<string name="save_as_file_name">Speichern als?</string>
|
||||
<string name="file_name">Dateiname</string>
|
||||
<string name="error_saving_binary_data">Datei konnte nicht gespeichert werden</string>
|
||||
<string name="error_file_exists">Datei existiert bereits</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,42 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Cette application n\'a aucun sens sans accès possible à la caméra. Au revoir.</string>
|
||||
<string name="camera_error">Impossible d\'accéder à la caméra, veuillez réessayer</string>
|
||||
<string name="scan_code">Scanner le code</string>
|
||||
<string name="compose_barcode">Créer un code-barres</string>
|
||||
<string name="decode_barcode">Décoder le code-barres</string>
|
||||
<string name="content">Contenu</string>
|
||||
<string name="binary_data">(binary data)</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="toggle_flash">Toggle flash</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="copy_to_clipboard">Copier dans le presse-papier</string>
|
||||
<string name="put_into_clipboard">Mis dans le presse-papiers</string>
|
||||
<string name="open_url">Ouvrir l\'url</string>
|
||||
<string name="cannot_resolve_action">Aucune application ne peut ouvrir ça</string>
|
||||
<string name="format">Format</string>
|
||||
<string name="size">Taille en pixels</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Entrez votre contenu ici</string>
|
||||
<string name="encode">ENCODER</string>
|
||||
<string name="error_no_content">Contenu manquant</string>
|
||||
<string name="error_encoding_barcode">Barcode cannot be generated</string>
|
||||
<string name="view_barcode">Voir le code barre</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Changer de camera</string>
|
||||
<string name="history">Historique</string>
|
||||
<string name="use_history">Sauvegarder l\'histoire des scans</string>
|
||||
<string name="really_remove_scan">Supprimer le scan ?</string>
|
||||
<string name="really_remove_all_scans">Supprimer tous les scans ?</string>
|
||||
<string name="clear_history">Clear history</string>
|
||||
<string name="no_barcode_found">Aucun code-barres trouvé</string>
|
||||
<string name="pick_list_separator">How to separate list items?</string>
|
||||
<string name="separator_line_break">Line break</string>
|
||||
<string name="separator_ruler">Ruler</string>
|
||||
<string name="save_as_file_name">Save as file?</string>
|
||||
<string name="file_name">File name</string>
|
||||
<string name="error_saving_binary_data">Cannot save file</string>
|
||||
<string name="error_file_exists">File already exists</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,42 @@
|
||||
<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">Vonalkód visszafejtése</string>
|
||||
<string name="content">Tartalom</string>
|
||||
<string name="binary_data">(binary data)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d karakter</item>
|
||||
<item quantity="other">%1$s\n%2$d karakter</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">Vaku be- vagy kikapcsolása</string>
|
||||
<string name="share">Megosztás</string>
|
||||
<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="error_encoding_barcode">Barcode cannot be generated</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">Előzmények</string>
|
||||
<string name="use_history">Beolvasási előzmények mentése</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="clear_history">Előzmények törlése</string>
|
||||
<string name="no_barcode_found">Nem található vonalkód</string>
|
||||
<string name="pick_list_separator">How to separate list items?</string>
|
||||
<string name="separator_line_break">Line break</string>
|
||||
<string name="separator_ruler">Ruler</string>
|
||||
<string name="save_as_file_name">Save as file?</string>
|
||||
<string name="file_name">File name</string>
|
||||
<string name="error_saving_binary_data">Cannot save file</string>
|
||||
<string name="error_file_exists">File already exists</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,42 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Quest\'app non ha alcuna utilità senza accesso alla camera. Addio.</string>
|
||||
<string name="camera_error">Impossibile accedere alla camera. Riprovare</string>
|
||||
<string name="scan_code">Codice</string>
|
||||
<string name="compose_barcode">Crea codice a barre</string>
|
||||
<string name="decode_barcode">Decodifica codice a barre</string>
|
||||
<string name="content">Contenuto</string>
|
||||
<string name="binary_data">(binary data)</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="toggle_flash">Flash</string>
|
||||
<string name="share">Condividi</string>
|
||||
<string name="copy_to_clipboard">Copia negli appunti</string>
|
||||
<string name="put_into_clipboard">Metti negli appunti</string>
|
||||
<string name="open_url">Apri URL</string>
|
||||
<string name="cannot_resolve_action">Nessuna app può aprirlo</string>
|
||||
<string name="format">Formato</string>
|
||||
<string name="size">Dimensioni in pixel</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Contenuto input qui</string>
|
||||
<string name="encode">CODIFICA</string>
|
||||
<string name="error_no_content">Contenuto mancante</string>
|
||||
<string name="error_encoding_barcode">Barcode cannot be generated</string>
|
||||
<string name="view_barcode">Vedi codice a barre</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Cambia camera</string>
|
||||
<string name="history">Cronologia</string>
|
||||
<string name="use_history">Salva cronologia delle scansioni</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="clear_history">Pulisci cronologia</string>
|
||||
<string name="no_barcode_found">Nessun codice a barre trovato</string>
|
||||
<string name="pick_list_separator">Come separare elementi di una lista?</string>
|
||||
<string name="separator_line_break">Interruzione di linea</string>
|
||||
<string name="separator_ruler">Righello</string>
|
||||
<string name="save_as_file_name">Save as file?</string>
|
||||
<string name="file_name">File name</string>
|
||||
<string name="error_saving_binary_data">Cannot save file</string>
|
||||
<string name="error_file_exists">File already exists</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,42 @@
|
||||
<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>
|
||||
<string name="binary_data">(binary data)</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="toggle_flash">Toggle flash</string>
|
||||
<string name="share">Share</string>
|
||||
<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="error_encoding_barcode">Barcode cannot be generated</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="really_remove_scan">Really remove scan?</string>
|
||||
<string name="really_remove_all_scans">Really remove all scans?</string>
|
||||
<string name="clear_history">Clear history</string>
|
||||
<string name="no_barcode_found">No barcode found</string>
|
||||
<string name="pick_list_separator">How to separate list items?</string>
|
||||
<string name="separator_line_break">Line break</string>
|
||||
<string name="separator_ruler">Ruler</string>
|
||||
<string name="save_as_file_name">Save as file?</string>
|
||||
<string name="file_name">File name</string>
|
||||
<string name="error_saving_binary_data">Cannot save file</string>
|
||||
<string name="error_file_exists">File already exists</string>
|
||||
</resources>
|
||||
@@ -3,7 +3,10 @@
|
||||
<color name="primary_translucent">#8222</color>
|
||||
<color name="primary_dark">#111</color>
|
||||
<color name="primary_dark_translucent">#8111</color>
|
||||
<color name="accent">#26e1cf</color>
|
||||
<color name="accent_dark">#16d1bf</color>
|
||||
<color name="accent">#b6d46f</color>
|
||||
<color name="accent_dark">#a6c45f</color>
|
||||
<color name="background_color">#111</color>
|
||||
<color name="background_progress">#8000</color>
|
||||
<color name="button_text">#fff</color>
|
||||
<color name="separator">#333</color>
|
||||
</resources>
|
||||
|
||||