Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
b9a5687c5a | ||
|
|
cbc0ff435f | ||
|
|
3a650bc0cd | ||
|
|
cbcc9591ff | ||
|
|
7578d9b217 | ||
|
|
7789c21a96 | ||
|
|
366548dc2a | ||
|
|
2324c18807 | ||
|
|
e7e42694bc | ||
|
|
981fb2f0d8 | ||
|
|
90036e1ba2 | ||
|
|
f0916d55ba | ||
|
|
07177a0c70 | ||
|
|
478e8930a8 | ||
|
|
2d70b64dd1 | ||
|
|
25173bd7c7 | ||
|
|
9adb5174af | ||
|
|
5f0c00cd55 |
@@ -0,0 +1,94 @@
|
||||
# Change Log
|
||||
|
||||
## 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
|
||||
* Streamlined video pre-processing
|
||||
* Decoded content is editable now
|
||||
* Fixed position of lock marks on tablets
|
||||
* Open README from menu
|
||||
|
||||
## 1.0.1
|
||||
* Keep proguard from messing with renderscript
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Markus Fisch
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -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,7 +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.
|
||||
|
||||
And it's written in [Kotlin][kotlin] because I've wanted to explore that 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 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 1
|
||||
versionName '1.0.0'
|
||||
versionCode 24
|
||||
versionName '1.7.2'
|
||||
|
||||
// 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,8 +41,10 @@ android {
|
||||
}
|
||||
|
||||
release {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
signingConfig signingConfigs.config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,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,4 @@
|
||||
-dontwarn com.google.**
|
||||
-dontwarn android.support.v8.**
|
||||
|
||||
-keep class android.support.v8.renderscript.** { *; }
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 8.5 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,8 +29,22 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:name=".activity.CameraActivity"
|
||||
android:label="@string/scan_code">
|
||||
<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"/>
|
||||
@@ -42,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,493 @@
|
||||
package de.markusfisch.android.binaryeye.activity
|
||||
|
||||
import com.google.zxing.Result
|
||||
|
||||
import de.markusfisch.android.cameraview.widget.CameraView
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.db
|
||||
import de.markusfisch.android.binaryeye.app.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.graphics.lumaToBitmap
|
||||
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.v4.app.ActivityCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.support.v8.renderscript.RSRuntimeException
|
||||
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 useRenderScript = true
|
||||
private var fallbackBuffer: IntArray? = null
|
||||
|
||||
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 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()
|
||||
saveZoom()
|
||||
fallbackBuffer = null
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
System.gc()
|
||||
returnResult = "com.google.zxing.client.android.SCAN".equals(
|
||||
intent.action
|
||||
)
|
||||
if (hasCameraPermission()) {
|
||||
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 hasCameraPermission(): Boolean {
|
||||
val permission = android.Manifest.permission.CAMERA
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this, permission) !=
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this, arrayOf(permission),
|
||||
REQUEST_CAMERA
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
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
|
||||
if (preprocessor == null) {
|
||||
preprocessor = Preprocessor(
|
||||
this,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation
|
||||
)
|
||||
}
|
||||
val pp = preprocessor
|
||||
pp ?: return null
|
||||
try {
|
||||
if (useRenderScript) {
|
||||
pp.process(frameData)
|
||||
}
|
||||
} catch (e: RSRuntimeException) {
|
||||
// because RenderScript fails on some devices/ROMS for
|
||||
// unknown reasons (e.g. Lineage)
|
||||
useRenderScript = false
|
||||
}
|
||||
invert = invert xor true
|
||||
if (useRenderScript) {
|
||||
return zxing.decode(frameData, pp.outWidth, pp.outHeight, invert)
|
||||
} else {
|
||||
val size = pp.outWidth * pp.outHeight
|
||||
val fb = fallbackBuffer ?: IntArray(size)
|
||||
fallbackBuffer = fb
|
||||
val bmp = lumaToBitmap(
|
||||
frameData,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation,
|
||||
pp.outWidth,
|
||||
pp.outHeight
|
||||
)
|
||||
return zxing.decode(fb, bmp, invert)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showResult(result: Result) {
|
||||
if (prefs.useHistory) {
|
||||
GlobalScope.launch {
|
||||
db.insertScan(
|
||||
System.currentTimeMillis(),
|
||||
result.text,
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,44 +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.fragment.CameraFragment
|
||||
import de.markusfisch.android.binaryeye.view.SystemBarMetrics
|
||||
import de.markusfisch.android.binaryeye.widget.LockOnView
|
||||
import de.markusfisch.android.binaryeye.app.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.app.setFragment
|
||||
import de.markusfisch.android.binaryeye.fragment.DecodeFragment
|
||||
import de.markusfisch.android.binaryeye.fragment.EncodeFragment
|
||||
import de.markusfisch.android.binaryeye.fragment.HistoryFragment
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.ActivityCompat
|
||||
import android.support.v4.app.FragmentManager
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.app.ActionBar
|
||||
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) {
|
||||
@@ -54,71 +31,64 @@ 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) {
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.content_frame, CameraFragment())
|
||||
.commit()
|
||||
setFragment(supportFragmentManager, when {
|
||||
intent?.hasExtra(HISTORY) == true -> HistoryFragment()
|
||||
intent?.hasExtra(ENCODE) == true -> EncodeFragment.newInstance(
|
||||
intent.getStringExtra(ENCODE)
|
||||
)
|
||||
intent?.hasExtra(DECODE) == true -> DecodeFragment.newInstance(
|
||||
intent.getStringExtra(DECODE),
|
||||
intent.getSerializableExtra(
|
||||
DECODE_FORMAT
|
||||
) as BarcodeFormat
|
||||
)
|
||||
else -> DecodeFragment()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val REQUEST_CAMERA = 1
|
||||
private const val HISTORY = "history"
|
||||
private const val ENCODE = "encode"
|
||||
private const val DECODE = "decode"
|
||||
private const val DECODE_FORMAT = "decode_format"
|
||||
|
||||
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(
|
||||
android.content.Intent.FLAG_ACTIVITY_NO_HISTORY or
|
||||
android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK or
|
||||
android.content.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
|
||||
): Intent {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.putExtra(DECODE, text)
|
||||
intent.putExtra(DECODE_FORMAT, format)
|
||||
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,59 @@
|
||||
package de.markusfisch.android.binaryeye.adapter
|
||||
|
||||
import de.markusfisch.android.binaryeye.data.Database
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CursorAdapter
|
||||
import android.widget.TextView
|
||||
|
||||
class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
CursorAdapter(context, cursor, false) {
|
||||
private val timeIndex = cursor.getColumnIndex(Database.SCANS_DATETIME)
|
||||
private val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
|
||||
private val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
|
||||
|
||||
override fun newView(
|
||||
context: Context,
|
||||
cursor: Cursor,
|
||||
parent: ViewGroup
|
||||
): View {
|
||||
return LayoutInflater.from(parent.context).inflate(
|
||||
R.layout.row_scan, parent, false
|
||||
)
|
||||
}
|
||||
|
||||
override fun bindView(
|
||||
view: View,
|
||||
context: Context,
|
||||
cursor: Cursor
|
||||
) {
|
||||
val holder = getViewHolder(view)
|
||||
holder.timeView.text = cursor.getString(timeIndex)
|
||||
holder.contentView.text = cursor.getString(contentIndex)
|
||||
holder.formatView.text = cursor.getString(formatIndex)
|
||||
}
|
||||
|
||||
private fun getViewHolder(view: View): ViewHolder {
|
||||
var holder = view.tag as ViewHolder?
|
||||
if (holder == null) {
|
||||
holder = ViewHolder(
|
||||
view.findViewById(R.id.time),
|
||||
view.findViewById(R.id.content),
|
||||
view.findViewById(R.id.format)
|
||||
)
|
||||
view.tag = holder
|
||||
}
|
||||
return holder
|
||||
}
|
||||
|
||||
private data class ViewHolder(
|
||||
val timeView: TextView,
|
||||
val contentView: TextView,
|
||||
val formatView: TextView
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import de.markusfisch.android.binaryeye.data.Database
|
||||
import de.markusfisch.android.binaryeye.preference.Preferences
|
||||
|
||||
import android.app.Application
|
||||
|
||||
val db = Database()
|
||||
val prefs = Preferences()
|
||||
|
||||
class BinaryEyeApp : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
db.open(this)
|
||||
prefs.init(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
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
|
||||
|
||||
fun setFragment(fm: FragmentManager?, fragment: Fragment) {
|
||||
fm?.let { getTransaction(fm, fragment).commit() }
|
||||
}
|
||||
|
||||
fun addFragment(fm: FragmentManager?, fragment: Fragment) {
|
||||
fm?.let { getTransaction(fm, fragment).addToBackStack(null).commit() }
|
||||
}
|
||||
|
||||
@SuppressLint("CommitTransaction")
|
||||
private fun getTransaction(
|
||||
fm: FragmentManager,
|
||||
fragment: Fragment
|
||||
): FragmentTransaction {
|
||||
return fm.beginTransaction().replace(R.id.content_frame, fragment)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -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,92 @@
|
||||
package de.markusfisch.android.binaryeye.data
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.text.format.DateFormat
|
||||
|
||||
class Database {
|
||||
private lateinit var db: SQLiteDatabase
|
||||
|
||||
fun open(context: Context) {
|
||||
db = OpenHelper(context).writableDatabase
|
||||
}
|
||||
|
||||
fun getScans(): Cursor? = db.rawQuery(
|
||||
"""SELECT
|
||||
$SCANS_ID,
|
||||
$SCANS_DATETIME,
|
||||
$SCANS_CONTENT,
|
||||
$SCANS_FORMAT
|
||||
FROM $SCANS
|
||||
ORDER BY $SCANS_DATETIME DESC
|
||||
""", null
|
||||
)
|
||||
|
||||
fun getScan(id: Long): Cursor? = db.rawQuery(
|
||||
"""SELECT
|
||||
$SCANS_DATETIME,
|
||||
$SCANS_CONTENT,
|
||||
$SCANS_FORMAT
|
||||
FROM $SCANS
|
||||
WHERE $SCANS_ID = ?
|
||||
""", arrayOf("$id")
|
||||
)
|
||||
|
||||
fun insertScan(timestamp: Long, name: String, code: String): Long {
|
||||
val cv = ContentValues()
|
||||
cv.put(
|
||||
SCANS_DATETIME, DateFormat.format(
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
timestamp
|
||||
).toString()
|
||||
)
|
||||
cv.put(SCANS_CONTENT, name)
|
||||
cv.put(SCANS_FORMAT, code)
|
||||
return db.insert(SCANS, null, cv)
|
||||
}
|
||||
|
||||
fun removeScan(id: Long) {
|
||||
db.delete(SCANS, "$SCANS_ID = ?", arrayOf("$id"))
|
||||
}
|
||||
|
||||
fun removeScans() {
|
||||
db.delete(SCANS, null, null)
|
||||
}
|
||||
|
||||
private class OpenHelper(context: Context) :
|
||||
SQLiteOpenHelper(context, "history.db", null, 1) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
createScans(db)
|
||||
}
|
||||
|
||||
override fun onUpgrade(
|
||||
db: SQLiteDatabase,
|
||||
oldVersion: Int,
|
||||
newVersion: Int
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SCANS = "scans"
|
||||
const val SCANS_ID = "_id"
|
||||
const val SCANS_DATETIME = "_datetime"
|
||||
const val SCANS_CONTENT = "content"
|
||||
const val SCANS_FORMAT = "format"
|
||||
|
||||
private fun createScans(db: SQLiteDatabase) {
|
||||
db.execSQL("DROP TABLE IF EXISTS $SCANS")
|
||||
db.execSQL(
|
||||
"""CREATE TABLE $SCANS (
|
||||
$SCANS_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
$SCANS_DATETIME DATETIME NOT NULL,
|
||||
$SCANS_CONTENT TEXT NOT NULL,
|
||||
$SCANS_FORMAT TEXT NOT NULL
|
||||
)"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import com.google.zxing.BarcodeFormat
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.shareUri
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
import de.markusfisch.android.binaryeye.BuildConfig
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.graphics.Bitmap
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.content.FileProvider
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class BarcodeFragment : Fragment() {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
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) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
e.message,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
fragmentManager.popBackStack()
|
||||
return null
|
||||
}
|
||||
view.findViewById<ImageView>(R.id.barcode).setImageBitmap(bitmap)
|
||||
view.findViewById<View>(R.id.share).setOnClickListener {
|
||||
bitmap?.let {
|
||||
share(bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun share(bitmap: Bitmap) {
|
||||
GlobalScope.launch {
|
||||
val file = saveBitmap(bitmap)
|
||||
GlobalScope.launch(Main) {
|
||||
file?.let {
|
||||
shareUri(
|
||||
context,
|
||||
getUriForFile(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
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUriForFile(file: File): Uri {
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Uri.fromFile(file)
|
||||
} else {
|
||||
FileProvider.getUriForFile(
|
||||
context,
|
||||
BuildConfig.APPLICATION_ID + ".provider",
|
||||
file
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONTENT = "content"
|
||||
private const val FORMAT = "format"
|
||||
private const val SIZE = "size"
|
||||
|
||||
fun newInstance(
|
||||
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.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,224 +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.rs.Inverter
|
||||
import de.markusfisch.android.binaryeye.rs.Rotator
|
||||
import de.markusfisch.android.binaryeye.rs.YuvToBitmap
|
||||
import de.markusfisch.android.binaryeye.widget.LockOnView
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Rect
|
||||
import android.hardware.Camera
|
||||
import android.os.Bundle
|
||||
import android.os.Vibrator
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentManager
|
||||
import android.view.LayoutInflater
|
||||
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 yuvToBitmap: YuvToBitmap
|
||||
private lateinit var rotator: Rotator
|
||||
private lateinit var inverter: Inverter
|
||||
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 onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?): View? {
|
||||
activity.setTitle(R.string.scan_code)
|
||||
vibrator = activity.getSystemService(
|
||||
Context.VIBRATOR_SERVICE) as Vibrator
|
||||
|
||||
yuvToBitmap = YuvToBitmap(context)
|
||||
rotator = Rotator(context)
|
||||
inverter = Inverter(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)
|
||||
|
||||
val fab = view.findViewById<View>(R.id.flash)
|
||||
if (context.packageManager.hasSystemFeature(
|
||||
PackageManager.FEATURE_CAMERA_FLASH)) {
|
||||
fab.setOnClickListener { _ -> toggleTorchMode() }
|
||||
} else {
|
||||
fab.visibility = View.GONE
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
yuvToBitmap.destroy()
|
||||
rotator.destroy()
|
||||
inverter.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
|
||||
}
|
||||
|
||||
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 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
|
||||
var bitmap = yuvToBitmap.convert(frameData!!, frameWidth, frameHeight)
|
||||
bitmap = rotator.convert(bitmap, frameOrientation)
|
||||
odd = odd xor true
|
||||
if (odd) {
|
||||
bitmap = inverter.convert(bitmap)
|
||||
}
|
||||
return zxing.decodeBitmap(bitmap)
|
||||
}
|
||||
|
||||
private fun found(result: Result) {
|
||||
cancelDecoding()
|
||||
vibrator.vibrate(100)
|
||||
lockOnView.lock(getRelativeBounds(result.resultPoints), 250)
|
||||
lockOnView.postDelayed({
|
||||
fragmentManager?.beginTransaction()
|
||||
?.replace(R.id.content_frame,
|
||||
ResultFragment.newInstance(result.text))
|
||||
?.addToBackStack(null)
|
||||
?.commit()
|
||||
}, 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import com.google.zxing.BarcodeFormat
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.addFragment
|
||||
import de.markusfisch.android.binaryeye.app.shareText
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.text.ClipboardManager
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
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.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
|
||||
class DecodeFragment : Fragment() {
|
||||
private lateinit var contentView: EditText
|
||||
private lateinit var formatView: TextView
|
||||
private lateinit var hexView: TextView
|
||||
private lateinit var format: BarcodeFormat
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View {
|
||||
activity.setTitle(R.string.content)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_decode,
|
||||
container,
|
||||
false
|
||||
)
|
||||
|
||||
val content = arguments?.getString(CONTENT) ?: ""
|
||||
format = arguments?.getSerializable(FORMAT) as BarcodeFormat? ?: BarcodeFormat.QR_CODE
|
||||
|
||||
contentView = view.findViewById(R.id.content)
|
||||
contentView.setText(content)
|
||||
contentView.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
updateFormatAndHex(contentView.text.toString())
|
||||
}
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int
|
||||
) {}
|
||||
override fun onTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
before: Int,
|
||||
count: Int
|
||||
) {}
|
||||
})
|
||||
|
||||
formatView = view.findViewById(R.id.format)
|
||||
hexView = view.findViewById(R.id.hex)
|
||||
|
||||
view.findViewById<View>(R.id.share).setOnClickListener { v ->
|
||||
shareText(v.context, getContent())
|
||||
}
|
||||
|
||||
updateFormatAndHex(content)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun updateFormatAndHex(content: String) {
|
||||
formatView.text = resources.getQuantityString(
|
||||
R.plurals.barcode_info,
|
||||
content.length,
|
||||
format.toString(),
|
||||
content.length
|
||||
)
|
||||
hexView.text = hexDump(content, 33)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.fragment_decode, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.copy_to_clipboard -> {
|
||||
copyToClipboard(getContent())
|
||||
true
|
||||
}
|
||||
R.id.open_url -> {
|
||||
openUrl(getContent())
|
||||
true
|
||||
}
|
||||
R.id.create -> {
|
||||
addFragment(
|
||||
fragmentManager,
|
||||
EncodeFragment.newInstance(getContent(), format)
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getContent(): String {
|
||||
return contentView.text.toString()
|
||||
}
|
||||
|
||||
private fun copyToClipboard(text: String) {
|
||||
activity ?: return
|
||||
|
||||
val cm = activity.getSystemService(
|
||||
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) {
|
||||
if (activity == null || url.isEmpty()) {
|
||||
return
|
||||
}
|
||||
var uri = Uri.parse(url)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
uri = uri.normalizeScheme()
|
||||
}
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
if (intent.resolveActivity(activity.packageManager) != null) {
|
||||
startActivity(intent)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
R.string.cannot_resolve_action,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hexDump(content: String, charsPerLine: Int): String {
|
||||
if (charsPerLine < 4 || content.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
val dump = StringBuilder()
|
||||
val hex = StringBuilder()
|
||||
val ascii = StringBuilder()
|
||||
val itemsPerLine = (charsPerLine - 1) / 4
|
||||
val bytes = content.toByteArray()
|
||||
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()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONTENT = "content"
|
||||
private const 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import com.google.zxing.BarcodeFormat
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.addFragment
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.SeekBar
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
|
||||
class EncodeFragment : 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
|
||||
)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View {
|
||||
activity.setTitle(R.string.compose_barcode)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_encode,
|
||||
container,
|
||||
false
|
||||
)
|
||||
|
||||
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(R.id.size_display)
|
||||
sizeBarView = view.findViewById(R.id.size_bar)
|
||||
initSizeBar()
|
||||
|
||||
val contentView = view.findViewById<EditText>(R.id.content)
|
||||
|
||||
val args = arguments
|
||||
args?.let {
|
||||
contentView.setText(args.getString(CONTENT))
|
||||
formatView.setSelection(
|
||||
writers.indexOf(
|
||||
args.getSerializable(FORMAT) as BarcodeFormat?
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.encode).setOnClickListener { v ->
|
||||
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()
|
||||
} else {
|
||||
hideSoftKeyboard(contentView)
|
||||
addFragment(
|
||||
fragmentManager, BarcodeFragment.newInstance(
|
||||
content,
|
||||
format,
|
||||
size
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun initSizeBar() {
|
||||
updateSize(sizeBarView.progress)
|
||||
sizeBarView.setOnSeekBarChangeListener(
|
||||
object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(
|
||||
seekBar: SeekBar,
|
||||
progressValue: Int,
|
||||
fromUser: Boolean
|
||||
) {
|
||||
updateSize(progressValue)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateSize(power: Int) {
|
||||
val size = getSize(power)
|
||||
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?
|
||||
im?.let {
|
||||
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,218 @@
|
||||
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 formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
|
||||
try {
|
||||
addFragment(
|
||||
fragmentManager,
|
||||
DecodeFragment.newInstance(
|
||||
cursor.getString(contentIndex),
|
||||
BarcodeFormat.valueOf(cursor.getString(formatIndex))
|
||||
)
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// shouldn't ever happen
|
||||
}
|
||||
}
|
||||
cursor?.close()
|
||||
}
|
||||
|
||||
private fun askToRemoveScan(context: Context, id: Long) {
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(R.string.really_remove_scan)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
db.removeScan(id)
|
||||
update(context)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun askToRemoveAllScans(context: Context) {
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(R.string.really_remove_all_scans)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
db.removeScans()
|
||||
update(context)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.text.ClipboardManager
|
||||
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.TextView
|
||||
import android.widget.Toast
|
||||
|
||||
class ResultFragment : Fragment() {
|
||||
companion object {
|
||||
private val RESULT = "result"
|
||||
|
||||
fun newInstance(content: String): Fragment {
|
||||
val args = Bundle()
|
||||
args.putString(RESULT, content)
|
||||
val fragment = ResultFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var content: String
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?): View {
|
||||
activity.setTitle(R.string.result)
|
||||
|
||||
content = arguments?.getString(RESULT) ?: ""
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_result,
|
||||
container,
|
||||
false)
|
||||
|
||||
val textView = view.findViewById<TextView>(R.id.text)
|
||||
textView.text = content
|
||||
|
||||
view.findViewById<View>(R.id.share).setOnClickListener { _ ->
|
||||
share(content)
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.fragment_result, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.copy_to_clipboard -> {
|
||||
copyToClipboard(content)
|
||||
true
|
||||
}
|
||||
R.id.open_url -> {
|
||||
openUrl(content)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
private fun openUrl(url: String) {
|
||||
if (activity == null || url.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
if (intent.resolveActivity(activity.getPackageManager()) != null) {
|
||||
startActivity(intent)
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package de.markusfisch.android.binaryeye.graphics
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Matrix
|
||||
|
||||
val matrix = Matrix()
|
||||
var buf: IntArray? = null
|
||||
|
||||
fun lumaToBitmap(
|
||||
yuv: ByteArray,
|
||||
width: Int,
|
||||
height: Int,
|
||||
orientation: Int,
|
||||
destWidth: Int,
|
||||
destHeight: Int
|
||||
): Bitmap {
|
||||
// convert luma component only; ZXing will binarize the input image
|
||||
// anyway so there's no need to preserve color information
|
||||
val bitmap = lumaToBitmap(yuv, width, height)
|
||||
var dw = destWidth
|
||||
var dh = destHeight
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
dw = destHeight
|
||||
dh = destWidth
|
||||
}
|
||||
matrix.setScale(
|
||||
dw.toFloat() / width.toFloat(),
|
||||
dh.toFloat() / height.toFloat()
|
||||
)
|
||||
matrix.postRotate(orientation.toFloat())
|
||||
return Bitmap.createBitmap(
|
||||
bitmap,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
matrix,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
fun lumaToBitmap(yuv: ByteArray, width: Int, height: Int): Bitmap {
|
||||
val size = width * height
|
||||
// reuse buf to avoid allocation in hot path
|
||||
val b = buf ?: IntArray(size)
|
||||
buf = b
|
||||
for (i in 0 until size) {
|
||||
val l = yuv[i].toInt() and 255
|
||||
b[i] = 0xff000000.toInt() or (l shl 16) or (l shl 8) or l
|
||||
}
|
||||
return Bitmap.createBitmap(b, width, height, Bitmap.Config.ARGB_8888)
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.rs
|
||||
|
||||
import de.markusfisch.android.binaryeye.renderscript.ScriptC_invert
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.support.v8.renderscript.Allocation
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
|
||||
class Inverter(context: Context) {
|
||||
private val rs: RenderScript = RenderScript.create(context)
|
||||
private val inverterScript: ScriptC_invert = ScriptC_invert(rs)
|
||||
|
||||
fun destroy() {
|
||||
inverterScript.destroy()
|
||||
rs.destroy()
|
||||
}
|
||||
|
||||
fun convert(bitmap: Bitmap): Bitmap {
|
||||
val allocation = Allocation.createFromBitmap(
|
||||
rs,
|
||||
bitmap,
|
||||
Allocation.MipmapControl.MIPMAP_NONE,
|
||||
Allocation.USAGE_SCRIPT)
|
||||
|
||||
inverterScript.forEach_invert(allocation, allocation)
|
||||
allocation.copyTo(bitmap)
|
||||
|
||||
return bitmap
|
||||
}
|
||||
}
|
||||
@@ -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,71 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.rs
|
||||
|
||||
import de.markusfisch.android.binaryeye.renderscript.ScriptC_rotator
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.support.v8.renderscript.Allocation
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
|
||||
class Rotator(context: Context) {
|
||||
private val rs: RenderScript = RenderScript.create(context)
|
||||
private var rotatorScript: ScriptC_rotator = ScriptC_rotator(rs)
|
||||
|
||||
fun destroy() {
|
||||
rotatorScript.destroy()
|
||||
rs.destroy()
|
||||
}
|
||||
|
||||
fun convert(bitmap: Bitmap, frameOrientation: Int): Bitmap {
|
||||
var orientation = frameOrientation
|
||||
var width = bitmap.width
|
||||
var height = bitmap.height
|
||||
|
||||
orientation = orientation % 360 / 90
|
||||
if (orientation == 0) {
|
||||
return bitmap
|
||||
}
|
||||
|
||||
rotatorScript._inWidth = width
|
||||
rotatorScript._inHeight = height
|
||||
val sourceAllocation = Allocation.createFromBitmap(
|
||||
rs,
|
||||
bitmap,
|
||||
Allocation.MipmapControl.MIPMAP_NONE,
|
||||
Allocation.USAGE_SCRIPT)
|
||||
rotatorScript._inImage = sourceAllocation
|
||||
|
||||
when (orientation) {
|
||||
1, 3 -> {
|
||||
val tmp = width
|
||||
width = height
|
||||
height = tmp
|
||||
}
|
||||
}
|
||||
|
||||
val target = Bitmap.createBitmap(
|
||||
width,
|
||||
height,
|
||||
Bitmap.Config.ARGB_8888)
|
||||
val targetAllocation = Allocation.createFromBitmap(
|
||||
rs,
|
||||
target,
|
||||
Allocation.MipmapControl.MIPMAP_NONE,
|
||||
Allocation.USAGE_SCRIPT)
|
||||
|
||||
when (orientation) {
|
||||
1 -> rotatorScript.forEach_rotate90(
|
||||
targetAllocation,
|
||||
targetAllocation)
|
||||
2 -> rotatorScript.forEach_rotate180(
|
||||
targetAllocation,
|
||||
targetAllocation)
|
||||
3 -> rotatorScript.forEach_rotate270(
|
||||
targetAllocation,
|
||||
targetAllocation)
|
||||
}
|
||||
|
||||
targetAllocation.copyTo(target)
|
||||
return target
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.rs
|
||||
|
||||
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.ScriptIntrinsicYuvToRGB
|
||||
import android.support.v8.renderscript.Type
|
||||
|
||||
class YuvToBitmap(context: Context) {
|
||||
private val rs: RenderScript = RenderScript.create(context)
|
||||
|
||||
private var yuvType: Type? = null
|
||||
private var rgbaType: Type? = null
|
||||
private var yuvAllocation: Allocation? = null
|
||||
private var rgbaAllocation: Allocation? = null
|
||||
private var yuvToRgbaScript: ScriptIntrinsicYuvToRGB? = null
|
||||
|
||||
fun destroy() {
|
||||
yuvToRgbaScript?.destroy()
|
||||
yuvToRgbaScript = null
|
||||
yuvType?.destroy()
|
||||
yuvType = null
|
||||
rgbaType?.destroy()
|
||||
rgbaType = null
|
||||
yuvAllocation?.destroy()
|
||||
yuvAllocation = null
|
||||
rgbaAllocation?.destroy()
|
||||
rgbaAllocation = null
|
||||
rs.destroy()
|
||||
}
|
||||
|
||||
fun convert(data: ByteArray, width: Int, height: Int): Bitmap {
|
||||
if (yuvAllocation == null) {
|
||||
init(data, width, height)
|
||||
}
|
||||
|
||||
yuvAllocation?.copyFrom(data)
|
||||
yuvToRgbaScript?.forEach(rgbaAllocation)
|
||||
|
||||
val bitmap = Bitmap.createBitmap(width, height,
|
||||
Bitmap.Config.ARGB_8888)
|
||||
rgbaAllocation?.copyTo(bitmap)
|
||||
|
||||
return bitmap
|
||||
}
|
||||
|
||||
private fun init(data: ByteArray, width: Int, height: Int) {
|
||||
yuvType = Type.createX(rs, Element.U8(rs), data.size)
|
||||
yuvType?.let {
|
||||
yuvAllocation = Allocation.createTyped(rs, yuvType,
|
||||
Allocation.USAGE_SCRIPT)
|
||||
}
|
||||
|
||||
rgbaType = Type.createXY(rs, Element.RGBA_8888(rs), width, height)
|
||||
rgbaType?.let {
|
||||
rgbaAllocation = Allocation.createTyped(rs, rgbaType)
|
||||
}
|
||||
|
||||
yuvToRgbaScript = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
|
||||
yuvToRgbaScript?.setInput(yuvAllocation)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.view
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
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
|
||||
}
|
||||
|
||||
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,260 +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 paddingLeftRight = padding * 2
|
||||
val paddingTopBottom = top + padding
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,11 @@ import com.google.zxing.BinaryBitmap
|
||||
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
|
||||
@@ -21,39 +23,97 @@ class Zxing {
|
||||
|
||||
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? {
|
||||
@@ -66,4 +126,37 @@ 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 |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 315 B |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 597 B |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 799 B |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,7 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="line"
|
||||
android:gravity="center">
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/accent"/>
|
||||
</shape>
|
||||
@@ -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,16 @@
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:constantSize="true">
|
||||
<item android:state_pressed="false">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="2dp"/>
|
||||
<solid android:color="@color/accent"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="2dp"/>
|
||||
<solid android:color="@color/accent_dark"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -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,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</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,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13L7,13v-2h10v2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M20,4h-3.17L15,2L9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM15,15.5L15,13L9,13v2.5L5.5,12 9,8.5L9,11h6L15,8.5l3.5,3.5 -3.5,3.5z"/>
|
||||
</vector>
|
||||
@@ -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,40 @@
|
||||
<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"
|
||||
android:background="@drawable/background_scanner">
|
||||
<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"
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
<ImageView
|
||||
android:id="@+id/barcode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"/>
|
||||
android:contentDescription="@string/view_barcode"/>
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
tools:ignore="UnusedAttribute"
|
||||
android:id="@+id/share"
|
||||
@@ -18,6 +18,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>
|
||||
@@ -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/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:src="@drawable/ic_action_flash"
|
||||
app:elevation="4dp"/>
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,63 @@
|
||||
<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">
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
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"
|
||||
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>
|
||||
@@ -0,0 +1,63 @@
|
||||
<ScrollView
|
||||
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">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
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/format"/>
|
||||
<Spinner
|
||||
android:id="@+id/format"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/size"/>
|
||||
<TextView
|
||||
android:id="@+id/size_display"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:gravity="center"/>
|
||||
<SeekBar
|
||||
android:id="@+id/size_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:progress="4"
|
||||
android:max="7"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="start|top"
|
||||
android:typeface="monospace"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:inputType="textMultiLine"
|
||||
android:hint="@string/input_content_here"
|
||||
android:importantForAutofill="no"/>
|
||||
<Button
|
||||
style="@style/Button"
|
||||
android:id="@+id/encode"
|
||||
android:layout_width="match_parent"
|
||||
android:text="@string/encode"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -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>
|
||||
@@ -11,4 +11,9 @@
|
||||
android:title="@string/open_url"
|
||||
android:icon="@drawable/ic_action_open"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/create"
|
||||
android:title="@string/compose_barcode"
|
||||
android:icon="@drawable/ic_action_create"
|
||||
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,36 @@
|
||||
<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>
|
||||
<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="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>
|
||||
</resources>
|
||||