Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9898a14dd3 | ||
|
|
85fc475118 | ||
|
|
987e8531e6 | ||
|
|
4d9a537bef | ||
|
|
05de15488e | ||
|
|
721a3ee801 | ||
|
|
bca000c21b | ||
|
|
80afd0c5b7 | ||
|
|
7416037762 | ||
|
|
325aa00bf8 | ||
|
|
40c2cc9b60 | ||
|
|
1bf16a2fb7 | ||
|
|
2687ce031a | ||
|
|
3213169ea1 | ||
|
|
5c3d6c4b31 | ||
|
|
336f596d08 | ||
|
|
930656fbe5 | ||
|
|
3b45061974 | ||
|
|
983c3e3c38 | ||
|
|
691fe05322 | ||
|
|
569389bb12 | ||
|
|
da886291cf | ||
|
|
05b219b3ea | ||
|
|
7cfefe7791 | ||
|
|
a2fe94285d | ||
|
|
6429a1ba52 | ||
|
|
477ae25fc7 | ||
|
|
a2646ffdb3 | ||
|
|
7a93172876 | ||
|
|
327cbe55d7 | ||
|
|
35ec26868c | ||
|
|
0c7317f09e | ||
|
|
a049db588b | ||
|
|
840691451a | ||
|
|
b50c956a21 | ||
|
|
3820d647e0 | ||
|
|
ce234381c8 | ||
|
|
2abeccdd1b | ||
|
|
ae4d6f8e2a | ||
|
|
78c3253979 | ||
|
|
8026478052 | ||
|
|
0d2ac7751e | ||
|
|
47cb298317 | ||
|
|
c1d0b98990 | ||
|
|
798c10d420 | ||
|
|
0d65f41bb5 | ||
|
|
eb32af9cda | ||
|
|
cb82374712 | ||
|
|
c4f7189435 | ||
|
|
2c037a6322 | ||
|
|
6b9e6ee5b4 | ||
|
|
adecdf7e27 | ||
|
|
d925e136ef | ||
|
|
3ae7e561c4 | ||
|
|
ca619efa14 | ||
|
|
47d5923a04 | ||
|
|
8c22a8ab00 | ||
|
|
8152cb6a24 | ||
|
|
ffed4fc5cf | ||
|
|
d8bd3c1176 | ||
|
|
3b12aa4b7b | ||
|
|
099b522d35 | ||
|
|
166d181469 | ||
|
|
a4a2a67177 | ||
|
|
7fe3de78af | ||
|
|
e75d658092 | ||
|
|
c840462fa2 | ||
|
|
c3475a37d2 | ||
|
|
183fe2c6f2 | ||
|
|
056f5e263e | ||
|
|
386c7a6056 | ||
|
|
80c876f35d | ||
|
|
30ae51d945 | ||
|
|
95b54ed4ab | ||
|
|
3650bda107 | ||
|
|
2bdf13761e | ||
|
|
ee4ee1f778 | ||
|
|
dbb72747e9 | ||
|
|
8e577c0924 | ||
|
|
6ce160447c | ||
|
|
b94a47ccb4 | ||
|
|
ec34b33ce7 | ||
|
|
024b95c12e | ||
|
|
cf4dc2aa4a | ||
|
|
1919ce2935 | ||
|
|
1e3dfb64ae | ||
|
|
f87eaa82dd | ||
|
|
13b90186d3 | ||
|
|
2c4e329663 | ||
|
|
624e129ef7 | ||
|
|
2f19bd7fc9 | ||
|
|
da48539585 | ||
|
|
a1d263837a | ||
|
|
188824ecaa | ||
|
|
f5376290ab | ||
|
|
b888e37556 | ||
|
|
cb43160902 | ||
|
|
a3ad6aa4c5 | ||
|
|
d0f0bedae2 | ||
|
|
3f58349ce8 | ||
|
|
e90d7d5f0e | ||
|
|
b39e5d2721 | ||
|
|
3ede2713b6 | ||
|
|
dc2fc292ce | ||
|
|
3dc50d2d7a | ||
|
|
3d15425460 | ||
|
|
412e27ee81 | ||
|
|
d73ea341c0 | ||
|
|
694ebf83ed | ||
|
|
78bb03fabf | ||
|
|
902215f064 | ||
|
|
eaf38729bd | ||
|
|
bdd89ca539 | ||
|
|
39ecd31455 | ||
|
|
c96a2cf3ec | ||
|
|
ea9e29b98c | ||
|
|
96e1ef5a52 | ||
|
|
ed4b0a75dd | ||
|
|
37354338cb | ||
|
|
3cd2eecc22 | ||
|
|
0e40d3f7c3 | ||
|
|
c8fa01116d | ||
|
|
04539b4c37 | ||
|
|
b9e8f4cfca | ||
|
|
ad06c2bf9e | ||
|
|
2c7ec74b1b | ||
|
|
b34f29c95f | ||
|
|
e11518c3f5 | ||
|
|
57c4fad307 | ||
|
|
856b8e37e1 | ||
|
|
6b8dd8e864 | ||
|
|
c993cd53f1 | ||
|
|
934983f668 | ||
|
|
ccf425faad | ||
|
|
a244763109 | ||
|
|
7ab712e040 | ||
|
|
cb7219b290 | ||
|
|
7f4ba50ee4 | ||
|
|
b1f00f0592 |
+1
-1
@@ -1,8 +1,8 @@
|
||||
*.DS_Store
|
||||
*.swp
|
||||
.gradle
|
||||
.idea
|
||||
build
|
||||
local.properties
|
||||
gradle.properties
|
||||
infer-out
|
||||
*.iml
|
||||
|
||||
@@ -1,5 +1,97 @@
|
||||
# Change Log
|
||||
|
||||
# 1.18.0
|
||||
* Make open URL default action after decoding a barcode
|
||||
* Add a setting to open contents without inspection
|
||||
* Highlight codes for picking in shared/loaded images
|
||||
* Improve visability of crop border
|
||||
* Fix returning result for ZXing's SCAN intent
|
||||
|
||||
# 1.17.1
|
||||
* Fix navigating back without choosing an image
|
||||
|
||||
# 1.17.0
|
||||
* Pinch/Zoom shared/loaded images to scan just a section
|
||||
* Add a load file menu item to load an image file
|
||||
* Add a save file menu item to the barcode view
|
||||
* Use discrete style for size seek bar
|
||||
* Fix bar transparency when zooming/pinching
|
||||
|
||||
# 1.16.0
|
||||
* Improve displaying scanned barcodes
|
||||
* Show a message if a barcode cannot be shared
|
||||
* Fix setting system bar transparency when double taping a barcode
|
||||
* Update Dutch translation
|
||||
* Update Hungarian translation
|
||||
* Update Indonesian translation
|
||||
* Update Italian translation
|
||||
|
||||
# 1.15.0
|
||||
* Additionally export history as CSV file
|
||||
* Color tool and system bars when a view is scrolled
|
||||
* Add an intent filter for opening images
|
||||
* Add Russian translation
|
||||
|
||||
# 1.14.0
|
||||
* Add OTP support
|
||||
* Update Indonesian translation
|
||||
|
||||
# 1.13.1
|
||||
* Fix crash caused by missing support for Regex on Android < 6
|
||||
|
||||
# 1.13.0
|
||||
* Add missing extras for ZXing result intents
|
||||
|
||||
# 1.12.0
|
||||
* Add preferences dialog and the option to use a custom URL with read contents
|
||||
* Add Brazilian Portuguese translation
|
||||
* Add Chinese translation
|
||||
* Encode text using UTF-8 encoding when creating a barcode
|
||||
|
||||
# 1.11.2
|
||||
* Downgrade ZXing version
|
||||
* Improve getting system UI metrics
|
||||
|
||||
# 1.11.1
|
||||
* Fix tap to focus on some devices
|
||||
|
||||
# 1.11.0
|
||||
* Add support for VCard and VEvent barcodes contents
|
||||
* Generated barcodes are now zoomable
|
||||
* Fix creating PDF417 barcodes
|
||||
|
||||
## 1.10.0
|
||||
* Add support for special barcode contents (WiFi, SMS, phone, E-mail)
|
||||
* Add Indonesian translation
|
||||
* Update Hungarian translation
|
||||
* Make system bars completely transparent
|
||||
|
||||
## 1.9.1
|
||||
* Pick search engine for content that is not an URL
|
||||
|
||||
## 1.9.0
|
||||
* Try Google when opening content as URL fails
|
||||
* Show generic error message if barcode generation fails
|
||||
|
||||
## 1.8.1
|
||||
* Distribute APK instead of App Bundle
|
||||
|
||||
## 1.8.0
|
||||
* Save binary data into external file
|
||||
* Fix storage of binary data
|
||||
* Remove horizontal orientation line from scanner
|
||||
|
||||
## 1.7.3
|
||||
* Fix crashes on Lineage and remove RenderScript fallback
|
||||
* Improve handling of binary data
|
||||
|
||||
## 1.7.2
|
||||
* Add a fallback for RenderScript
|
||||
* Add italian translation
|
||||
|
||||
## 1.7.1
|
||||
* Fix support for 64 bit architectures
|
||||
|
||||
## 1.7.0
|
||||
* Pick list separator on sharing history listing
|
||||
* Update Hungarian translation
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Contribution Guidelines
|
||||
|
||||
Please try to keep things in good shape and comply to what's there.
|
||||
|
||||
This project follows Android [best practices][android_best_practices]
|
||||
so please have a look if you've never heard of them.
|
||||
|
||||
The code is formatted according to Android Studio's standard, with the
|
||||
exception of indent being tabs instead of spaces.
|
||||
|
||||
Use the feature branch workflow to add new features and make sure
|
||||
to squash when merging into master:
|
||||
|
||||
$ git merge cool_feature --squash
|
||||
|
||||
Then write a [good commit messages][commit_messages] to keep the history
|
||||
meaningful and useful. One feature, one commit.
|
||||
|
||||
[android_best_practices]: https://developer.android.com/distribute/best-practices/develop/
|
||||
[commit_messages]: https://juffalow.com/other/write-good-git-commit-message
|
||||
@@ -1,5 +1,4 @@
|
||||
PACKAGE = de.markusfisch.android.binaryeye
|
||||
APK = app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
all: debug install start
|
||||
|
||||
@@ -7,26 +6,22 @@ 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
|
||||
./gradlew test 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 \
|
||||
|
||||
+2
-1
@@ -2,4 +2,5 @@
|
||||
|
||||
Binary Eye does not collect any personal information.
|
||||
|
||||
If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me.
|
||||
If you have any questions or suggestions about my Privacy Policy, do not
|
||||
hesitate to contact me.
|
||||
|
||||
@@ -5,15 +5,29 @@ Yet another barcode scanner for Android. As if there weren't [enough][play].
|
||||
This one is totally free, open source and does use the [ZXing][zxing] barcode
|
||||
scanning library.
|
||||
|
||||
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
|
||||
Unlike many others, it works in portrait and landscape orientation, can read
|
||||
inverted codes, comes in Material Design and doesn't do much else but decoding
|
||||
a barcode.
|
||||
|
||||
<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.
|
||||
|
||||
## Screenshots
|
||||
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/markusfisch/BinaryEye/gh-pages/screencap-scanning.png"
|
||||
alt="Screenshot Scanning" width="160"/>
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/markusfisch/BinaryEye/gh-pages/screencap-decoded.png"
|
||||
alt="Screenshot Result" width="160"/>
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/markusfisch/BinaryEye/gh-pages/screencap-compose-barcode.png"
|
||||
alt="Screenshot Compose Barcode" width="160"/>
|
||||
|
||||
## Download
|
||||
|
||||
<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>
|
||||
|
||||
[play]: https://play.google.com/store/search?q=barcode%20scanner&c=apps
|
||||
[zxing]: https://github.com/zxing/zxing
|
||||
[kotlin]: http://kotlinlang.org/
|
||||
|
||||
+20
-10
@@ -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,20 +9,31 @@ android {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion sdk_version
|
||||
|
||||
versionCode 22
|
||||
versionName '1.7.0'
|
||||
versionCode 46
|
||||
versionName '1.18.0'
|
||||
|
||||
// it's recommended to set this value to the lowest API level
|
||||
// able to provide all the functionality
|
||||
renderscriptTargetApi 18
|
||||
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'
|
||||
test.java.srcDirs += 'src/test/kotlin'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -39,6 +45,7 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
signingConfig signingConfigs.config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,9 +67,12 @@ dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
|
||||
implementation "com.android.support:appcompat-v7:$support_version"
|
||||
implementation "com.android.support:design:$support_version"
|
||||
// Do NOT upgrade ZXing because newer versions won't work on API < 24
|
||||
// https://github.com/zxing/zxing/issues/1170
|
||||
implementation 'com.google.zxing:core:3.3.3'
|
||||
implementation 'com.github.markusfisch:CameraView:1.8.3'
|
||||
implementation 'com.github.markusfisch:CameraView:1.8.4'
|
||||
implementation 'com.github.markusfisch:ScalingImageView:1.1.4'
|
||||
}
|
||||
|
||||
@@ -21,22 +21,21 @@ class PreprocessorTest {
|
||||
val pattern = Pattern.compile(
|
||||
"[0-9]+-([0-9]+)x([0-9]+)-([0-9]+)deg.yuv"
|
||||
)
|
||||
|
||||
for (file in assets.list("yuv")) {
|
||||
val files = assets.list("yuv") ?: return
|
||||
for (file in files) {
|
||||
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 frameWidth = m.group(1) ?: return
|
||||
val frameHeight = m.group(2) ?: return
|
||||
val frameOrientation = m.group(3) ?: return
|
||||
val frameData = assets.open("yuv/$file").readBytes()
|
||||
val preprocessor = Preprocessor(
|
||||
InstrumentationRegistry.getTargetContext(),
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation
|
||||
frameWidth.toInt(),
|
||||
frameHeight.toInt(),
|
||||
frameOrientation.toInt()
|
||||
)
|
||||
preprocessor.process(frameData)
|
||||
val result = zxing.decode(
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-feature android:name="android.hardware.camera"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus"/>
|
||||
<application
|
||||
@@ -36,11 +38,6 @@
|
||||
<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"/>
|
||||
@@ -55,6 +52,20 @@
|
||||
android:scheme="http"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.PickActivity"
|
||||
android:label="@string/pick_code_to_scan">
|
||||
<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 android:label="@string/decode_barcode">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="image/*" android:scheme="file"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:label="@string/app_name"/>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package de.markusfisch.android.binaryeye.actions
|
||||
|
||||
import de.markusfisch.android.binaryeye.actions.mail.MailAction
|
||||
import de.markusfisch.android.binaryeye.actions.otpauth.OtpauthAction
|
||||
import de.markusfisch.android.binaryeye.actions.search.OpenOrSearchAction
|
||||
import de.markusfisch.android.binaryeye.actions.sms.SmsAction
|
||||
import de.markusfisch.android.binaryeye.actions.tel.TelAction
|
||||
import de.markusfisch.android.binaryeye.actions.vtype.vcard.VCardAction
|
||||
import de.markusfisch.android.binaryeye.actions.vtype.vevent.VEventAction
|
||||
import de.markusfisch.android.binaryeye.actions.web.WebAction
|
||||
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
|
||||
|
||||
object ActionRegistry {
|
||||
val DEFAULT_ACTION: IAction = OpenOrSearchAction
|
||||
|
||||
val REGISTRY: Set<IAction> = setOf(
|
||||
MailAction,
|
||||
OtpauthAction,
|
||||
SmsAction,
|
||||
TelAction,
|
||||
VCardAction,
|
||||
VEventAction,
|
||||
WebAction,
|
||||
WifiAction
|
||||
)
|
||||
|
||||
fun getAction(data: ByteArray): IAction = REGISTRY.find {
|
||||
it.canExecuteOn(data)
|
||||
} ?: DEFAULT_ACTION
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package de.markusfisch.android.binaryeye.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import de.markusfisch.android.binaryeye.app.execShareIntent
|
||||
import de.markusfisch.android.binaryeye.app.parseAndNormalizeUri
|
||||
|
||||
interface IAction {
|
||||
val iconResId: Int
|
||||
val titleResId: Int
|
||||
|
||||
fun canExecuteOn(data: ByteArray): Boolean
|
||||
suspend fun execute(context: Context, data: ByteArray)
|
||||
}
|
||||
|
||||
abstract class IntentAction : IAction {
|
||||
abstract val errorMsg: Int
|
||||
|
||||
final override suspend fun execute(context: Context, data: ByteArray) {
|
||||
val intent =
|
||||
createIntent(context, data) ?: return Toast.makeText(
|
||||
context,
|
||||
errorMsg,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
execShareIntent(context, intent)
|
||||
}
|
||||
|
||||
abstract suspend fun createIntent(context: Context, data: ByteArray): Intent?
|
||||
}
|
||||
|
||||
abstract class SchemeAction : IAction {
|
||||
abstract val scheme: String
|
||||
open val intentAction: String = Intent.ACTION_VIEW
|
||||
open val buildRegex: Boolean = false
|
||||
|
||||
final override fun canExecuteOn(data: ByteArray): Boolean {
|
||||
val content = String(data)
|
||||
return if (buildRegex) {
|
||||
content.matches("""^$scheme://[\w\W]+$""".toRegex(RegexOption.IGNORE_CASE))
|
||||
} else {
|
||||
content.startsWith("$scheme://", ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
final override suspend fun execute(context: Context, data: ByteArray) {
|
||||
val uri = parseAndNormalizeUri(String(data))
|
||||
execShareIntent(context, Intent(intentAction, uri))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package de.markusfisch.android.binaryeye.actions.mail
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IntentAction
|
||||
|
||||
object MailAction : IntentAction() {
|
||||
private val mailRegex =
|
||||
"""^mail(?:to)?:([\w.%+-]+@[A-Za-z\d.-]+\.[A-Za-z]{2,6}(?:[?&](?:subject|body)=[\S\s]*?){0,2})$""".toRegex()
|
||||
|
||||
override val iconResId: Int = R.drawable.ic_action_mail
|
||||
override val titleResId: Int = R.string.mail_send
|
||||
override val errorMsg: Int = R.string.mail_error
|
||||
|
||||
override fun canExecuteOn(data: ByteArray): Boolean {
|
||||
return String(data).matches(mailRegex)
|
||||
}
|
||||
|
||||
override suspend fun createIntent(context: Context, data: ByteArray): Intent? {
|
||||
val mailWithMessage = mailRegex.matchEntire(
|
||||
String(data)
|
||||
)?.groupValues?.get(1) ?: return null
|
||||
return Intent(
|
||||
Intent.ACTION_SENDTO,
|
||||
Uri.parse("mailto:$mailWithMessage")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package de.markusfisch.android.binaryeye.actions.otpauth
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.SchemeAction
|
||||
|
||||
object OtpauthAction : SchemeAction() {
|
||||
override val iconResId: Int = R.drawable.ic_action_otpauth
|
||||
override val titleResId: Int = R.string.otpauth_add
|
||||
override val scheme: String = "otpauth"
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package de.markusfisch.android.binaryeye.actions.search
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IAction
|
||||
import de.markusfisch.android.binaryeye.app.alertDialog
|
||||
import de.markusfisch.android.binaryeye.app.execShareIntent
|
||||
import de.markusfisch.android.binaryeye.app.parseAndNormalizeUri
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import java.net.URLEncoder
|
||||
|
||||
object OpenOrSearchAction : IAction {
|
||||
override val iconResId: Int = R.drawable.ic_action_search
|
||||
override val titleResId: Int = R.string.search_web
|
||||
|
||||
override fun canExecuteOn(data: ByteArray): Boolean = false
|
||||
|
||||
override suspend fun execute(context: Context, data: ByteArray) {
|
||||
val intent = openUri(context, String(data)) ?: return
|
||||
execShareIntent(context, intent)
|
||||
}
|
||||
|
||||
private suspend fun openUri(context: Context, data: String, search: Boolean = true): Intent? {
|
||||
val uri = parseAndNormalizeUri(data)
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
when {
|
||||
intent.resolveActivity(context.packageManager) != null -> return intent
|
||||
search -> return getSearchIntent(context, data)
|
||||
else -> Toast.makeText(
|
||||
context,
|
||||
R.string.cannot_resolve_action,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun getSearchIntent(context: Context, query: String): Intent? {
|
||||
val names = context.resources.getStringArray(
|
||||
R.array.search_engines_names
|
||||
).toMutableList()
|
||||
val urls = context.resources.getStringArray(
|
||||
R.array.search_engines_values
|
||||
).toMutableList()
|
||||
if (prefs.openWithUrl.isNotEmpty()) {
|
||||
names.add(prefs.openWithUrl)
|
||||
urls.add(prefs.openWithUrl)
|
||||
}
|
||||
val queryUri = alertDialog<String>(context) { resume ->
|
||||
setTitle(R.string.pick_search_engine)
|
||||
setItems(names.toTypedArray()) { _, which ->
|
||||
resume(urls[which] + URLEncoder.encode(query, "utf-8"))
|
||||
}
|
||||
} ?: return null
|
||||
return openUri(context, queryUri, false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package de.markusfisch.android.binaryeye.actions.sms
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IntentAction
|
||||
|
||||
object SmsAction : IntentAction() {
|
||||
private val smsRegex =
|
||||
"""^sms(?:to)?:(\+?[0-9]+)(?::([\S\s]*))?$""".toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
override val iconResId: Int = R.drawable.ic_action_sms
|
||||
override val titleResId: Int = R.string.sms_send
|
||||
override val errorMsg: Int = R.string.sms_error
|
||||
|
||||
override fun canExecuteOn(data: ByteArray): Boolean {
|
||||
return String(data).matches(smsRegex)
|
||||
}
|
||||
|
||||
override suspend fun createIntent(context: Context, data: ByteArray): Intent? {
|
||||
val (number: String, message: String) = smsRegex.matchEntire(
|
||||
String(data)
|
||||
)?.let {
|
||||
it.groupValues[1] to it.groupValues[2]
|
||||
} ?: return null
|
||||
return Intent(
|
||||
Intent.ACTION_SENDTO,
|
||||
Uri.parse("smsto:$number")
|
||||
).apply {
|
||||
if (message.isNotEmpty()) putExtra("sms_body", message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.markusfisch.android.binaryeye.actions.tel
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IntentAction
|
||||
import de.markusfisch.android.binaryeye.app.parseAndNormalizeUri
|
||||
|
||||
object TelAction : IntentAction() {
|
||||
private val telRegex = """^tel:(\+?[0-9]+)$""".toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
override val iconResId: Int = R.drawable.ic_action_tel
|
||||
override val titleResId: Int = R.string.tel_dial
|
||||
override val errorMsg: Int = R.string.tel_error
|
||||
|
||||
override fun canExecuteOn(data: ByteArray): Boolean {
|
||||
return String(data).matches(telRegex)
|
||||
}
|
||||
|
||||
override suspend fun createIntent(context: Context, data: ByteArray): Intent {
|
||||
return Intent(Intent.ACTION_DIAL, parseAndNormalizeUri(String(data)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package de.markusfisch.android.binaryeye.actions.vtype
|
||||
|
||||
import java.util.*
|
||||
|
||||
object VTypeParser {
|
||||
private const val BACKSLASH_R_LEGACY =
|
||||
"(?:\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])"
|
||||
private val vTypeRegex =
|
||||
"""^BEGIN:(VCARD|VEVENT)(?:$BACKSLASH_R_LEGACY.+?:[\s\S]+?)+?${BACKSLASH_R_LEGACY}END:\1$BACKSLASH_R_LEGACY?$""".toRegex(
|
||||
RegexOption.IGNORE_CASE
|
||||
)
|
||||
private val propertyRegex = """^(.+?):([\s\S]*?)$""".toRegex(RegexOption.MULTILINE)
|
||||
|
||||
fun parseVType(data: String): String? =
|
||||
vTypeRegex.matchEntire(data)?.groupValues?.get(1)?.toUpperCase(Locale.US)
|
||||
|
||||
fun parseMap(data: String): Map<String, List<VTypeProperty>> = propertyRegex.findAll(data).map {
|
||||
it.groupValues[1].split(';').let { typeAndInfo ->
|
||||
typeAndInfo[0] to VTypeProperty(typeAndInfo.drop(1), it.groupValues[2])
|
||||
}
|
||||
}.groupBy({ it.first.toUpperCase(Locale.US) }) { it.second }
|
||||
}
|
||||
|
||||
data class VTypeProperty(
|
||||
val info: List<String>,
|
||||
val value: String
|
||||
) {
|
||||
val firstTypeOrFirstInfo: String?
|
||||
get() = this.info.find { it.startsWith("TYPE=", true) }?.let {
|
||||
typeRegex.matchEntire(it)?.groupValues?.get(1)
|
||||
} ?: this.info.firstOrNull()
|
||||
|
||||
companion object {
|
||||
private val typeRegex = """^(?:TYPE=)(.+?)[:;]?.*$""".toRegex(RegexOption.IGNORE_CASE)
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
package de.markusfisch.android.binaryeye.actions.vtype.vcard
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.provider.ContactsContract
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IntentAction
|
||||
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
object VCardAction : IntentAction() {
|
||||
override val iconResId: Int
|
||||
get() = R.drawable.ic_action_vcard
|
||||
override val titleResId: Int
|
||||
get() = R.string.vcard_add
|
||||
override val errorMsg: Int
|
||||
get() = R.string.vcard_failed
|
||||
|
||||
override fun canExecuteOn(data: ByteArray): Boolean {
|
||||
return VTypeParser.parseVType(String(data)) == "VCARD"
|
||||
}
|
||||
|
||||
override suspend fun createIntent(context: Context, data: ByteArray): Intent? {
|
||||
val info = VTypeParser.parseMap(String(data))
|
||||
|
||||
return Intent(Intent.ACTION_INSERT_OR_EDIT).apply {
|
||||
type = ContactsContract.Contacts.CONTENT_ITEM_TYPE
|
||||
info["FN"]?.singleOrNull()?.also { name ->
|
||||
putExtra(ContactsContract.Intents.Insert.NAME, name.value)
|
||||
} ?: info["N"]?.singleOrNull()?.also { name ->
|
||||
putExtra(ContactsContract.Intents.Insert.NAME, name.value.nameFormat)
|
||||
}
|
||||
info["TEL"]?.filter { it.value.isNotEmpty() }?.forEachIndexed { index, phoneProperty ->
|
||||
val (extraPhoneType, extraPhone) = when (index) {
|
||||
0 -> ContactsContract.Intents.Insert.PHONE_TYPE to
|
||||
ContactsContract.Intents.Insert.PHONE
|
||||
1 -> ContactsContract.Intents.Insert.SECONDARY_PHONE_TYPE to
|
||||
ContactsContract.Intents.Insert.SECONDARY_PHONE
|
||||
2 -> ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE to
|
||||
ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE
|
||||
else -> return@forEachIndexed
|
||||
}
|
||||
phoneProperty.firstTypeOrFirstInfo?.also {
|
||||
it.phoneType?.also { phoneType ->
|
||||
putExtra(extraPhoneType, phoneType)
|
||||
} ?: putExtra(extraPhoneType, it)
|
||||
}
|
||||
putExtra(
|
||||
extraPhone,
|
||||
phoneProperty.value.toLowerCase(Locale.US).removePrefix("tel:")
|
||||
)
|
||||
}
|
||||
info["EMAIL"]?.forEachIndexed { index, mailProperty ->
|
||||
val (extraMailType, extraMail) = when (index) {
|
||||
0 -> ContactsContract.Intents.Insert.EMAIL_TYPE to
|
||||
ContactsContract.Intents.Insert.EMAIL
|
||||
1 -> ContactsContract.Intents.Insert.SECONDARY_EMAIL_TYPE to
|
||||
ContactsContract.Intents.Insert.SECONDARY_EMAIL
|
||||
2 -> ContactsContract.Intents.Insert.TERTIARY_EMAIL_TYPE to
|
||||
ContactsContract.Intents.Insert.TERTIARY_EMAIL
|
||||
else -> return@forEachIndexed
|
||||
}
|
||||
mailProperty.firstTypeOrFirstInfo?.also {
|
||||
it.mailType?.also { mailType ->
|
||||
putExtra(extraMailType, mailType)
|
||||
} ?: putExtra(extraMailType, it)
|
||||
}
|
||||
putExtra(extraMail, mailProperty.value)
|
||||
}
|
||||
info["ORG"]?.singleOrNull()?.also { organization ->
|
||||
putExtra(ContactsContract.Intents.Insert.COMPANY, organization.value)
|
||||
}
|
||||
info["TITLE"]?.singleOrNull()?.also { jobTitle ->
|
||||
putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobTitle.value)
|
||||
}
|
||||
info["ADR"]?.forEach { addr ->
|
||||
addr.firstTypeOrFirstInfo?.also {
|
||||
it.addressType?.also { addressType ->
|
||||
putExtra(ContactsContract.Intents.Insert.POSTAL_TYPE, addressType)
|
||||
} ?: putExtra(ContactsContract.Intents.Insert.POSTAL_TYPE, it)
|
||||
}
|
||||
putExtra(ContactsContract.Intents.Insert.POSTAL, addr.value.locationFormat)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
val urls = info["URL"]?.mapNotNull {
|
||||
it.value.takeIf { url -> url.isNotBlank() }?.let { url ->
|
||||
ContentValues(2).apply {
|
||||
put(
|
||||
ContactsContract.Data.MIMETYPE,
|
||||
ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE
|
||||
)
|
||||
put(ContactsContract.CommonDataKinds.Website.URL, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
val bDays = info["BDAY"]?.mapNotNull {
|
||||
it.value.takeIf { day -> day.isNotBlank() }?.let { day ->
|
||||
ContentValues(3).apply {
|
||||
put(
|
||||
ContactsContract.Data.MIMETYPE,
|
||||
ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE
|
||||
)
|
||||
put(
|
||||
ContactsContract.CommonDataKinds.Event.TYPE,
|
||||
ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY
|
||||
)
|
||||
put(ContactsContract.CommonDataKinds.Event.START_DATE, day)
|
||||
}
|
||||
}
|
||||
}
|
||||
putParcelableArrayListExtra(
|
||||
ContactsContract.Intents.Insert.DATA,
|
||||
ArrayList(listOfNotNull(urls, bDays).flatten())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val String.locationFormat: String
|
||||
get() = """^([^;]*?);([^;]*?);([^;]*?);([^;]*?);([^;]*?);([^;]*?);([^;]*?)$""".toRegex().matchEntire(
|
||||
this
|
||||
)?.groupValues?.let {
|
||||
listOf(
|
||||
it[1],
|
||||
it[2],
|
||||
it[3],
|
||||
"${it[4]} ${it[6]}",
|
||||
"${it[5]} ${it[7]}"
|
||||
).filter { line -> line.isNotBlank() }
|
||||
.joinToString("\n").takeIf { location -> location.isNotBlank() }
|
||||
} ?: this
|
||||
|
||||
private val String.nameFormat: String
|
||||
get() = """^([^;]*?);([^;]*?);([^;]*?);([^;]*?);([^;]*?)$""".toRegex().matchEntire(this)?.groupValues?.let {
|
||||
listOf(it[4], it[2], it[3], it[1], it[5]).joinToString(" ")
|
||||
.takeIf { name -> name.isNotBlank() }
|
||||
} ?: this
|
||||
|
||||
private val String.mailType: Int?
|
||||
get() = when (this.toUpperCase(Locale.US)) {
|
||||
"HOME" -> ContactsContract.CommonDataKinds.Email.TYPE_HOME
|
||||
"WORK" -> ContactsContract.CommonDataKinds.Email.TYPE_WORK
|
||||
"OTHER" -> ContactsContract.CommonDataKinds.Email.TYPE_OTHER
|
||||
"MOBILE" -> ContactsContract.CommonDataKinds.Email.TYPE_MOBILE
|
||||
else -> null
|
||||
}
|
||||
|
||||
private val String.phoneType: Int?
|
||||
get() = when (this.toUpperCase(Locale.US)) {
|
||||
"HOME" -> ContactsContract.CommonDataKinds.Phone.TYPE_HOME
|
||||
"MOBILE", "CELL" -> ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE
|
||||
"WORK" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK
|
||||
"FAX_WORK" -> ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK
|
||||
"FAX_HOME" -> ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME
|
||||
"PAGER" -> ContactsContract.CommonDataKinds.Phone.TYPE_PAGER
|
||||
"OTHER" -> ContactsContract.CommonDataKinds.Phone.TYPE_OTHER
|
||||
"CALLBACK" -> ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK
|
||||
"CAR" -> ContactsContract.CommonDataKinds.Phone.TYPE_CAR
|
||||
"COMPANY_MAIN" -> ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN
|
||||
"ISDN" -> ContactsContract.CommonDataKinds.Phone.TYPE_ISDN
|
||||
"MAIN" -> ContactsContract.CommonDataKinds.Phone.TYPE_MAIN
|
||||
"OTHER_FAX" -> ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX
|
||||
"RADIO" -> ContactsContract.CommonDataKinds.Phone.TYPE_RADIO
|
||||
"TELEX" -> ContactsContract.CommonDataKinds.Phone.TYPE_TELEX
|
||||
"TTY_TDD" -> ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD
|
||||
"WORK_MOBILE" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE
|
||||
"WORK_PAGER" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER
|
||||
"ASSISTANT" -> ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT
|
||||
"MMS" -> ContactsContract.CommonDataKinds.Phone.TYPE_MMS
|
||||
else -> null
|
||||
}
|
||||
|
||||
private val String.addressType: Int?
|
||||
get() = when (this.toUpperCase(Locale.US)) {
|
||||
"HOME" -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME
|
||||
"WORK" -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK
|
||||
"OTHER" -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package de.markusfisch.android.binaryeye.actions.vtype.vevent
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.provider.CalendarContract
|
||||
import android.support.annotation.RequiresApi
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IntentAction
|
||||
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
object VEventAction : IntentAction() {
|
||||
override val iconResId: Int
|
||||
get() = R.drawable.ic_action_vevent
|
||||
override val titleResId: Int
|
||||
get() = R.string.vevent_add
|
||||
override val errorMsg: Int
|
||||
get() = R.string.vevent_failed
|
||||
|
||||
override fun canExecuteOn(data: ByteArray): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH
|
||||
&& VTypeParser.parseVType(String(data)) == "VEVENT"
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
override suspend fun createIntent(context: Context, data: ByteArray): Intent? {
|
||||
val info = VTypeParser.parseMap(String(data))
|
||||
|
||||
return Intent(Intent.ACTION_EDIT).apply {
|
||||
type = "vnd.android.cursor.item/event"
|
||||
info["SUMMARY"]?.singleOrNull()?.also { title ->
|
||||
putExtra(CalendarContract.Events.TITLE, title.value)
|
||||
}
|
||||
info["DESCRIPTION"]?.singleOrNull()?.also { description ->
|
||||
putExtra(CalendarContract.Events.DESCRIPTION, description.value)
|
||||
}
|
||||
info["LOCATION"]?.singleOrNull()?.also { location ->
|
||||
putExtra(CalendarContract.Events.EVENT_LOCATION, location.value)
|
||||
}
|
||||
info["DTSTART"]?.singleOrNull()?.also { eventStart ->
|
||||
dateFormats.simpleFindParse(eventStart.value)?.also {
|
||||
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, it.time)
|
||||
}
|
||||
}
|
||||
info["DTEND"]?.singleOrNull()?.also { eventEnd ->
|
||||
dateFormats.simpleFindParse(eventEnd.value)?.also {
|
||||
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, it.time)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat") // we definitely don't wan't the local format
|
||||
private val dateFormats = listOf(
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"),
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"),
|
||||
SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"),
|
||||
SimpleDateFormat("yyyyMMdd'T'HHmmss"),
|
||||
SimpleDateFormat("yyyy-MM-dd"),
|
||||
SimpleDateFormat("yyyyMMdd")
|
||||
)
|
||||
|
||||
private fun List<SimpleDateFormat>.simpleFindParse(data: String): Date? {
|
||||
for (simpleDataFormat in this) {
|
||||
return simpleDataFormat.simpleParse(data) ?: continue
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun SimpleDateFormat.simpleParse(data: String): Date? = try {
|
||||
this.parse(data)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.markusfisch.android.binaryeye.actions.web
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.SchemeAction
|
||||
|
||||
object WebAction : SchemeAction() {
|
||||
override val iconResId: Int = R.drawable.ic_action_open
|
||||
override val titleResId: Int = R.string.open_url
|
||||
override val scheme: String = "https?"
|
||||
override val buildRegex: Boolean = true
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package de.markusfisch.android.binaryeye.actions.wifi
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Context.WIFI_SERVICE
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import android.net.wifi.WifiManager
|
||||
import android.widget.Toast
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IAction
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
|
||||
object WifiAction : IAction {
|
||||
override val iconResId = R.drawable.ic_action_wifi
|
||||
override val titleResId = R.string.connect_to_wifi
|
||||
|
||||
override fun canExecuteOn(data: ByteArray): Boolean =
|
||||
WifiConfigurationFactory.parse(String(data)) != null
|
||||
|
||||
override suspend fun execute(context: Context, data: ByteArray) = withContext(Dispatchers.IO) {
|
||||
val wifiConfig = WifiConfigurationFactory.parse(
|
||||
String(data)
|
||||
) ?: return@withContext
|
||||
val wifiManager = context.applicationContext.getSystemService(
|
||||
WIFI_SERVICE
|
||||
) as WifiManager
|
||||
|
||||
wifiManager.enableWifi(context)
|
||||
wifiManager.mayRemoveOldNetwork(wifiConfig)
|
||||
wifiManager.enableNewNetwork(wifiConfig)
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.wifi_added,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun WifiManager.enableWifi(context: Context): Boolean {
|
||||
if (!isWifiEnabled) {
|
||||
if (!setWifiEnabled(true)) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.wifi_config_failed,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
return false
|
||||
}
|
||||
// wait for wifi to really be enabled, takes some time
|
||||
withTimeoutOrNull(10000) {
|
||||
while (!isWifiEnabled) delay(500)
|
||||
} ?: if (!isWifiEnabled) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.wifi_config_failed,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun WifiManager.mayRemoveOldNetwork(
|
||||
wifiConfig: WifiConfiguration
|
||||
) {
|
||||
configuredNetworks?.firstOrNull {
|
||||
it.SSID == wifiConfig.SSID &&
|
||||
it.allowedKeyManagement == wifiConfig.allowedKeyManagement
|
||||
}?.networkId?.also {
|
||||
removeNetwork(it)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun WifiManager.enableNewNetwork(
|
||||
wifiConfig: WifiConfiguration
|
||||
) {
|
||||
val id = addNetwork(wifiConfig)
|
||||
disconnect()
|
||||
enableNetwork(id, true)
|
||||
reconnect()
|
||||
}
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
package de.markusfisch.android.binaryeye.actions.wifi
|
||||
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import android.net.wifi.WifiEnterpriseConfig
|
||||
import android.os.Build
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Normal:
|
||||
* WIFI:S:[network SSID];T:<WPA|WEP|nopass|>;P:[network password];H:<true|false|>;;
|
||||
*
|
||||
* WPA2 enterprise (EAP):
|
||||
* WIFI:S:[network SSID];T:WPA2-EAP;H:<true|false|nopass|>;E:[EAP method];PH2:[Phase 2 method];AI:[anonymous identity];I:[identity];P:[password];;
|
||||
*
|
||||
* EPA methods:
|
||||
* "AKA", "AKA_PRIME",
|
||||
* "NONE", "PEAP",
|
||||
* "PWD", "SIM",
|
||||
* "TLS", "TTLS",
|
||||
* "UNAUTH_TLS"
|
||||
* https://developer.android.com/reference/android/net/wifi/WifiEnterpriseConfig.Eap.html
|
||||
*
|
||||
* Phase 2 methods:
|
||||
* "AKA", "AKA_PRIME",
|
||||
* "GTC", "MSCHAP",
|
||||
* "MSCHAPV2", "NONE",
|
||||
* "PAP", "SIM"
|
||||
* https://developer.android.com/reference/android/net/wifi/WifiEnterpriseConfig.Phase2.html
|
||||
*
|
||||
* The fields can appear in any order. Only "S:" is required.
|
||||
*/
|
||||
object WifiConfigurationFactory {
|
||||
fun parse(input: String): WifiConfiguration? {
|
||||
val inputMap = parseMap(input) ?: return null
|
||||
val parsedData = SimpleDataAccessor.of(inputMap) ?: return null
|
||||
return WifiConfiguration().apply(parsedData)
|
||||
}
|
||||
|
||||
internal fun parseMap(string: String): Map<String, String>? {
|
||||
// normally those codes should have the last semicolon, but many
|
||||
// generators don't add it
|
||||
val wifiRegex = """^WIFI:((?:.+?:(?:[^\\;]|\\.)*;)+);?$""".toRegex()
|
||||
// should be: ^(.+):((?:[^\\;,":]|\\.)*);$ but allows unescaped , "
|
||||
// and : because many qr creator doesn't escape
|
||||
val pairRegex = """(.+?):((?:[^\\;]|\\.)*);""".toRegex()
|
||||
|
||||
return wifiRegex.matchEntire(
|
||||
string
|
||||
)?.groupValues?.get(1)?.let { pairs ->
|
||||
pairRegex.findAll(pairs).map { pair ->
|
||||
pair.groupValues[1].toUpperCase(Locale.US) to pair.groupValues[2]
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
|
||||
internal class SimpleDataAccessor private constructor(
|
||||
private val inputMap: Map<String, String>
|
||||
) {
|
||||
private val hexRegex = """^[0-9a-f]+$""".toRegex(
|
||||
RegexOption.IGNORE_CASE
|
||||
)
|
||||
// keep possibility of wrongly not escaped \ by explicitly searching
|
||||
// for special chars
|
||||
private val escapedRegex = """\\([\\;,":])""".toRegex()
|
||||
|
||||
// this should be private but because of testing it isn't possible
|
||||
internal val ssid: String
|
||||
get() = inputMap.getValue("S").quotedUnlessHex.unescaped
|
||||
internal val securityType: String
|
||||
get() = inputMap["T"]?.unescaped ?: ""
|
||||
internal val password: String?
|
||||
get() = inputMap["P"]?.quotedUnlessHex?.unescaped
|
||||
internal val hidden: Boolean
|
||||
get() = inputMap["H"]?.unescaped == "true"
|
||||
internal val anonymousIdentity: String
|
||||
get() = inputMap["AI"]?.unescaped ?: ""
|
||||
internal val identity: String
|
||||
get() = inputMap["I"]?.unescaped ?: ""
|
||||
internal val eapMethod: Int?
|
||||
get() = if (inputMap["E"].isNullOrEmpty()) {
|
||||
requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
WifiEnterpriseConfig.Eap.NONE
|
||||
}
|
||||
} else when (inputMap["E"]) {
|
||||
"AKA" -> requireSdk(Build.VERSION_CODES.LOLLIPOP) { WifiEnterpriseConfig.Eap.AKA }
|
||||
"AKA_PRIME" -> requireSdk(Build.VERSION_CODES.M) { WifiEnterpriseConfig.Eap.AKA_PRIME }
|
||||
"NONE" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.NONE }
|
||||
"PEAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.PEAP }
|
||||
"PWD" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.PWD }
|
||||
"SIM" -> requireSdk(Build.VERSION_CODES.LOLLIPOP) { WifiEnterpriseConfig.Eap.SIM }
|
||||
"TLS" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.TLS }
|
||||
"TTLS" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.TTLS }
|
||||
"UNAUTH_TLS" -> requireSdk(Build.VERSION_CODES.N) { WifiEnterpriseConfig.Eap.UNAUTH_TLS }
|
||||
else -> null
|
||||
}
|
||||
internal val phase2Method: Int?
|
||||
get() = if (inputMap["PH2"].isNullOrEmpty()) {
|
||||
requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
WifiEnterpriseConfig.Phase2.NONE
|
||||
}
|
||||
} else when (inputMap["PH2"]) {
|
||||
"AKA" -> requireSdk(Build.VERSION_CODES.O) { WifiEnterpriseConfig.Phase2.AKA }
|
||||
"AKA_PRIME" -> requireSdk(Build.VERSION_CODES.O) { WifiEnterpriseConfig.Phase2.AKA_PRIME }
|
||||
"GTC" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.GTC }
|
||||
"MSCHAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.MSCHAP }
|
||||
"MSCHAPV2" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.MSCHAPV2 }
|
||||
"NONE" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.NONE }
|
||||
"PAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.PAP }
|
||||
"SIM" -> requireSdk(Build.VERSION_CODES.O) { WifiEnterpriseConfig.Phase2.SIM }
|
||||
else -> null
|
||||
}
|
||||
|
||||
private val String.unescaped: String
|
||||
get() = this.replace(escapedRegex) { escaped ->
|
||||
escaped.groupValues[1]
|
||||
}
|
||||
|
||||
private val String.quotedUnlessHex: String
|
||||
get() = if (matches(hexRegex) || (startsWith("\"") &&
|
||||
endsWith("\""))
|
||||
) this else "\"$this\""
|
||||
|
||||
internal companion object {
|
||||
internal fun of(inputMap: Map<String, String>): SimpleDataAccessor? {
|
||||
return SimpleDataAccessor(inputMap).takeUnless {
|
||||
inputMap["S"].isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WifiConfiguration.apply(
|
||||
data: SimpleDataAccessor
|
||||
): WifiConfiguration? {
|
||||
fun WifiConfiguration.applyCommon(
|
||||
data: SimpleDataAccessor
|
||||
): WifiConfiguration? {
|
||||
allowedAuthAlgorithms.clear()
|
||||
allowedGroupCiphers.clear()
|
||||
allowedKeyManagement.clear()
|
||||
allowedPairwiseCiphers.clear()
|
||||
allowedProtocols.clear()
|
||||
|
||||
SSID = data.ssid
|
||||
hiddenSSID = data.hidden
|
||||
return this
|
||||
}
|
||||
|
||||
fun WifiConfiguration.applySecurity(
|
||||
data: SimpleDataAccessor
|
||||
): WifiConfiguration? {
|
||||
when (data.securityType) {
|
||||
"", "nopass" -> allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
|
||||
"WEP" -> @Suppress("DEPRECATION") /* WEP as insecure */ {
|
||||
data.password?.also {
|
||||
wepKeys[0] = it
|
||||
} ?: return null
|
||||
wepTxKeyIndex = 0
|
||||
allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED)
|
||||
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
|
||||
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)
|
||||
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
|
||||
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40)
|
||||
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104)
|
||||
}
|
||||
"WPA" -> {
|
||||
data.password?.also {
|
||||
preSharedKey = it
|
||||
} ?: return null
|
||||
allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
|
||||
@Suppress("DEPRECATION") // WPA 1 is insecure and has bad performance
|
||||
allowedProtocols.set(WifiConfiguration.Protocol.WPA) // WPA
|
||||
allowedProtocols.set(WifiConfiguration.Protocol.RSN) // WPA2
|
||||
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK)
|
||||
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP)
|
||||
@Suppress("DEPRECATION") // TKIP is insecure and has bad performance
|
||||
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
|
||||
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
|
||||
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)
|
||||
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
|
||||
}
|
||||
"WPA2-EAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
data.password?.also {
|
||||
preSharedKey = it
|
||||
} ?: return null
|
||||
allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
|
||||
allowedProtocols.set(WifiConfiguration.Protocol.RSN) // WPA2
|
||||
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP)
|
||||
@Suppress("DEPRECATION") // TKIP is insecure and has bad performance
|
||||
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
|
||||
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
|
||||
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)
|
||||
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
|
||||
|
||||
enterpriseConfig.identity = data.identity
|
||||
enterpriseConfig.anonymousIdentity = data.anonymousIdentity
|
||||
enterpriseConfig.password = data.password
|
||||
enterpriseConfig.eapMethod =
|
||||
data.eapMethod ?: return null // non valid eapMethod
|
||||
enterpriseConfig.phase2Method =
|
||||
data.phase2Method ?: return null // non valid phase2Method
|
||||
} ?: return null // api isn't high enough
|
||||
}
|
||||
return this
|
||||
}
|
||||
return this.applyCommon(data)?.applySecurity(data)
|
||||
}
|
||||
|
||||
private inline fun <T> requireSdk(version: Int, block: () -> T): T? {
|
||||
return if (Build.VERSION.SDK_INT >= version) return block() else null
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,13 @@
|
||||
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.rs.Preprocessor
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.app.Activity
|
||||
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.view.Menu
|
||||
@@ -30,12 +15,19 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.SeekBar
|
||||
import android.widget.Toast
|
||||
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.ResultMetadataType
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.db
|
||||
import de.markusfisch.android.binaryeye.app.hasCameraPermission
|
||||
import de.markusfisch.android.binaryeye.app.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.rs.Preprocessor
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
import de.markusfisch.android.cameraview.widget.CameraView
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
import java.io.IOException
|
||||
|
||||
class CameraActivity : AppCompatActivity() {
|
||||
private val zxing = Zxing()
|
||||
|
||||
@@ -44,12 +36,11 @@ class CameraActivity : AppCompatActivity() {
|
||||
private lateinit var zoomBar: SeekBar
|
||||
private lateinit var flashFab: View
|
||||
|
||||
private var decodingThread: Thread? = null
|
||||
private var preprocessor: Preprocessor? = null
|
||||
private var invert = false
|
||||
private var flash = false
|
||||
private var returnResult = false
|
||||
private var frontFacing = false
|
||||
private var fallbackBuffer: IntArray? = null
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
@@ -57,8 +48,9 @@ class CameraActivity : AppCompatActivity() {
|
||||
grantResults: IntArray
|
||||
) {
|
||||
when (requestCode) {
|
||||
REQUEST_CAMERA -> if (grantResults.size > 0 &&
|
||||
grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
REQUEST_CAMERA -> if (grantResults.isNotEmpty() &&
|
||||
grantResults[0] != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
R.string.no_camera_no_fun,
|
||||
@@ -69,6 +61,23 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(
|
||||
requestCode: Int,
|
||||
resultCode: Int,
|
||||
resultData: Intent?
|
||||
) {
|
||||
when (requestCode) {
|
||||
PICK_FILE_RESULT_CODE -> {
|
||||
if (resultCode == Activity.RESULT_OK && resultData != null) {
|
||||
val pick = Intent(this, PickActivity::class.java)
|
||||
pick.action = Intent.ACTION_VIEW
|
||||
pick.setDataAndType(resultData.data, "image/*")
|
||||
startActivity(pick)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setContentView(R.layout.activity_camera)
|
||||
@@ -81,34 +90,30 @@ class CameraActivity : AppCompatActivity() {
|
||||
cameraView = findViewById(R.id.camera_view) as CameraView
|
||||
zoomBar = findViewById(R.id.zoom) as SeekBar
|
||||
flashFab = findViewById(R.id.flash)
|
||||
flashFab.setOnClickListener { _ -> toggleTorchMode() }
|
||||
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)
|
||||
}
|
||||
if (intent?.action == Intent.ACTION_SEND &&
|
||||
intent.type == "text/plain"
|
||||
) {
|
||||
handleSendText(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
preprocessor?.destroy()
|
||||
fallbackBuffer = null
|
||||
saveZoom()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
System.gc()
|
||||
returnResult = "com.google.zxing.client.android.SCAN".equals(
|
||||
intent.action
|
||||
)
|
||||
if (hasCameraPermission()) {
|
||||
if (hasCameraPermission(this, REQUEST_CAMERA)) {
|
||||
openCamera()
|
||||
}
|
||||
}
|
||||
@@ -161,10 +166,26 @@ class CameraActivity : AppCompatActivity() {
|
||||
startActivity(MainActivity.getHistoryIntent(this))
|
||||
true
|
||||
}
|
||||
R.id.pick_file -> {
|
||||
val chooseFile = Intent(Intent.ACTION_GET_CONTENT)
|
||||
chooseFile.type = "image/*"
|
||||
startActivityForResult(
|
||||
Intent.createChooser(
|
||||
chooseFile,
|
||||
getString(R.string.pick_file)
|
||||
),
|
||||
PICK_FILE_RESULT_CODE
|
||||
)
|
||||
true
|
||||
}
|
||||
R.id.switch_camera -> {
|
||||
switchCamera()
|
||||
true
|
||||
}
|
||||
R.id.preferences -> {
|
||||
startActivity(MainActivity.getPreferencesIntent(this))
|
||||
true
|
||||
}
|
||||
R.id.info -> {
|
||||
openReadme()
|
||||
true
|
||||
@@ -197,50 +218,6 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
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.decode(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()
|
||||
@@ -297,7 +274,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
result?.let {
|
||||
cameraView.post {
|
||||
vibrator.vibrate(100)
|
||||
showResult(result)
|
||||
showResult(this@CameraActivity, result)
|
||||
}
|
||||
decoding = false
|
||||
}
|
||||
@@ -392,18 +369,15 @@ class CameraActivity : AppCompatActivity() {
|
||||
frameOrientation: Int
|
||||
): Result? {
|
||||
frameData ?: return null
|
||||
if (preprocessor == null) {
|
||||
preprocessor = Preprocessor(
|
||||
this,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation
|
||||
)
|
||||
}
|
||||
val pp = preprocessor
|
||||
pp ?: return null
|
||||
pp.process(frameData)
|
||||
invert = invert xor true
|
||||
val pp = preprocessor ?: Preprocessor(
|
||||
this,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation
|
||||
)
|
||||
pp.process(frameData)
|
||||
preprocessor = pp
|
||||
return zxing.decode(
|
||||
frameData,
|
||||
pp.outWidth,
|
||||
@@ -412,58 +386,100 @@ class CameraActivity : AppCompatActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
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 PICK_FILE_RESULT_CODE = 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()
|
||||
fun showResult(
|
||||
activity: Activity,
|
||||
result: Result,
|
||||
isResult: Boolean = false
|
||||
) {
|
||||
val rawBytes = getRawBytes(result)
|
||||
if (prefs.useHistory) {
|
||||
GlobalScope.launch {
|
||||
db.insertScan(
|
||||
System.currentTimeMillis(),
|
||||
result.text,
|
||||
rawBytes,
|
||||
result.barcodeFormat.toString()
|
||||
)
|
||||
}
|
||||
Bitmap.createScaledBitmap(bitmap, width, height, true)
|
||||
}
|
||||
if (isResult) {
|
||||
activity.setResult(Activity.RESULT_OK, getReturnIntent(result))
|
||||
activity.finish()
|
||||
} else {
|
||||
bitmap
|
||||
activity.startActivity(
|
||||
MainActivity.getDecodeIntent(
|
||||
activity,
|
||||
result.text,
|
||||
result.barcodeFormat,
|
||||
rawBytes
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getRawBytes(result: Result): ByteArray? {
|
||||
val metadata = result.resultMetadata ?: return null
|
||||
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
|
||||
var bytes = ByteArray(0)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
for (seg in segments as Iterable<ByteArray>) {
|
||||
bytes += seg
|
||||
}
|
||||
// byte segments can never be shorter than the text.
|
||||
// Zxing cuts off content prefixes like "WIFI:"
|
||||
return if (bytes.size >= result.text.length) bytes else null
|
||||
}
|
||||
|
||||
fun getReturnIntent(result: Result): Intent {
|
||||
val intent = Intent()
|
||||
intent.putExtra("SCAN_RESULT", result.text)
|
||||
intent.putExtra(
|
||||
"SCAN_RESULT_FORMAT",
|
||||
result.barcodeFormat.toString()
|
||||
)
|
||||
if (result.rawBytes?.isNotEmpty() == true) {
|
||||
intent.putExtra("SCAN_RESULT_BYTES", result.rawBytes)
|
||||
}
|
||||
val metadata = result.resultMetadata
|
||||
metadata?.let {
|
||||
val orientation = metadata[ResultMetadataType.UPC_EAN_EXTENSION]
|
||||
orientation?.let {
|
||||
intent.putExtra(
|
||||
"SCAN_RESULT_ORIENTATION",
|
||||
orientation.toString()
|
||||
)
|
||||
}
|
||||
val ecLevel = metadata[ResultMetadataType.ERROR_CORRECTION_LEVEL]
|
||||
ecLevel?.let {
|
||||
intent.putExtra(
|
||||
"SCAN_RESULT_ERROR_CORRECTION_LEVEL",
|
||||
ecLevel.toString()
|
||||
)
|
||||
}
|
||||
val upcEanExtension = metadata[ResultMetadataType.UPC_EAN_EXTENSION]
|
||||
upcEanExtension?.let {
|
||||
intent.putExtra(
|
||||
"SCAN_RESULT_UPC_EAN_EXTENSION",
|
||||
upcEanExtension.toString()
|
||||
)
|
||||
}
|
||||
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS]
|
||||
segments?.let {
|
||||
var i = 0
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
for (seg in segments as Iterable<ByteArray>) {
|
||||
intent.putExtra("SCAN_RESULT_BYTE_SEGMENTS_$i", seg)
|
||||
++i
|
||||
}
|
||||
}
|
||||
}
|
||||
return intent
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package de.markusfisch.android.binaryeye.activity
|
||||
|
||||
import com.google.zxing.BarcodeFormat
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.app.setFragment
|
||||
import de.markusfisch.android.binaryeye.fragment.DecodeFragment
|
||||
import de.markusfisch.android.binaryeye.fragment.EncodeFragment
|
||||
import de.markusfisch.android.binaryeye.fragment.HistoryFragment
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.app.setFragment
|
||||
import de.markusfisch.android.binaryeye.app.setSystemAndToolBarTransparency
|
||||
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.fragment.PreferencesFragment
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
@@ -34,28 +35,48 @@ class MainActivity : AppCompatActivity() {
|
||||
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
supportFragmentManager.addOnBackStackChangedListener {
|
||||
setSystemAndToolBarTransparency(this@MainActivity)
|
||||
}
|
||||
|
||||
if (state == null) {
|
||||
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()
|
||||
})
|
||||
setFragment(
|
||||
supportFragmentManager, when {
|
||||
intent?.hasExtra(PREFERENCES) == true ->
|
||||
PreferencesFragment()
|
||||
intent?.hasExtra(HISTORY) == true ->
|
||||
HistoryFragment()
|
||||
intent?.hasExtra(ENCODE) == true ->
|
||||
EncodeFragment.newInstance(
|
||||
intent.getStringExtra(ENCODE) ?: ""
|
||||
)
|
||||
intent?.hasExtra(DECODED_TEXT) == true ->
|
||||
DecodeFragment.newInstance(
|
||||
intent.getStringExtra(DECODED_TEXT) ?: "",
|
||||
intent.getSerializableExtra(
|
||||
DECODED_FORMAT
|
||||
) as BarcodeFormat,
|
||||
intent.getByteArrayExtra(DECODED_RAW)
|
||||
)
|
||||
else -> DecodeFragment()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFERENCES = "preferences"
|
||||
private const val HISTORY = "history"
|
||||
private const val ENCODE = "encode"
|
||||
private const val DECODE = "decode"
|
||||
private const val DECODE_FORMAT = "decode_format"
|
||||
private const val DECODED_TEXT = "decoded_text"
|
||||
private const val DECODED_FORMAT = "decoded_format"
|
||||
private const val DECODED_RAW = "decoded_raw"
|
||||
|
||||
fun getPreferencesIntent(context: Context): Intent {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.putExtra(PREFERENCES, true)
|
||||
return intent
|
||||
}
|
||||
|
||||
fun getHistoryIntent(context: Context): Intent {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
@@ -71,10 +92,14 @@ class MainActivity : AppCompatActivity() {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.putExtra(ENCODE, text)
|
||||
if (isExternal) {
|
||||
val flagActivityClearTask =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
} else 0
|
||||
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
|
||||
Intent.FLAG_ACTIVITY_NO_HISTORY or
|
||||
flagActivityClearTask or
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
)
|
||||
}
|
||||
return intent
|
||||
@@ -83,11 +108,15 @@ class MainActivity : AppCompatActivity() {
|
||||
fun getDecodeIntent(
|
||||
context: Context,
|
||||
text: String,
|
||||
format: BarcodeFormat
|
||||
format: BarcodeFormat,
|
||||
raw: ByteArray? = null
|
||||
): Intent {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.putExtra(DECODE, text)
|
||||
intent.putExtra(DECODE_FORMAT, format)
|
||||
intent.putExtra(DECODED_TEXT, text)
|
||||
intent.putExtra(DECODED_FORMAT, format)
|
||||
if (raw != null) {
|
||||
intent.putExtra(DECODED_RAW, raw)
|
||||
}
|
||||
return intent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
package de.markusfisch.android.binaryeye.activity
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.provider.MediaStore
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import com.google.zxing.Result
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.app.setSystemAndToolBarTransparency
|
||||
import de.markusfisch.android.binaryeye.graphics.crop
|
||||
import de.markusfisch.android.binaryeye.graphics.downsizeIfBigger
|
||||
import de.markusfisch.android.binaryeye.widget.CropImageView
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
import java.io.IOException
|
||||
import kotlin.math.min
|
||||
import kotlin.math.max
|
||||
|
||||
class PickActivity : AppCompatActivity() {
|
||||
private val zxing = Zxing()
|
||||
|
||||
private lateinit var cropImageView: CropImageView
|
||||
|
||||
private var returnResult = false
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setContentView(R.layout.activity_pick)
|
||||
initSystemBars(this)
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
|
||||
|
||||
supportFragmentManager.addOnBackStackChangedListener {
|
||||
setSystemAndToolBarTransparency(this@PickActivity)
|
||||
}
|
||||
|
||||
returnResult = intent?.action == "com.google.zxing.client.android.SCAN"
|
||||
|
||||
val bitmap = if (
|
||||
intent?.action == Intent.ACTION_SEND &&
|
||||
intent.type?.startsWith("image/") == true
|
||||
) {
|
||||
loadSentImage(intent)
|
||||
} else if (
|
||||
intent?.action == Intent.ACTION_VIEW &&
|
||||
intent.type?.startsWith("image/") == true
|
||||
) {
|
||||
loadImageToOpen(intent)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (bitmap == null) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.error_no_content,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
val scannedRect = Rect()
|
||||
val scanBounds: () -> Result? = {
|
||||
var result: Result? = null
|
||||
crop(
|
||||
bitmap,
|
||||
cropImageView.normalizedRectInBounds,
|
||||
cropImageView.imageRotation
|
||||
)?.also {
|
||||
scannedRect.set(0, 0, it.width, it.height)
|
||||
result = zxing.decodePositiveNegative(it)
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
cropImageView = findViewById(R.id.image) as CropImageView
|
||||
cropImageView.setImageBitmap(bitmap)
|
||||
cropImageView.onScan = {
|
||||
val rect = Rect()
|
||||
scanBounds()?.also {
|
||||
rect.left = Int.MAX_VALUE
|
||||
rect.top = Int.MAX_VALUE
|
||||
for (rp in it.resultPoints) {
|
||||
val x = rp.x.toInt()
|
||||
val y = rp.y.toInt()
|
||||
rect.left = min(rect.left, x)
|
||||
rect.right = max(rect.right, x)
|
||||
rect.top = min(rect.top, y)
|
||||
rect.bottom = max(rect.bottom, y)
|
||||
}
|
||||
// map result points onto view
|
||||
val bounds = cropImageView.getBoundsRect()
|
||||
val fx = bounds.width() / scannedRect.width().toFloat()
|
||||
val fy = bounds.height() / scannedRect.height().toFloat()
|
||||
rect.set(
|
||||
(rect.left * fx).toInt(),
|
||||
(rect.top * fy).toInt(),
|
||||
(rect.right * fx).toInt(),
|
||||
(rect.bottom * fx).toInt()
|
||||
)
|
||||
rect.offset(bounds.left.toInt(), bounds.top.toInt())
|
||||
}
|
||||
rect
|
||||
}
|
||||
|
||||
findViewById(R.id.scan).setOnClickListener {
|
||||
scanImage(scanBounds())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.activity_pick, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.rotate -> {
|
||||
rotateClockwise()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun rotateClockwise() {
|
||||
cropImageView.imageRotation = cropImageView.imageRotation + 90 % 360
|
||||
}
|
||||
|
||||
private fun loadSentImage(intent: Intent): Bitmap? {
|
||||
val uri = intent.getParcelableExtra<Parcelable>(
|
||||
Intent.EXTRA_STREAM
|
||||
) as? Uri ?: return null
|
||||
return loadImageUri(uri)
|
||||
}
|
||||
|
||||
private fun loadImageToOpen(intent: Intent): Bitmap? {
|
||||
val uri = intent.data ?: return null
|
||||
return loadImageUri(uri)
|
||||
}
|
||||
|
||||
private fun loadImageUri(uri: Uri): Bitmap? = try {
|
||||
downsizeIfBigger(
|
||||
MediaStore.Images.Media.getBitmap(contentResolver, uri),
|
||||
1024
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
null
|
||||
}
|
||||
|
||||
private fun scanImage(result: Result?) {
|
||||
if (result != null) {
|
||||
showResult(this, result, returnResult)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.no_barcode_found,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
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
|
||||
@@ -10,6 +7,8 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CursorAdapter
|
||||
import android.widget.TextView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.data.Database
|
||||
|
||||
class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
CursorAdapter(context, cursor, false) {
|
||||
@@ -33,22 +32,24 @@ class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
cursor: Cursor
|
||||
) {
|
||||
val holder = getViewHolder(view)
|
||||
val content = cursor.getString(contentIndex)
|
||||
holder.timeView.text = cursor.getString(timeIndex)
|
||||
holder.contentView.text = cursor.getString(contentIndex)
|
||||
holder.contentView.text = if (content.isEmpty()) {
|
||||
context.getString(R.string.binary_data)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
holder.formatView.text = cursor.getString(formatIndex)
|
||||
}
|
||||
|
||||
private fun getViewHolder(view: View): ViewHolder {
|
||||
var holder = view.tag as ViewHolder?
|
||||
if (holder == null) {
|
||||
holder = ViewHolder(
|
||||
view.findViewById(R.id.time),
|
||||
view.findViewById(R.id.content),
|
||||
view.findViewById(R.id.format)
|
||||
)
|
||||
view.tag = holder
|
||||
return view.tag as ViewHolder? ?: ViewHolder(
|
||||
view.findViewById(R.id.time),
|
||||
view.findViewById(R.id.content),
|
||||
view.findViewById(R.id.format)
|
||||
).also {
|
||||
view.tag = it
|
||||
}
|
||||
return holder
|
||||
}
|
||||
|
||||
private data class ViewHolder(
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import de.markusfisch.android.binaryeye.data.Database
|
||||
import de.markusfisch.android.binaryeye.preference.Preferences
|
||||
|
||||
import android.app.Application
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
import de.markusfisch.android.binaryeye.preference.Preferences
|
||||
import de.markusfisch.android.binaryeye.repository.DatabaseRepository
|
||||
|
||||
val db = Database()
|
||||
val db = DatabaseRepository()
|
||||
val prefs = Preferences()
|
||||
|
||||
class BinaryEyeApp : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// required to make RenderScript work for Lineage 16.0
|
||||
// possibly because of a system/device bug
|
||||
RenderScript.forceCompat()
|
||||
|
||||
db.open(this)
|
||||
prefs.init(this)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentManager
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.view.View
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
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
|
||||
) = fm.beginTransaction().replace(R.id.content_frame, fragment)
|
||||
|
||||
suspend inline fun View.useVisibility(
|
||||
whileExecuting: Int = View.VISIBLE,
|
||||
otherwise: Int = View.GONE,
|
||||
crossinline block: suspend () -> Unit
|
||||
) {
|
||||
if (visibility == whileExecuting) {
|
||||
return
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
visibility = whileExecuting
|
||||
}
|
||||
try {
|
||||
block()
|
||||
} finally {
|
||||
withContext(Dispatchers.Main) {
|
||||
visibility = otherwise
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <T : Any> alertDialog(
|
||||
context: Context,
|
||||
crossinline build: AlertDialog.Builder.(resume: (T?) -> Unit) -> Unit
|
||||
): T? = withContext(Dispatchers.Main) {
|
||||
suspendCoroutine<T?> { continuation ->
|
||||
AlertDialog.Builder(context).apply {
|
||||
setOnCancelListener {
|
||||
continuation.resume(null)
|
||||
}
|
||||
build(continuation::resume)
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
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,12 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
|
||||
private val nonPrintable = "[\\x00-\\x08\\x0e-\\x1f]".toRegex()
|
||||
|
||||
fun hasNonPrintableCharacters(s: String) = nonPrintable.containsMatchIn(s)
|
||||
|
||||
fun parseAndNormalizeUri(input: String): Uri = Uri.parse(input).let {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) it.normalizeScheme() else it
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.support.annotation.MainThread
|
||||
import android.widget.EditText
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
fun addSuffixIfNotGiven(name: String, suffix: String): String {
|
||||
val trimmed = name.trim()
|
||||
return if (suffix.isNotEmpty() && !trimmed.endsWith(suffix)) {
|
||||
"$trimmed$suffix"
|
||||
} else {
|
||||
trimmed
|
||||
}
|
||||
}
|
||||
|
||||
// Dialogs doesn't have any root layout, need to be inflated with null root
|
||||
@Suppress("InflateParams")
|
||||
@MainThread
|
||||
suspend fun Activity.askForFileName(suffix: String = ""): String? {
|
||||
val view = layoutInflater.inflate(R.layout.dialog_save_file, null)
|
||||
val editText = view.findViewById<EditText>(R.id.file_name)
|
||||
return alertDialog<String>(this as Context) { resume ->
|
||||
setView(view)
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
resume(editText.text.toString())
|
||||
}
|
||||
}?.let { name ->
|
||||
addSuffixIfNotGiven(name, suffix)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun Flow<ByteArray>.writeToFile(name: String): Int {
|
||||
return try {
|
||||
val file = File(
|
||||
Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS
|
||||
),
|
||||
name
|
||||
)
|
||||
if (file.exists()) {
|
||||
return R.string.error_file_exists
|
||||
}
|
||||
collect { bytes ->
|
||||
file.appendBytes(bytes)
|
||||
}
|
||||
R.string.saved_in_downloads
|
||||
} catch (e: IOException) {
|
||||
R.string.error_saving_binary_data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.scanReduce
|
||||
|
||||
fun Flow<ByteArray>.addDelimiter(delimiter: String) = addDelimiter(
|
||||
delimiter.toByteArray(),
|
||||
ByteArray::plus
|
||||
)
|
||||
|
||||
inline fun <T, R> Flow<T>.addDelimiter(
|
||||
delimiter: R,
|
||||
crossinline append: R.(T) -> T
|
||||
): Flow<T> = scanReduce { _, content ->
|
||||
delimiter.append(content)
|
||||
}
|
||||
|
||||
infix fun <T> T.asFlowWith(other: Flow<T>) = flow<T> {
|
||||
emit(this@asFlowWith)
|
||||
emitAll(other)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.pm.PackageManager
|
||||
import android.support.v4.app.ActivityCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
|
||||
fun hasCameraPermission(activity: Activity, requestCode: Int): Boolean {
|
||||
return hasPermission(activity, Manifest.permission.CAMERA, requestCode)
|
||||
}
|
||||
|
||||
fun hasWritePermission(activity: Activity, requestCode: Int = 1): Boolean {
|
||||
return hasPermission(
|
||||
activity,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
requestCode
|
||||
)
|
||||
}
|
||||
|
||||
fun hasPermission(
|
||||
activity: Activity,
|
||||
permission: String,
|
||||
requestCode: Int
|
||||
): Boolean {
|
||||
return if (ContextCompat.checkSelfPermission(activity, permission) !=
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
arrayOf(permission),
|
||||
requestCode
|
||||
)
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,18 @@ package de.markusfisch.android.binaryeye.app
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.support.v4.content.FileProvider
|
||||
import android.widget.Toast
|
||||
import de.markusfisch.android.binaryeye.BuildConfig
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import java.io.File
|
||||
|
||||
fun shareText(context: Context, text: String, type: String = "text/plain") {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.putExtra(Intent.EXTRA_TEXT, text)
|
||||
intent.type = type
|
||||
context.startActivity(intent)
|
||||
execShareIntent(context, intent)
|
||||
}
|
||||
|
||||
fun shareUri(context: Context, uri: Uri, type: String) {
|
||||
@@ -16,5 +22,33 @@ fun shareUri(context: Context, uri: Uri, type: String) {
|
||||
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.type = type
|
||||
context.startActivity(intent)
|
||||
execShareIntent(context, intent)
|
||||
}
|
||||
|
||||
fun execShareIntent(context: Context, intent: Intent) {
|
||||
if (intent.resolveActivity(context.packageManager) != null) {
|
||||
context.startActivity(intent)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.cannot_resolve_action,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun shareFile(context: Context, file: File, type: String) {
|
||||
shareUri(context, getUriForFile(context, file), type)
|
||||
}
|
||||
|
||||
fun getUriForFile(context: Context, file: File): Uri {
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Uri.fromFile(file)
|
||||
} else {
|
||||
FileProvider.getUriForFile(
|
||||
context,
|
||||
BuildConfig.APPLICATION_ID + ".provider",
|
||||
file
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,83 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Build
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v4.view.ViewCompat
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.widget.AbsListView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
val windowInsets = Rect()
|
||||
val systemBarScrollListener = object : AbsListView.OnScrollListener {
|
||||
override fun onScroll(
|
||||
view: AbsListView,
|
||||
firstVisibleItem: Int,
|
||||
visibleItemCount: Int,
|
||||
totalItemCount: Int
|
||||
) {
|
||||
// give Android some time to settle down before running this;
|
||||
// not putting it on the queue makes it only work sometimes
|
||||
view.post {
|
||||
val scrolled = firstVisibleItem > 0 ||
|
||||
(totalItemCount > 0 && view.getChildAt(0).top < 0)
|
||||
val scrollable = if (scrolled) true else totalItemCount > 0 &&
|
||||
view.getChildAt(view.lastVisiblePosition).bottom > view.height
|
||||
setSystemAndToolBarTransparency(view.context, scrolled, scrollable)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onScrollStateChanged(
|
||||
view: AbsListView,
|
||||
scrollState: Int
|
||||
) {
|
||||
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
|
||||
fun setSystemAndToolBarTransparency(
|
||||
context: Context,
|
||||
scrolled: Boolean = false,
|
||||
scrollable: Boolean = false
|
||||
) {
|
||||
val opaqueColor = ContextCompat.getColor(context, R.color.primary)
|
||||
val transparentColor = 0x00000000
|
||||
val topColor = if (scrolled) opaqueColor else transparentColor
|
||||
val bottomColor = if (scrolled || scrollable) opaqueColor else transparentColor
|
||||
val activity = context as AppCompatActivity
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val window = activity.window
|
||||
window.statusBarColor = topColor
|
||||
window.navigationBarColor = bottomColor
|
||||
}
|
||||
|
||||
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
|
||||
activity.supportActionBar?.setBackgroundDrawable(ColorDrawable(topColor))
|
||||
}
|
||||
|
||||
fun getStatusBarHeight(res: Resources): Int {
|
||||
return getIdentifierDimen(res, "status_bar_height")
|
||||
fun initSystemBars(activity: AppCompatActivity) {
|
||||
activity.findViewById(R.id.main_layout)?.also { view ->
|
||||
setWindowInsetListener(view)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
activity.window.decorView.systemUiVisibility =
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
}
|
||||
setSystemAndToolBarTransparency(activity)
|
||||
}
|
||||
|
||||
private fun getIdentifierDimen(res: Resources, name: String): Int {
|
||||
val id = res.getIdentifier(name, "dimen", "android")
|
||||
return if (id > 0) res.getDimensionPixelSize(id) else 0
|
||||
private fun setWindowInsetListener(view: View) {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets ->
|
||||
if (insets.hasSystemWindowInsets()) {
|
||||
val left = insets.systemWindowInsetLeft
|
||||
val top = insets.systemWindowInsetTop
|
||||
val right = insets.systemWindowInsetRight
|
||||
val bottom = insets.systemWindowInsetBottom
|
||||
view.setPadding(left, top, right, bottom)
|
||||
windowInsets.set(left, top, right, bottom)
|
||||
}
|
||||
insets.consumeSystemWindowInsets()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.text.format.DateFormat
|
||||
import de.markusfisch.android.binaryeye.app.hasNonPrintableCharacters
|
||||
|
||||
class Database {
|
||||
private lateinit var db: SQLiteDatabase
|
||||
@@ -29,13 +30,28 @@ class Database {
|
||||
"""SELECT
|
||||
$SCANS_DATETIME,
|
||||
$SCANS_CONTENT,
|
||||
$SCANS_RAW,
|
||||
$SCANS_FORMAT
|
||||
FROM $SCANS
|
||||
WHERE $SCANS_ID = ?
|
||||
""", arrayOf("$id")
|
||||
)
|
||||
|
||||
fun insertScan(timestamp: Long, name: String, code: String): Long {
|
||||
fun hasBinaryData(): Cursor? = db.rawQuery(
|
||||
"""SELECT
|
||||
1
|
||||
FROM $SCANS
|
||||
WHERE $SCANS_RAW IS NOT NULL
|
||||
LIMIT 1
|
||||
""", null
|
||||
)
|
||||
|
||||
fun insertScan(
|
||||
timestamp: Long,
|
||||
content: String,
|
||||
raw: ByteArray?,
|
||||
format: String
|
||||
): Long {
|
||||
val cv = ContentValues()
|
||||
cv.put(
|
||||
SCANS_DATETIME, DateFormat.format(
|
||||
@@ -43,8 +59,13 @@ class Database {
|
||||
timestamp
|
||||
).toString()
|
||||
)
|
||||
cv.put(SCANS_CONTENT, name)
|
||||
cv.put(SCANS_FORMAT, code)
|
||||
if (hasNonPrintableCharacters(content) && raw != null) {
|
||||
cv.put(SCANS_CONTENT, "")
|
||||
cv.put(SCANS_RAW, raw)
|
||||
} else {
|
||||
cv.put(SCANS_CONTENT, content)
|
||||
}
|
||||
cv.put(SCANS_FORMAT, format)
|
||||
return db.insert(SCANS, null, cv)
|
||||
}
|
||||
|
||||
@@ -57,7 +78,7 @@ class Database {
|
||||
}
|
||||
|
||||
private class OpenHelper(context: Context) :
|
||||
SQLiteOpenHelper(context, "history.db", null, 1) {
|
||||
SQLiteOpenHelper(context, "history.db", null, 2) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
createScans(db)
|
||||
}
|
||||
@@ -67,6 +88,9 @@ class Database {
|
||||
oldVersion: Int,
|
||||
newVersion: Int
|
||||
) {
|
||||
if (oldVersion < 2) {
|
||||
addRawColumn(db)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +99,7 @@ class Database {
|
||||
const val SCANS_ID = "_id"
|
||||
const val SCANS_DATETIME = "_datetime"
|
||||
const val SCANS_CONTENT = "content"
|
||||
const val SCANS_RAW = "raw"
|
||||
const val SCANS_FORMAT = "format"
|
||||
|
||||
private fun createScans(db: SQLiteDatabase) {
|
||||
@@ -84,9 +109,14 @@ class Database {
|
||||
$SCANS_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
$SCANS_DATETIME DATETIME NOT NULL,
|
||||
$SCANS_CONTENT TEXT NOT NULL,
|
||||
$SCANS_RAW BLOB,
|
||||
$SCANS_FORMAT TEXT NOT NULL
|
||||
)"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun addRawColumn(db: SQLiteDatabase) {
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_RAW BLOB")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package de.markusfisch.android.binaryeye.data.csv
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.addDelimiter
|
||||
import de.markusfisch.android.binaryeye.app.asFlowWith
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
fun <T> csvBuilder(config: CSVBuilder<T>.() -> Unit): CSVBuilder<T> {
|
||||
return CSVBuilder<T>().apply(config)
|
||||
}
|
||||
|
||||
class CSVBuilder<T> {
|
||||
private val columns = mutableListOf<ColumnBuilder>()
|
||||
|
||||
fun column(config: ColumnBuilder.() -> Unit): ColumnBuilder {
|
||||
return ColumnBuilder().apply(config).also {
|
||||
columns.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
inner class ColumnBuilder {
|
||||
var isBinary = false
|
||||
var name: String? = null
|
||||
internal var gettingBy: ((T) -> ByteArray)? = null
|
||||
|
||||
fun gettingBy(getter: (T) -> ByteArray) {
|
||||
gettingBy = getter
|
||||
}
|
||||
|
||||
fun gettingByString(getter: (T) -> String): Unit = gettingBy {
|
||||
getter(it).toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
fun buildWith(inputs: Flow<T>, delimiter: String): Flow<ByteArray> = flow {
|
||||
val columnsFlow = columns.asFlow()
|
||||
|
||||
val header = columnsFlow.buildHeader(delimiter)
|
||||
|
||||
val contentLines = inputs.buildContent(columnsFlow, delimiter)
|
||||
|
||||
emitAll(header)
|
||||
emit("\n".toByteArray())
|
||||
emitAll(contentLines)
|
||||
}
|
||||
|
||||
private fun Flow<ColumnBuilder>.buildHeader(delimiter: String) = map {
|
||||
it.name?.escaped(delimiter)?.toByteArray()
|
||||
?: throw IllegalStateException("You need to provide a name for csv column")
|
||||
}.addDelimiter(delimiter)
|
||||
|
||||
private fun Flow<T>.buildContent(
|
||||
columnsFlow: Flow<ColumnBuilder>,
|
||||
delimiter: String
|
||||
): Flow<ByteArray> = map { input ->
|
||||
input.getLine(columnsFlow, delimiter)
|
||||
}.addDelimiter("\n".toByteArray()) { other: Flow<ByteArray> ->
|
||||
// this: ByteArray
|
||||
this asFlowWith other
|
||||
}.flattenConcat()
|
||||
|
||||
private fun T.getLine(columnsFlow: Flow<ColumnBuilder>, delimiter: String): Flow<ByteArray> =
|
||||
columnsFlow.map { column ->
|
||||
column.gettingBy?.run {
|
||||
var result = invoke(this@getLine)
|
||||
if (!column.isBinary) {
|
||||
result = String(result).escaped(delimiter).toByteArray()
|
||||
}
|
||||
return@run result
|
||||
} ?: throw IllegalStateException("You need to provide a getter for the value")
|
||||
}.addDelimiter(delimiter)
|
||||
|
||||
private fun Any.escaped(delimiter: String): String {
|
||||
val any = this.toString()
|
||||
return when {
|
||||
any.run { contains('"') || contains('\n') || contains(delimiter) } ->
|
||||
"\"${any.replace("\"", "\"\"").replace(delimiter, "\"$delimiter")}\""
|
||||
else -> any
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,42 @@
|
||||
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.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
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.view.*
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.addSuffixIfNotGiven
|
||||
import de.markusfisch.android.binaryeye.app.hasWritePermission
|
||||
import de.markusfisch.android.binaryeye.app.shareFile
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
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() {
|
||||
private var barcode: Bitmap? = null
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View? {
|
||||
activity.setTitle(R.string.view_barcode)
|
||||
activity?.setTitle(R.string.view_barcode)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_barcode,
|
||||
@@ -41,80 +44,121 @@ class BarcodeFragment : Fragment() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
val args = arguments ?: return view
|
||||
val content = args.getString(CONTENT) ?: return view
|
||||
val format = args.getSerializable(FORMAT) as BarcodeFormat? ?: return view
|
||||
val size = args.getInt(SIZE)
|
||||
try {
|
||||
barcode = Zxing.encodeAsBitmap(content, format, size, size)
|
||||
} catch (e: Exception) {
|
||||
var message = e.message
|
||||
if (message == null || message.isEmpty()) {
|
||||
message = getString(R.string.error_encoding_barcode)
|
||||
}
|
||||
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
|
||||
fragmentManager.popBackStack()
|
||||
return null
|
||||
}
|
||||
view.findViewById<ImageView>(R.id.barcode).setImageBitmap(barcode)
|
||||
view.findViewById<View>(R.id.share).setOnClickListener {
|
||||
val bitmap = barcode
|
||||
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"
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.fragment_barcode, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.save -> {
|
||||
askForFileNameAndSave()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
// dialogs do not have a parent view
|
||||
@SuppressLint("InflateParams")
|
||||
private fun askForFileNameAndSave() {
|
||||
val ac = activity ?: return
|
||||
val view = ac.layoutInflater.inflate(R.layout.dialog_save_file, null)
|
||||
val editText = view.findViewById<EditText>(R.id.file_name)
|
||||
AlertDialog.Builder(ac)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val bitmap = barcode
|
||||
bitmap?.let {
|
||||
saveAsFile(
|
||||
bitmap,
|
||||
addSuffixIfNotGiven(
|
||||
editText.text.toString(),
|
||||
".png"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun saveAsFile(bitmap: Bitmap, path: String) {
|
||||
val ac = activity ?: return
|
||||
if (!hasWritePermission(ac)) {
|
||||
return
|
||||
}
|
||||
GlobalScope.launch {
|
||||
val success = saveBitmap(
|
||||
bitmap,
|
||||
File(
|
||||
Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS
|
||||
),
|
||||
path
|
||||
)
|
||||
)
|
||||
GlobalScope.launch(Main) {
|
||||
Toast.makeText(
|
||||
ac,
|
||||
if (success) {
|
||||
R.string.saved_in_downloads
|
||||
} else {
|
||||
R.string.error_saving_binary_data
|
||||
},
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveBitmap(bitmap: Bitmap): File? {
|
||||
return try {
|
||||
private fun share(bitmap: Bitmap) {
|
||||
GlobalScope.launch {
|
||||
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
|
||||
)
|
||||
val success = saveBitmap(bitmap, file)
|
||||
GlobalScope.launch(Main) {
|
||||
if (success) {
|
||||
shareFile(context, file, "image/png")
|
||||
} else {
|
||||
val ac = activity
|
||||
ac?.let {
|
||||
Toast.makeText(
|
||||
ac,
|
||||
R.string.error_saving_binary_data,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,3 +182,14 @@ class BarcodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveBitmap(bitmap: Bitmap, file: File): Boolean {
|
||||
return try {
|
||||
val fos = FileOutputStream(file)
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos)
|
||||
fos.close()
|
||||
true
|
||||
} catch (e: IOException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,41 @@
|
||||
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.design.widget.FloatingActionButton
|
||||
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.view.*
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.ActionRegistry
|
||||
import de.markusfisch.android.binaryeye.app.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class DecodeFragment : Fragment() {
|
||||
private lateinit var contentView: EditText
|
||||
private lateinit var formatView: TextView
|
||||
private lateinit var hexView: TextView
|
||||
private lateinit var format: BarcodeFormat
|
||||
private lateinit var fab: FloatingActionButton
|
||||
|
||||
private var action = ActionRegistry.DEFAULT_ACTION
|
||||
private var isBinary = false
|
||||
private val content: String
|
||||
get() = contentView.text.toString()
|
||||
|
||||
private val parentJob = Job()
|
||||
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main + parentJob)
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
@@ -41,7 +47,7 @@ class DecodeFragment : Fragment() {
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View {
|
||||
activity.setTitle(R.string.content)
|
||||
activity?.setTitle(R.string.content)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_decode,
|
||||
@@ -49,69 +55,111 @@ class DecodeFragment : Fragment() {
|
||||
false
|
||||
)
|
||||
|
||||
val content = arguments?.getString(CONTENT) ?: ""
|
||||
val inputContent = arguments?.getString(CONTENT) ?: ""
|
||||
isBinary = hasNonPrintableCharacters(inputContent) or inputContent.isEmpty()
|
||||
val raw = arguments?.getByteArray(RAW) ?: inputContent.toByteArray()
|
||||
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())
|
||||
fab = view.findViewById(R.id.open)
|
||||
|
||||
if (!isBinary) {
|
||||
contentView.setText(inputContent)
|
||||
contentView.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
updateViewsAndAction(content.toByteArray())
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
before: Int,
|
||||
count: Int
|
||||
) {
|
||||
}
|
||||
})
|
||||
fab.setOnClickListener {
|
||||
executeAction(content.toByteArray())
|
||||
}
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int
|
||||
) {}
|
||||
override fun onTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
before: Int,
|
||||
count: Int
|
||||
) {}
|
||||
})
|
||||
if (prefs.openImmediately) {
|
||||
executeAction(content.toByteArray())
|
||||
}
|
||||
} else {
|
||||
contentView.setText(R.string.binary_data)
|
||||
contentView.isEnabled = false
|
||||
fab.setImageResource(R.drawable.ic_action_save)
|
||||
fab.setOnClickListener {
|
||||
askForFileNameAndSave(raw)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
updateViewsAndAction(raw)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun updateFormatAndHex(content: String) {
|
||||
private fun updateViewsAndAction(bytes: ByteArray) {
|
||||
val prevAction = action
|
||||
if (!prevAction.canExecuteOn(bytes)) {
|
||||
action = ActionRegistry.getAction(bytes)
|
||||
}
|
||||
formatView.text = resources.getQuantityString(
|
||||
R.plurals.barcode_info,
|
||||
content.length,
|
||||
bytes.size,
|
||||
format.toString(),
|
||||
content.length
|
||||
bytes.size
|
||||
)
|
||||
hexView.text = hexDump(content, 33)
|
||||
hexView.text = hexDump(bytes, 33)
|
||||
if (prevAction !== action) {
|
||||
fab.setImageResource(action.iconResId)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
fab.setOnLongClickListener { v ->
|
||||
Toast.makeText(
|
||||
v.context,
|
||||
action.titleResId,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
true
|
||||
}
|
||||
} else {
|
||||
fab.tooltipText = getString(action.titleResId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.fragment_decode, menu)
|
||||
if (isBinary) {
|
||||
menu.findItem(R.id.copy_to_clipboard).isVisible = false
|
||||
menu.findItem(R.id.create).isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.copy_to_clipboard -> {
|
||||
copyToClipboard(getContent())
|
||||
copyToClipboard(content)
|
||||
true
|
||||
}
|
||||
R.id.open_url -> {
|
||||
openUrl(getContent())
|
||||
R.id.share -> {
|
||||
context?.also { shareText(it, content) }
|
||||
true
|
||||
}
|
||||
R.id.create -> {
|
||||
addFragment(
|
||||
fragmentManager,
|
||||
EncodeFragment.newInstance(getContent(), format)
|
||||
EncodeFragment.newInstance(content, format)
|
||||
)
|
||||
true
|
||||
}
|
||||
@@ -119,10 +167,6 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getContent(): String {
|
||||
return contentView.text.toString()
|
||||
}
|
||||
|
||||
private fun copyToClipboard(text: String) {
|
||||
activity ?: return
|
||||
|
||||
@@ -137,79 +181,91 @@ class DecodeFragment : Fragment() {
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun openUrl(url: String) {
|
||||
if (activity == null || url.isEmpty()) {
|
||||
return
|
||||
private fun executeAction(content: ByteArray) {
|
||||
if (activity != null && content.isNotEmpty()) scope.launch {
|
||||
action.execute(activity, content)
|
||||
}
|
||||
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 {
|
||||
}
|
||||
|
||||
private fun askForFileNameAndSave(raw: ByteArray) {
|
||||
val ac = activity ?: return
|
||||
if (!hasWritePermission(ac)) return
|
||||
scope.launch(Dispatchers.Main) {
|
||||
val name = ac.askForFileName() ?: return@launch
|
||||
val message = flowOf(raw).writeToFile(name)
|
||||
Toast.makeText(
|
||||
activity,
|
||||
R.string.cannot_resolve_action,
|
||||
ac,
|
||||
message,
|
||||
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()
|
||||
override fun onDestroyView() {
|
||||
parentJob.cancel()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONTENT = "content"
|
||||
private const val FORMAT = "format"
|
||||
private const val RAW = "raw"
|
||||
|
||||
fun newInstance(content: String, format: BarcodeFormat): Fragment {
|
||||
fun newInstance(
|
||||
content: String,
|
||||
format: BarcodeFormat,
|
||||
raw: ByteArray? = null
|
||||
): Fragment {
|
||||
val args = Bundle()
|
||||
args.putString(CONTENT, content)
|
||||
args.putSerializable(FORMAT, format)
|
||||
if (raw != null) {
|
||||
args.putByteArray(RAW, raw)
|
||||
}
|
||||
val fragment = DecodeFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hexDump(bytes: ByteArray, charsPerLine: Int): String {
|
||||
if (charsPerLine < 4 || bytes.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
val dump = StringBuilder()
|
||||
val hex = StringBuilder()
|
||||
val ascii = StringBuilder()
|
||||
val itemsPerLine = (charsPerLine - 1) / 4
|
||||
val len = bytes.size
|
||||
var i = 0
|
||||
while (true) {
|
||||
val ord = bytes[i]
|
||||
hex.append(String.format("%02X ", ord))
|
||||
ascii.append(if (ord > 31) ord.toChar() else " ")
|
||||
|
||||
++i
|
||||
|
||||
val posInLine = i % itemsPerLine
|
||||
val atEnd = i >= len
|
||||
var atLineEnd = posInLine == 0
|
||||
if (atEnd && !atLineEnd) {
|
||||
for (j in posInLine until itemsPerLine) {
|
||||
hex.append(" ")
|
||||
}
|
||||
atLineEnd = true
|
||||
}
|
||||
if (atLineEnd) {
|
||||
dump.append(hex.toString())
|
||||
dump.append(" ")
|
||||
dump.append(ascii.toString())
|
||||
dump.append("\n")
|
||||
hex.setLength(0)
|
||||
ascii.setLength(0)
|
||||
}
|
||||
if (atEnd) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return dump.toString()
|
||||
}
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
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
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.*
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.addFragment
|
||||
|
||||
class EncodeFragment : Fragment() {
|
||||
private lateinit var formatView: Spinner
|
||||
@@ -41,8 +34,9 @@ class EncodeFragment : Fragment() {
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View {
|
||||
activity.setTitle(R.string.compose_barcode)
|
||||
): View? {
|
||||
val ac = activity ?: return null
|
||||
ac.setTitle(R.string.compose_barcode)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_encode,
|
||||
@@ -52,7 +46,7 @@ class EncodeFragment : Fragment() {
|
||||
|
||||
formatView = view.findViewById(R.id.format)
|
||||
formatView.adapter = ArrayAdapter<String>(
|
||||
activity,
|
||||
ac,
|
||||
android.R.layout.simple_list_item_1,
|
||||
writers.map { it.name }
|
||||
)
|
||||
@@ -86,11 +80,8 @@ class EncodeFragment : Fragment() {
|
||||
} else {
|
||||
hideSoftKeyboard(contentView)
|
||||
addFragment(
|
||||
fragmentManager, BarcodeFragment.newInstance(
|
||||
content,
|
||||
format,
|
||||
size
|
||||
)
|
||||
fragmentManager,
|
||||
BarcodeFragment.newInstance(content, format, size)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
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.annotation.WorkerThread
|
||||
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.view.*
|
||||
import android.widget.ListView
|
||||
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import android.widget.Toast
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.adapter.ScansAdapter
|
||||
import de.markusfisch.android.binaryeye.app.*
|
||||
import de.markusfisch.android.binaryeye.data.csv.csvBuilder
|
||||
import de.markusfisch.android.binaryeye.repository.DatabaseRepository
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
|
||||
class HistoryFragment : Fragment() {
|
||||
private lateinit var listView: ListView
|
||||
private lateinit var fab: View
|
||||
private lateinit var progressView: View
|
||||
|
||||
private val parentJob = Job()
|
||||
private val scope = CoroutineScope(Dispatchers.IO + parentJob)
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setHasOptionsMenu(true)
|
||||
@@ -42,7 +38,7 @@ class HistoryFragment : Fragment() {
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View {
|
||||
activity.setTitle(R.string.history)
|
||||
activity?.setTitle(R.string.history)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_history,
|
||||
@@ -64,6 +60,7 @@ class HistoryFragment : Fragment() {
|
||||
askToRemoveScan(v.context, id)
|
||||
true
|
||||
}
|
||||
listView.setOnScrollListener(systemBarScrollListener)
|
||||
|
||||
fab = view.findViewById(R.id.share)
|
||||
fab.setOnClickListener { v ->
|
||||
@@ -78,6 +75,7 @@ class HistoryFragment : Fragment() {
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
(listView.adapter as ScansAdapter?)?.changeCursor(null)
|
||||
parentJob.cancel()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -95,6 +93,10 @@ class HistoryFragment : Fragment() {
|
||||
askToRemoveAllScans(context)
|
||||
true
|
||||
}
|
||||
R.id.export_history -> {
|
||||
askToExportToFile(context)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
@@ -109,9 +111,9 @@ class HistoryFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun update(context: Context) {
|
||||
GlobalScope.launch {
|
||||
val cursor = db.getScans()
|
||||
GlobalScope.launch(Main) {
|
||||
scope.launch {
|
||||
val cursor = db.getScansCursor()
|
||||
withContext(Dispatchers.Main) {
|
||||
fab.visibility = if (cursor != null && cursor.count > 0) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
@@ -130,24 +132,19 @@ class HistoryFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
)
|
||||
private fun showScan(id: Long) = db.getScan(id)?.also { scan ->
|
||||
try {
|
||||
addFragment(
|
||||
fragmentManager,
|
||||
DecodeFragment.newInstance(
|
||||
scan.content,
|
||||
BarcodeFormat.valueOf(scan.format),
|
||||
scan.raw
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// shouldn't ever happen
|
||||
}
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// shouldn't ever happen
|
||||
}
|
||||
cursor?.close()
|
||||
}
|
||||
|
||||
private fun askToRemoveScan(context: Context, id: Long) {
|
||||
@@ -174,6 +171,75 @@ class HistoryFragment : Fragment() {
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun askToExportToFile(context: Context) = scope.launch {
|
||||
progressView.useVisibility {
|
||||
if (!hasWritePermission(activity)) {
|
||||
return@useVisibility
|
||||
}
|
||||
val (getBinaries, scans) = getScans {
|
||||
alertDialog(context) { resume ->
|
||||
setTitle(R.string.csv_allow_binary)
|
||||
setMessage(R.string.csv_allow_binary_message)
|
||||
setPositiveButton(R.string.no) { _, _ ->
|
||||
resume(false)
|
||||
}
|
||||
setNegativeButton(android.R.string.yes) { _, _ ->
|
||||
resume(true)
|
||||
}
|
||||
setNeutralButton(android.R.string.cancel) { _, _ ->
|
||||
resume(null)
|
||||
}
|
||||
}
|
||||
} ?: return@useVisibility
|
||||
val delimiters = context.resources.getStringArray(
|
||||
R.array.csv_delimiters_values
|
||||
)
|
||||
val delimiter = alertDialog<String>(context) { resume ->
|
||||
setTitle(R.string.csv_delimiter)
|
||||
setItems(R.array.csv_delimiters_names) { _, which ->
|
||||
resume(delimiters[which])
|
||||
}
|
||||
} ?: return@useVisibility
|
||||
val name = withContext(Dispatchers.Main) {
|
||||
activity.askForFileName(suffix = "csv")
|
||||
} ?: return@useVisibility
|
||||
val csv = scans.toCSV(delimiter, getBinaries)
|
||||
val toastMessage = csv.writeToFile(name)
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
toastMessage,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Flow<DatabaseRepository.Scan>.toCSV(
|
||||
delimiter: String,
|
||||
allowBinary: Boolean
|
||||
): Flow<ByteArray> {
|
||||
return csvBuilder<DatabaseRepository.Scan> {
|
||||
column {
|
||||
name = "DATE"
|
||||
gettingByString { it.timestamp }
|
||||
}
|
||||
column {
|
||||
name = "TYPE"
|
||||
gettingByString { it.format }
|
||||
}
|
||||
column {
|
||||
name = "CONTENT"
|
||||
gettingByString { it.content }
|
||||
}
|
||||
if (allowBinary) column {
|
||||
isBinary = true
|
||||
name = "BINARY_CONTENT"
|
||||
gettingBy { it.raw ?: ByteArray(0) }
|
||||
}
|
||||
}.buildWith(this, delimiter)
|
||||
}
|
||||
|
||||
private fun pickListSeparatorAndShare(context: Context) {
|
||||
val separators = context.resources.getStringArray(
|
||||
R.array.list_separators_values
|
||||
@@ -187,32 +253,32 @@ class HistoryFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun shareScans(separator: String) {
|
||||
if (progressView.visibility == View.VISIBLE) {
|
||||
return
|
||||
}
|
||||
progressView.visibility = View.VISIBLE
|
||||
GlobalScope.launch {
|
||||
val cursor = db.getScans()
|
||||
cursor?.let {
|
||||
scope.launch {
|
||||
progressView.useVisibility {
|
||||
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())
|
||||
getScans { false }?.second?.collect {
|
||||
sb.append(it.content)
|
||||
sb.append(separator)
|
||||
}
|
||||
cursor.close()
|
||||
val text = sb.toString()
|
||||
if (text.isNotEmpty()) {
|
||||
GlobalScope.launch(Main) {
|
||||
progressView.visibility = View.GONE
|
||||
shareText(context, text)
|
||||
}
|
||||
if (text.isNotEmpty()) withContext(Dispatchers.Main) {
|
||||
shareText(context, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private suspend inline fun getScans(
|
||||
crossinline binaryData: suspend () -> Boolean?
|
||||
): Pair<Boolean, Flow<DatabaseRepository.Scan>>? {
|
||||
val getBinaries: Boolean = db.hasBinaryData() && binaryData() ?: return null
|
||||
return getBinaries to db.getScans().mapNotNull {
|
||||
when {
|
||||
it.content.isNotEmpty() -> db.getScan(it.id)
|
||||
getBinaries -> db.getScan(it.id)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v7.widget.SwitchCompat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
|
||||
class PreferencesFragment : Fragment() {
|
||||
private lateinit var openImmediatelySwitch: SwitchCompat
|
||||
private lateinit var useHistorySwitch: SwitchCompat
|
||||
private lateinit var openWithUrlInput: EditText
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View? {
|
||||
activity?.setTitle(R.string.preferences)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_preferences,
|
||||
container,
|
||||
false
|
||||
)
|
||||
|
||||
openImmediatelySwitch = view.findViewById<SwitchCompat>(R.id.open_immediately)
|
||||
if (prefs.openImmediately) {
|
||||
openImmediatelySwitch.toggle()
|
||||
}
|
||||
|
||||
useHistorySwitch = view.findViewById<SwitchCompat>(R.id.use_history)
|
||||
if (prefs.useHistory) {
|
||||
useHistorySwitch.toggle()
|
||||
}
|
||||
|
||||
openWithUrlInput = view.findViewById<EditText>(R.id.open_with_url)
|
||||
openWithUrlInput.setText(prefs.openWithUrl)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
prefs.openImmediately = openImmediatelySwitch.isChecked
|
||||
prefs.useHistory = useHistorySwitch.isChecked
|
||||
prefs.openWithUrl = openWithUrlInput.text.toString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package de.markusfisch.android.binaryeye.graphics
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.RectF
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun downsizeIfBigger(bitmap: Bitmap, maxSize: Int): Bitmap = if (
|
||||
max(bitmap.width, bitmap.height) > maxSize
|
||||
) {
|
||||
val srcWidth = bitmap.width
|
||||
val srcHeight = bitmap.height
|
||||
var newWidth = maxSize
|
||||
var newHeight = maxSize
|
||||
if (srcWidth > srcHeight) {
|
||||
newHeight = (newWidth.toFloat() / srcWidth.toFloat() *
|
||||
srcHeight.toFloat()).roundToInt()
|
||||
} else {
|
||||
newWidth = (newHeight.toFloat() / srcHeight.toFloat() *
|
||||
srcWidth.toFloat()).roundToInt()
|
||||
}
|
||||
Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
|
||||
} else {
|
||||
bitmap
|
||||
}
|
||||
|
||||
fun crop(bitmap: Bitmap, rect: RectF, rotation: Float) = try {
|
||||
val erected = erect(bitmap, rotation)
|
||||
val w = erected.width
|
||||
val h = erected.height
|
||||
Bitmap.createBitmap(
|
||||
erected,
|
||||
(rect.left * w).roundToInt(),
|
||||
(rect.top * h).roundToInt(),
|
||||
(rect.width() * w).roundToInt(),
|
||||
(rect.height() * h).roundToInt()
|
||||
)
|
||||
} catch (e: OutOfMemoryError) {
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
|
||||
private fun erect(bitmap: Bitmap, rotation: Float): Bitmap = if (
|
||||
rotation % 360f != 0f
|
||||
) {
|
||||
val matrix = Matrix()
|
||||
matrix.setRotate(rotation)
|
||||
Bitmap.createBitmap(
|
||||
bitmap,
|
||||
0,
|
||||
0,
|
||||
bitmap.width,
|
||||
bitmap.height,
|
||||
matrix,
|
||||
true
|
||||
)
|
||||
} else {
|
||||
bitmap
|
||||
}
|
||||
@@ -7,11 +7,21 @@ import android.preference.PreferenceManager
|
||||
class Preferences {
|
||||
lateinit var preferences: SharedPreferences
|
||||
|
||||
var openImmediately = false
|
||||
set(value) {
|
||||
setBoolean(OPEN_IMMEDIATELY, value)
|
||||
field = value
|
||||
}
|
||||
var useHistory = false
|
||||
set(value) {
|
||||
setBoolean(USE_HISTORY, value)
|
||||
field = value
|
||||
}
|
||||
var openWithUrl: String = ""
|
||||
set(value) {
|
||||
setString(OPEN_WITH_URL, value)
|
||||
field = value
|
||||
}
|
||||
|
||||
fun init(context: Context) {
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
@@ -19,7 +29,9 @@ class Preferences {
|
||||
}
|
||||
|
||||
fun update() {
|
||||
openImmediately = preferences.getBoolean(OPEN_IMMEDIATELY, openImmediately)
|
||||
useHistory = preferences.getBoolean(USE_HISTORY, useHistory)
|
||||
preferences.getString(OPEN_WITH_URL, openWithUrl)?.also { openWithUrl = it }
|
||||
}
|
||||
|
||||
private fun setBoolean(label: String, value: Boolean) {
|
||||
@@ -28,7 +40,15 @@ class Preferences {
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun setString(label: String, value: String) {
|
||||
val editor = preferences.edit()
|
||||
editor.putString(label, value)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val OPEN_IMMEDIATELY = "open_immediately"
|
||||
const val USE_HISTORY = "use_history"
|
||||
const val OPEN_WITH_URL = "open_with_url"
|
||||
}
|
||||
}
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
package de.markusfisch.android.binaryeye.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import de.markusfisch.android.binaryeye.data.Database
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class DatabaseRepository {
|
||||
private val db = Database()
|
||||
|
||||
fun open(applicationContext: Context) {
|
||||
db.open(applicationContext)
|
||||
}
|
||||
|
||||
fun getScan(id: Long): Scan? = getScan(id) { _, cursor ->
|
||||
val timeIndex = cursor.getColumnIndex(Database.SCANS_DATETIME)
|
||||
val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
|
||||
val rawIndex = cursor.getColumnIndex(Database.SCANS_RAW)
|
||||
val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
|
||||
Scan(
|
||||
id = id,
|
||||
timestamp = cursor.getString(timeIndex),
|
||||
content = cursor.getString(contentIndex),
|
||||
raw = cursor.getBlob(rawIndex),
|
||||
format = cursor.getString(formatIndex)
|
||||
)
|
||||
}
|
||||
|
||||
fun <T> getScan(id: Long, map: (id: Long, Cursor) -> T): T? =
|
||||
db.getScan(id)?.use { map(id, it.apply { moveToFirst() }) }
|
||||
|
||||
fun getScans(): Flow<SimpleScan> = getScans { cursor ->
|
||||
val idIndex = cursor.getColumnIndex(Database.SCANS_ID)
|
||||
val timeIndex = cursor.getColumnIndex(Database.SCANS_DATETIME)
|
||||
val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
|
||||
val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
|
||||
SimpleScan(
|
||||
id = cursor.getLong(idIndex),
|
||||
timestamp = cursor.getString(timeIndex),
|
||||
content = cursor.getString(contentIndex),
|
||||
format = cursor.getString(formatIndex)
|
||||
)
|
||||
}
|
||||
|
||||
fun <T> getScans(map: suspend (Cursor) -> T): Flow<T> = flow {
|
||||
db.getScans()?.use { results ->
|
||||
results.asIterable.forEach {
|
||||
emit(it)
|
||||
}
|
||||
}
|
||||
}.map(map).flowOn(Dispatchers.IO)
|
||||
|
||||
fun getScansCursor(): Cursor? = db.getScans()
|
||||
|
||||
fun hasBinaryData() = db.hasBinaryData()?.use { it.count > 0 } ?: false
|
||||
|
||||
fun insertScan(
|
||||
timestamp: Long,
|
||||
content: String,
|
||||
raw: ByteArray?,
|
||||
format: String
|
||||
): Long = db.insertScan(timestamp, content, raw, format)
|
||||
|
||||
fun removeScan(id: Long) = db.removeScan(id)
|
||||
|
||||
fun removeScans() = db.removeScans()
|
||||
|
||||
data class SimpleScan(
|
||||
val id: Long,
|
||||
val timestamp: String,
|
||||
val content: String,
|
||||
val format: String
|
||||
)
|
||||
|
||||
data class Scan(
|
||||
val id: Long,
|
||||
val timestamp: String,
|
||||
val content: String,
|
||||
val raw: ByteArray?,
|
||||
val format: String
|
||||
) {
|
||||
// Needed to be overwritten manually, as ByteArray is an array and this isn't handled well by Kotlin
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Scan
|
||||
|
||||
if (id != other.id) return false
|
||||
if (timestamp != other.timestamp) return false
|
||||
if (content != other.content) return false
|
||||
if (raw != null) {
|
||||
if (other.raw == null) return false
|
||||
if (!raw.contentEquals(other.raw)) return false
|
||||
} else if (other.raw != null) return false
|
||||
if (format != other.format) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Needs to be overwritten manually, as ByteArray is an array and this
|
||||
// isn't handled well by Kotlin
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + timestamp.hashCode()
|
||||
result = 31 * result + content.hashCode()
|
||||
result = 31 * result + (raw?.contentHashCode() ?: 0)
|
||||
result = 31 * result + format.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val Cursor.asIterable: Iterable<Cursor>
|
||||
get() = object : Iterable<Cursor> {
|
||||
override fun iterator(): Iterator<Cursor> = object : Iterator<Cursor> {
|
||||
override fun hasNext(): Boolean = position + 1 < count
|
||||
override fun next(): Cursor = if (moveToNext()) {
|
||||
this@asIterable
|
||||
} else {
|
||||
throw IllegalStateException("You can't access more items, then there are")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
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
|
||||
import android.support.v8.renderscript.*
|
||||
import de.markusfisch.android.binaryeye.renderscript.ScriptC_rotator
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class Preprocessor(
|
||||
context: Context,
|
||||
@@ -43,8 +39,8 @@ class Preprocessor(
|
||||
)
|
||||
|
||||
val f = .75f
|
||||
var w = Math.round(width * f)
|
||||
var h = Math.round(height * f)
|
||||
var w = (width * f).roundToInt()
|
||||
var h = (height * f).roundToInt()
|
||||
|
||||
resizedType = Type.createXY(
|
||||
rs,
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package de.markusfisch.android.binaryeye.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.util.AttributeSet
|
||||
import de.markusfisch.android.binaryeye.app.setSystemAndToolBarTransparency
|
||||
import de.markusfisch.android.scalingimageview.widget.ScalingImageView
|
||||
|
||||
open class ConfinedScalingImageView : ScalingImageView {
|
||||
constructor(context: Context, attrs: AttributeSet, defStyle: Int) :
|
||||
super(context, attrs, defStyle)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) :
|
||||
this(context, attrs, 0)
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
setSystemAndToolBarTransparency(context, !bounds.contains(mappedRect))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package de.markusfisch.android.binaryeye.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ScrollView
|
||||
import de.markusfisch.android.binaryeye.app.setSystemAndToolBarTransparency
|
||||
|
||||
class ConfinedScrollView : ScrollView {
|
||||
private var scrollable = false
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyle: Int) :
|
||||
super(context, attrs, defStyle)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) :
|
||||
this(context, attrs, 0)
|
||||
|
||||
override fun onLayout(
|
||||
changed: Boolean,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int
|
||||
) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
if (changed) {
|
||||
// give Android some time to settle down before running this;
|
||||
// not putting it on the queue makes it only work sometimes
|
||||
post {
|
||||
getChildAt(0)?.also { child ->
|
||||
scrollable = height < child.height + paddingTop + paddingBottom
|
||||
setSystemAndToolBarTransparency(
|
||||
context,
|
||||
false,
|
||||
scrollable
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onScrollChanged(x: Int, y: Int, oldx: Int, oldy: Int) {
|
||||
super.onScrollChanged(x, y, oldx, oldy)
|
||||
setSystemAndToolBarTransparency(context, y > 0, scrollable)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package de.markusfisch.android.binaryeye.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.util.AttributeSet
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.windowInsets
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class CropImageView(context: Context, attr: AttributeSet) :
|
||||
ConfinedScalingImageView(context, attr) {
|
||||
var onScan: (() -> Rect)? = null
|
||||
|
||||
private val boundsPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val candidatePaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val lastMappedRect = RectF()
|
||||
private val dp = context.resources.displayMetrics.density
|
||||
private val padding: Int = (dp * 24f).roundToInt()
|
||||
private val onScanRunnable = Runnable {
|
||||
onScan?.invoke()?.also {
|
||||
candidateRect = it
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private var candidateRect: Rect? = null
|
||||
|
||||
init {
|
||||
boundsPaint.color = ContextCompat.getColor(context, R.color.crop_bound)
|
||||
boundsPaint.style = Paint.Style.STROKE
|
||||
boundsPaint.strokeWidth = dp * 2f
|
||||
boundsPaint.pathEffect = DashPathEffect(floatArrayOf(dp * 10f, dp * 10f), 0f)
|
||||
candidatePaint.color = ContextCompat.getColor(context, R.color.candidate)
|
||||
candidatePaint.style = Paint.Style.FILL_AND_STROKE
|
||||
candidatePaint.strokeWidth = dp * 3f
|
||||
scaleType = ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
override fun onLayout(
|
||||
changed: Boolean,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int
|
||||
) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
setBoundsWithPadding(
|
||||
left + windowInsets.left,
|
||||
top + windowInsets.top,
|
||||
right - windowInsets.right,
|
||||
bottom - windowInsets.bottom
|
||||
)
|
||||
}
|
||||
|
||||
fun getBoundsRect(): RectF {
|
||||
return bounds
|
||||
}
|
||||
|
||||
private fun setBoundsWithPadding(
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int
|
||||
) {
|
||||
val width = right - left
|
||||
val height = bottom - top
|
||||
val size = if (width < height)
|
||||
width - padding * 2
|
||||
else
|
||||
height - padding * 2
|
||||
val hpad = (width - size) / 2
|
||||
val vpad = (height - size) / 2
|
||||
setBounds(
|
||||
left.toFloat() + hpad,
|
||||
top.toFloat() + vpad,
|
||||
right.toFloat() - hpad,
|
||||
bottom.toFloat() - vpad
|
||||
)
|
||||
center(bounds)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
canvas.drawRect(bounds, boundsPaint)
|
||||
val rc = candidateRect
|
||||
if (rc != null && rc.width() > 0) {
|
||||
canvas.drawRect(rc, candidatePaint)
|
||||
candidateRect = null
|
||||
}
|
||||
val mr = mappedRect ?: return
|
||||
if (mr != lastMappedRect) {
|
||||
removeCallbacks(onScanRunnable)
|
||||
postDelayed(onScanRunnable, 500)
|
||||
lastMappedRect.set(mr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,9 @@
|
||||
package de.markusfisch.android.binaryeye.zxing
|
||||
|
||||
import com.google.zxing.BarcodeFormat
|
||||
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.common.HybridBinarizer
|
||||
|
||||
import android.graphics.Bitmap
|
||||
|
||||
import java.util.Arrays
|
||||
import java.util.EnumMap
|
||||
import java.util.EnumSet
|
||||
import com.google.zxing.*
|
||||
import com.google.zxing.common.HybridBinarizer
|
||||
import java.util.*
|
||||
|
||||
class Zxing {
|
||||
private val multiFormatReader: MultiFormatReader = MultiFormatReader()
|
||||
@@ -27,7 +14,7 @@ class Zxing {
|
||||
)
|
||||
decodeFormats.addAll(
|
||||
EnumSet.copyOf(
|
||||
Arrays.asList(
|
||||
listOf(
|
||||
BarcodeFormat.AZTEC,
|
||||
BarcodeFormat.CODABAR,
|
||||
BarcodeFormat.CODE_39,
|
||||
@@ -71,6 +58,42 @@ class Zxing {
|
||||
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
|
||||
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()
|
||||
@@ -80,23 +103,6 @@ class Zxing {
|
||||
)
|
||||
}
|
||||
|
||||
fun decode(bitmap: Bitmap): Result? {
|
||||
val width = bitmap.width
|
||||
val height = bitmap.height
|
||||
val pixels = IntArray(width * height)
|
||||
if (bitmap.config != Bitmap.Config.ARGB_8888) {
|
||||
bitmap.copy(Bitmap.Config.ARGB_8888, true)
|
||||
} else {
|
||||
bitmap
|
||||
}.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
val source = RGBLuminanceSource(width, height, pixels)
|
||||
val result = decodeLuminanceSource(source)
|
||||
if (result != null) {
|
||||
return result
|
||||
}
|
||||
return decodeLuminanceSource(source.invert())
|
||||
}
|
||||
|
||||
private fun decodeLuminanceSource(source: LuminanceSource): Result? {
|
||||
val bitmap = BinaryBitmap(HybridBinarizer(source))
|
||||
return try {
|
||||
@@ -118,9 +124,14 @@ class Zxing {
|
||||
width: Int,
|
||||
height: Int
|
||||
): Bitmap? {
|
||||
val hints = EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)
|
||||
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||
val result = MultiFormatWriter().encode(
|
||||
text, format,
|
||||
width, height, null
|
||||
text,
|
||||
format,
|
||||
width,
|
||||
height,
|
||||
hints
|
||||
)
|
||||
val w = result.width
|
||||
val h = result.height
|
||||
@@ -128,15 +139,16 @@ class Zxing {
|
||||
var offset = 0
|
||||
for (y in 0 until h) {
|
||||
for (x in 0 until w) {
|
||||
pixels[offset + x] = if (result.get(x, y))
|
||||
pixels[offset + x] = if (result.get(x, y)) {
|
||||
BLACK
|
||||
else
|
||||
} else {
|
||||
WHITE
|
||||
}
|
||||
}
|
||||
offset += w
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||
bitmap.setPixels(pixels, 0, width, 0, 0, w, h)
|
||||
bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
|
||||
return bitmap
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<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>
|
||||
@@ -0,0 +1,6 @@
|
||||
<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,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8l8,5 8,-5v10zM12,11L4,6h16l-8,5z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0"
|
||||
android:width="24dp">
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2L2,14v2z"/>
|
||||
</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="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/>
|
||||
</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="M15.55,5.55L11,1v3.07C7.06,4.56 4,7.92 4,12s3.05,7.44 7,7.93v-2.02c-2.84,-0.48 -5,-2.94 -5,-5.91s2.16,-5.43 5,-5.91L11,10l4.55,-4.45zM19.93,11c-0.17,-1.39 -0.72,-2.73 -1.62,-3.89l-1.42,1.42c0.54,0.75 0.88,1.6 1.02,2.47h2.02zM13,17.9v2.02c1.39,-0.17 2.74,-0.71 3.9,-1.61l-1.44,-1.44c-0.75,0.54 -1.59,0.89 -2.46,1.03zM16.89,15.48l1.42,1.41c0.9,-1.16 1.45,-2.5 1.62,-3.89h-2.02c-0.14,0.87 -0.48,1.72 -1.02,2.48z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp"
|
||||
android:tint="#FFFFFF" android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0" android:width="24dp">
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<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,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM9,11L7,11L7,9h2v2zM13,11h-2L11,9h2v2zM17,11h-2L15,9h2v2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<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.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<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="M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,6c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM18,18L6,18v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M17,12h-5v5h5v-5zM16,1v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2h-1L18,1h-2zM19,19L5,19L5,8h14v11z"/>
|
||||
</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="M1,9l2,2c4.97,-4.97 13.03,-4.97 18,0l2,-2C16.93,2.93 7.08,2.93 1,9zM9,17l3,3 3,-3c-1.65,-1.66 -4.34,-1.66 -6,0zM5,13l2,2c2.76,-2.76 7.24,-2.76 10,0l2,-2C15.14,9.14 8.87,9.14 5,13z"/>
|
||||
</vector>
|
||||
@@ -9,9 +9,7 @@
|
||||
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">
|
||||
android:fitsSystemWindows="true">
|
||||
<include layout="@layout/toolbar"/>
|
||||
<SeekBar
|
||||
android:id="@+id/zoom"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<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:fitsSystemWindows="true">
|
||||
<de.markusfisch.android.binaryeye.widget.CropImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/view_barcode"/>
|
||||
<include layout="@layout/toolbar"/>
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
tools:ignore="UnusedAttribute"
|
||||
android:id="@+id/scan"
|
||||
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/scan_code"
|
||||
android:src="@drawable/ic_action_scan"
|
||||
app:elevation="4dp"/>
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,21 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/save_as_file_name"/>
|
||||
<EditText
|
||||
tools:targetApi="o"
|
||||
android:id="@+id/file_name"
|
||||
android:inputType="text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/file_name"
|
||||
android:importantForAutofill="no"/>
|
||||
</LinearLayout>
|
||||
@@ -4,7 +4,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ImageView
|
||||
<de.markusfisch.android.binaryeye.widget.ConfinedScalingImageView
|
||||
android:id="@+id/barcode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -4,36 +4,36 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ScrollView
|
||||
<de.markusfisch.android.binaryeye.widget.ConfinedScrollView
|
||||
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:hint="@string/content"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textMultiLine"
|
||||
android:typeface="monospace"
|
||||
android:hint="@string/content"
|
||||
android:importantForAutofill="no"/>
|
||||
tools:ignore="UnusedAttribute"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="end|top"
|
||||
android:textSize="14sp"/>
|
||||
android:textSize="14sp" />
|
||||
<TextView
|
||||
android:id="@+id/hex"
|
||||
android:layout_width="match_parent"
|
||||
@@ -45,19 +45,21 @@
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:typeface="monospace"
|
||||
android:textSize="12sp"/>
|
||||
android:textSize="12sp"
|
||||
tools:text="54 65 73 74 20 51 52 20 Test QR\n43 6F 64 65 Code"/>
|
||||
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
||||
</de.markusfisch.android.binaryeye.widget.ConfinedScrollView>
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
tools:ignore="UnusedAttribute"
|
||||
android:id="@+id/share"
|
||||
android:id="@+id/open"
|
||||
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"
|
||||
android:contentDescription="@string/open_url"
|
||||
android:src="@drawable/ic_action_open"
|
||||
app:elevation="4dp"/>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ScrollView
|
||||
<de.markusfisch.android.binaryeye.widget.ConfinedScrollView
|
||||
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"
|
||||
@@ -31,6 +31,7 @@
|
||||
android:layout_marginBottom="8dp"
|
||||
android:gravity="center"/>
|
||||
<SeekBar
|
||||
style="@style/Widget.AppCompat.SeekBar.Discrete"
|
||||
android:id="@+id/size_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -60,4 +61,4 @@
|
||||
android:layout_width="match_parent"
|
||||
android:text="@string/encode"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</de.markusfisch.android.binaryeye.widget.ConfinedScrollView>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<de.markusfisch.android.binaryeye.widget.ConfinedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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="match_parent"
|
||||
android:padding="16dp"
|
||||
android:orientation="vertical">
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/open_immediately"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/open_immediately"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/use_history"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/use_history"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/open_with_url"/>
|
||||
<EditText
|
||||
tools:targetApi="o"
|
||||
android:id="@+id/open_with_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:gravity="start|top"
|
||||
android:typeface="monospace"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:inputType="textUri"
|
||||
android:hint="@string/open_with_url_hint"
|
||||
android:importantForAutofill="no"/>
|
||||
</LinearLayout>
|
||||
</de.markusfisch.android.binaryeye.widget.ConfinedScrollView>
|
||||
@@ -5,4 +5,4 @@
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/primary_translucent"/>
|
||||
android:background="@android:color/transparent"/>
|
||||
|
||||
@@ -11,11 +11,21 @@
|
||||
android:title="@string/compose_barcode"
|
||||
android:icon="@drawable/ic_action_create"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/pick_file"
|
||||
android:title="@string/pick_file"
|
||||
android:icon="@drawable/ic_action_pick_file"
|
||||
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/preferences"
|
||||
android:title="@string/preferences"
|
||||
android:icon="@drawable/ic_action_preferences"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/info"
|
||||
android:title="@string/info"
|
||||
|
||||
@@ -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/rotate"
|
||||
android:title="@string/rotate_image_cw"
|
||||
android:icon="@drawable/ic_action_rotate_clockwise"
|
||||
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/save"
|
||||
android:title="@string/export_to_file"
|
||||
android:icon="@drawable/ic_action_save"
|
||||
material:showAsAction="ifRoom"/>
|
||||
</menu>
|
||||
@@ -7,9 +7,9 @@
|
||||
android:icon="@drawable/ic_action_copy"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/open_url"
|
||||
android:title="@string/open_url"
|
||||
android:icon="@drawable/ic_action_open"
|
||||
android:id="@+id/share"
|
||||
android:title="@string/share"
|
||||
android:icon="@drawable/ic_action_share"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/create"
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
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"/>
|
||||
android:id="@+id/export_history"
|
||||
android:title="@string/export_to_file"
|
||||
android:icon="@drawable/ic_action_save"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/clear"
|
||||
android:title="@string/clear_history"
|
||||
android:icon="@drawable/ic_action_clear"
|
||||
material:showAsAction="ifRoom"/>
|
||||
</menu>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<string name="compose_barcode">Barcode erstellen</string>
|
||||
<string name="decode_barcode">Barcode dekodieren</string>
|
||||
<string name="content">Inhalt</string>
|
||||
<string name="binary_data">(Binäre Daten)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d Zeichen</item>
|
||||
<item quantity="other">%1$s\n%2$d Zeichen</item>
|
||||
@@ -15,17 +16,22 @@
|
||||
<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="pick_search_engine">Suchmaschine auswählen</string>
|
||||
<string name="format">Format</string>
|
||||
<string name="size">Größe in Pixeln</string>
|
||||
<string name="size">Größe in Pixel</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Gib den Inhalt hier ein</string>
|
||||
<string name="encode">KODIEREN</string>
|
||||
<string name="error_no_content">Fehlender Inhalt</string>
|
||||
<string name="error_encoding_barcode">Barcode kann nicht generiert werden</string>
|
||||
<string name="view_barcode">Barcode ansehen</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Kamera wechseln</string>
|
||||
<string name="history">Gespeicherte Codes</string>
|
||||
<string name="preferences">Einstellungen</string>
|
||||
<string name="open_immediately">Inspektion überspringen und Inhalte sofort öffnen</string>
|
||||
<string name="use_history">Codes speichern</string>
|
||||
<string name="open_with_url">URL zum Öffnen unbekannter Daten</string>
|
||||
<string name="really_remove_scan">Code wirklich entfernen?</string>
|
||||
<string name="really_remove_all_scans">Alle Codes entfernen?</string>
|
||||
<string name="clear_history">Gespeicherte Codes löschen</string>
|
||||
@@ -33,4 +39,35 @@
|
||||
<string name="pick_list_separator">Wie sollen die Einträge getrennt werden?</string>
|
||||
<string name="separator_line_break">Zeilenumbruch</string>
|
||||
<string name="separator_ruler">Trennstrich</string>
|
||||
<string name="save_as_file_name">Speichern in Downloads als?</string>
|
||||
<string name="file_name">Dateiname</string>
|
||||
<string name="error_saving_binary_data">Datei konnte nicht gespeichert werden</string>
|
||||
<string name="error_file_exists">Datei existiert bereits</string>
|
||||
<string name="connect_to_wifi">Mit WiFi verbinden</string>
|
||||
<string name="wifi_config_failed">Konnte WiFi nicht konfigurieren</string>
|
||||
<string name="wifi_added">WiFi hinzugefügt</string>
|
||||
<string name="sms_send">SMS senden</string>
|
||||
<string name="sms_error">SMS konnte nicht gesendet werden</string>
|
||||
<string name="tel_dial">Telefonnummer wählen</string>
|
||||
<string name="tel_error">Konnte Telefonnummer nicht wählen</string>
|
||||
<string name="mail_send">E-Mail senden</string>
|
||||
<string name="mail_error">Konnte E-Mail nicht senden</string>
|
||||
<string name="vcard_add">Zu den Kontakten hinzufügen</string>
|
||||
<string name="vcard_failed">Konnte nicht zu den Kontakten hinzufügen</string>
|
||||
<string name="vevent_add">Zum Kalender hinzufügen</string>
|
||||
<string name="vevent_failed">Konnte nicht zum Kalender hinzufügen</string>
|
||||
<string name="otpauth_add">2FA hinzufügen</string>
|
||||
<string name="otpauth_error">Konnte 2FA nicht hinzufügen</string>
|
||||
<string name="search_web">Suche im Internet</string>
|
||||
<string name="export_to_file">In Datei exportieren</string>
|
||||
<string name="csv_delimiter">Welches Trennzeichen soll genutzt werden (CSV-Datei)?</string>
|
||||
<string name="delimiter_comma">Komma</string>
|
||||
<string name="delimiter_semicolon">Semikolon</string>
|
||||
<string name="csv_allow_binary">Binäre Daten in Ausgabedatei erlauben?</string>
|
||||
<string name="no">Nein</string>
|
||||
<string name="csv_allow_binary_message">Dies sollte wahrscheinlich nicht erlabubt weden, da dies das Dateiformat bricht und damit die Datei wahrscheinlich nicht mehr von Textverarbeitungsprogrammen geöffnet werden kann.</string>
|
||||
<string name="saved_in_downloads">In den Downloads gespeichert</string>
|
||||
<string name="rotate_image_cw">Bild im Uhrzeigersinn rotieren</string>
|
||||
<string name="pick_file">Bilddatei auswählen</string>
|
||||
<string name="pick_code_to_scan">Code im Bild auswählen</string>
|
||||
</resources>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<string name="compose_barcode">Créer un code-barres</string>
|
||||
<string name="decode_barcode">Décoder le code-barres</string>
|
||||
<string name="content">Contenu</string>
|
||||
<string name="binary_data">(binary data)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d character</item>
|
||||
<item quantity="other">%1$s\n%2$d characters</item>
|
||||
@@ -15,17 +16,22 @@
|
||||
<string name="put_into_clipboard">Mis dans le presse-papiers</string>
|
||||
<string name="open_url">Ouvrir l\'url</string>
|
||||
<string name="cannot_resolve_action">Aucune application ne peut ouvrir ça</string>
|
||||
<string name="pick_search_engine">Pick a search engine</string>
|
||||
<string name="format">Format</string>
|
||||
<string name="size">Taille en pixels</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Entrez votre contenu ici</string>
|
||||
<string name="encode">ENCODER</string>
|
||||
<string name="error_no_content">Contenu manquant</string>
|
||||
<string name="error_encoding_barcode">Barcode cannot be generated</string>
|
||||
<string name="view_barcode">Voir le code barre</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Changer de camera</string>
|
||||
<string name="history">Historique</string>
|
||||
<string name="preferences">Preferences</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="use_history">Sauvegarder l\'histoire des scans</string>
|
||||
<string name="open_with_url">Open unknown data with URL</string>
|
||||
<string name="really_remove_scan">Supprimer le scan ?</string>
|
||||
<string name="really_remove_all_scans">Supprimer tous les scans ?</string>
|
||||
<string name="clear_history">Clear history</string>
|
||||
@@ -33,4 +39,35 @@
|
||||
<string name="pick_list_separator">How to separate list items?</string>
|
||||
<string name="separator_line_break">Line break</string>
|
||||
<string name="separator_ruler">Ruler</string>
|
||||
<string name="save_as_file_name">Save in Downloads as file?</string>
|
||||
<string name="file_name">File name</string>
|
||||
<string name="error_saving_binary_data">Cannot save file</string>
|
||||
<string name="error_file_exists">File already exists</string>
|
||||
<string name="connect_to_wifi">Connect to WiFi</string>
|
||||
<string name="wifi_config_failed">Could not configure WiFi</string>
|
||||
<string name="wifi_added">WiFi added</string>
|
||||
<string name="sms_send">Send SMS</string>
|
||||
<string name="sms_error">Could not send SMS</string>
|
||||
<string name="tel_dial">Dial number</string>
|
||||
<string name="tel_error">Could not dial number</string>
|
||||
<string name="mail_send">Send mail</string>
|
||||
<string name="mail_error">Could not send mail</string>
|
||||
<string name="vcard_add">Add to contacts</string>
|
||||
<string name="vcard_failed">Could not add to contacts</string>
|
||||
<string name="vevent_add">Add to calendar</string>
|
||||
<string name="vevent_failed">Could not add to calendar</string>
|
||||
<string name="otpauth_add">Add 2FA</string>
|
||||
<string name="otpauth_error">Could not add 2FA</string>
|
||||
<string name="search_web">Search the web</string>
|
||||
<string name="export_to_file">Export to file</string>
|
||||
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
|
||||
<string name="delimiter_comma">Comma</string>
|
||||
<string name="delimiter_semicolon">Semicolon</string>
|
||||
<string name="csv_allow_binary">Allow binary data in output file?</string>
|
||||
<string name="no">No</string>
|
||||
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
|
||||
<string name="saved_in_downloads">Saved in downloads</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
</resources>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<string name="compose_barcode">Vonalkód létrehozása</string>
|
||||
<string name="decode_barcode">Vonalkód visszafejtése</string>
|
||||
<string name="content">Tartalom</string>
|
||||
<string name="binary_data">(bináris adat)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d karakter</item>
|
||||
<item quantity="other">%1$s\n%2$d karakter</item>
|
||||
@@ -15,22 +16,58 @@
|
||||
<string name="put_into_clipboard">Vágólapra helyezés</string>
|
||||
<string name="open_url">URL megnyitása</string>
|
||||
<string name="cannot_resolve_action">Nincs alkalmazás, amely meg tudná nyitni azt</string>
|
||||
<string name="pick_search_engine">Válasszon keresőmotort</string>
|
||||
<string name="format">Formátum</string>
|
||||
<string name="size">Méret képpontban</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Itt adja meg a tartalmat</string>
|
||||
<string name="encode">KÓDOLÁS</string>
|
||||
<string name="error_no_content">Hiányzó tartalom</string>
|
||||
<string name="error_encoding_barcode">A vonalkódot nem lehet előállítani</string>
|
||||
<string name="view_barcode">Vonalkód megtekintése</string>
|
||||
<string name="info">Információ</string>
|
||||
<string name="switch_camera">Kamera átkapcsolása</string>
|
||||
<string name="history">Előzmények</string>
|
||||
<string name="preferences">Beállítások</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="use_history">Beolvasási előzmények mentése</string>
|
||||
<string name="open_with_url">Ismeretlen adat megnyitása URL-lel</string>
|
||||
<string name="really_remove_scan">Valóban eltávolítja a beolvasást?</string>
|
||||
<string name="really_remove_all_scans">Valóban eltávolítja az összes beolvasását?</string>
|
||||
<string name="clear_history">Előzmények törlése</string>
|
||||
<string name="no_barcode_found">Nem található vonalkód</string>
|
||||
<string name="pick_list_separator">How to separate list items?</string>
|
||||
<string name="separator_line_break">Line break</string>
|
||||
<string name="separator_ruler">Ruler</string>
|
||||
<string name="pick_list_separator">Hogyan kell elválasztani a listaelemeket?</string>
|
||||
<string name="separator_line_break">Sortörés</string>
|
||||
<string name="separator_ruler">Vonalzó</string>
|
||||
<string name="save_as_file_name">Menti fájlként?</string>
|
||||
<string name="file_name">Fájlnév</string>
|
||||
<string name="error_saving_binary_data">Nem lehet elmenteni a fájlt</string>
|
||||
<string name="error_file_exists">A fájl már létezik</string>
|
||||
<string name="connect_to_wifi">Kapcsolódás WiFi-hez</string>
|
||||
<string name="wifi_config_failed">Nem sikerült beállítani a WiFi-t</string>
|
||||
<string name="wifi_added">WiFi hozzáadva</string>
|
||||
<string name="sms_send">SMS küldése</string>
|
||||
<string name="sms_error">Nem sikerült elküldeni az SMS-t</string>
|
||||
<string name="tel_dial">Szám tárcsázása</string>
|
||||
<string name="tel_error">Nem sikerült tárcsázni a számot</string>
|
||||
<string name="mail_send">Levél küldése</string>
|
||||
<string name="mail_error">Nem sikerült elküldeni a levelet</string>
|
||||
<string name="vcard_add">Hozzáadás a címjegyzékhez</string>
|
||||
<string name="vcard_failed">Nem sikerült hozzáadni a címjegyzékhez</string>
|
||||
<string name="vevent_add">Hozzáadás a naptárhoz</string>
|
||||
<string name="vevent_failed">Nem sikerült hozzáadni a naptárhoz</string>
|
||||
<string name="otpauth_add">Kétlépcsős hitelesítés hozzáadása</string>
|
||||
<string name="otpauth_error">Nem sikerült hozzáadni a kétlépcsős hitelesítést</string>
|
||||
<string name="search_web">Keresés a weben</string>
|
||||
<string name="export_to_file">Exportálás fájlba</string>
|
||||
<string name="csv_delimiter">Milyen mezőelválasztót kell használni (CSV-fájl)?</string>
|
||||
<string name="delimiter_comma">Vessző</string>
|
||||
<string name="delimiter_semicolon">Pontosvessző</string>
|
||||
<string name="csv_allow_binary">Engedélyezi a bináris adatot a kimeneti fájlban?</string>
|
||||
<string name="no">Nem</string>
|
||||
<string name="csv_allow_binary_message">Valószínűleg ezt nem szeretné engedélyezni, mivel tönkre fogja tenni a fájlformátumot, és a szövegszerkesztők talán nem fogják tudni többé olvasni.</string>
|
||||
<string name="saved_in_downloads">Elmentve a letöltésekbe</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Aplikasi tidak akan bisa bekerja tanpa akses kamera. Selamat tinggal.</string>
|
||||
<string name="camera_error">Tidak bisa mengakses kamera, silakan coba lagi</string>
|
||||
<string name="scan_code">Pindai kode</string>
|
||||
<string name="compose_barcode">Buat barcode</string>
|
||||
<string name="decode_barcode">Dekode barcode</string>
|
||||
<string name="content">Konten</string>
|
||||
<string name="binary_data">(data binari)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d karakter</item>
|
||||
<item quantity="other">%1$s\n%2$d karakter</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">Nyala/Matikan blitz</string>
|
||||
<string name="share">Bagikan</string>
|
||||
<string name="copy_to_clipboard">Salin ke papan klip</string>
|
||||
<string name="put_into_clipboard">Simpan ke papan klip</string>
|
||||
<string name="open_url">Buka url</string>
|
||||
<string name="cannot_resolve_action">Tidak ada aplikasi yang bisa membukanya</string>
|
||||
<string name="pick_search_engine">Pilih mesin pencari</string>
|
||||
<string name="format">Format</string>
|
||||
<string name="size">Ukuran dalam piksel</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Masukkan konten di sini</string>
|
||||
<string name="encode">ENKODE</string>
|
||||
<string name="error_no_content">Tidak ada konten</string>
|
||||
<string name="error_encoding_barcode">Barcode tidak bisa dibuat</string>
|
||||
<string name="view_barcode">Lihat barcode</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Ganti kamera</string>
|
||||
<string name="history">Riwayat</string>
|
||||
<string name="preferences">Preferensi</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="use_history">Simpan riwayat pemindaian</string>
|
||||
<string name="open_with_url">Buka data tak dikenal dengan URL</string>
|
||||
<string name="really_remove_scan">Yakin menghapus pemindaian?</string>
|
||||
<string name="really_remove_all_scans">Yakin menghapus semua pemindaian?</string>
|
||||
<string name="clear_history">Hapus riwayat</string>
|
||||
<string name="no_barcode_found">Tidak ada barcode yang ditemukan</string>
|
||||
<string name="pick_list_separator">Pilih pemisah daftar item?</string>
|
||||
<string name="separator_line_break">Baris baru</string>
|
||||
<string name="separator_ruler">Penggaris</string>
|
||||
<string name="save_as_file_name">Simpan sebagai berkas?</string>
|
||||
<string name="file_name">Nama berkas</string>
|
||||
<string name="error_saving_binary_data">Tidak bisa menyimpan berkas</string>
|
||||
<string name="error_file_exists">Berkas sudah ada</string>
|
||||
<string name="connect_to_wifi">Sambungkan ke WiFi</string>
|
||||
<string name="wifi_config_failed">Tidak bisa mengelola WiFi</string>
|
||||
<string name="wifi_added">WiFi ditambahkan</string>
|
||||
<string name="sms_send">Kirim SMS</string>
|
||||
<string name="sms_error">Tidak bisa mengirim SMS</string>
|
||||
<string name="tel_dial">Panggil nomor</string>
|
||||
<string name="tel_error">Tidak bisa memanggil nomor</string>
|
||||
<string name="mail_send">Kirim surel</string>
|
||||
<string name="mail_error">Tidak bisa mengirim surel</string>
|
||||
<string name="vcard_add">Tambahkan ke kontak</string>
|
||||
<string name="vcard_failed">Tidak bisa menambahkan ke kontak</string>
|
||||
<string name="vevent_add">Tambahkan ke kalender</string>
|
||||
<string name="vevent_failed">Tidak bisa menambahkan ke kalender</string>
|
||||
<string name="otpauth_add">Tambahkan 2FA</string>
|
||||
<string name="otpauth_error">Tidak bisa menambahkan 2FA</string>
|
||||
<string name="search_web">Cari secara daring</string>
|
||||
<string name="export_to_file">Ekspor ke berkas</string>
|
||||
<string name="csv_delimiter">Karakter pemisah apa yang harus digunakan (Berkas-CSV)?</string>
|
||||
<string name="delimiter_comma">Koma</string>
|
||||
<string name="delimiter_semicolon">Titik koma</string>
|
||||
<string name="csv_allow_binary">Izinkan data binari di dalam berkas keluaran?</string>
|
||||
<string name="no">Tidak</string>
|
||||
<string name="csv_allow_binary_message">Anda sebaiknya tidak mengizinkannya, karena akan merusak format berkas dan kemungkinan tidak bisa lagi dibaca oleh aplikasi penyunting teks.</string>
|
||||
<string name="saved_in_downloads">Simpan di dalam unduhan</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,73 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Quest\'app non ha alcuna utilità senza accesso alla camera. Addio.</string>
|
||||
<string name="camera_error">Impossibile accedere alla camera. Riprovare</string>
|
||||
<string name="scan_code">Codice</string>
|
||||
<string name="compose_barcode">Crea codice a barre</string>
|
||||
<string name="decode_barcode">Decodifica codice a barre</string>
|
||||
<string name="content">Contenuto</string>
|
||||
<string name="binary_data">(dati binari)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d carattere</item>
|
||||
<item quantity="other">%1$s\n%2$d caratteri</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">Flash</string>
|
||||
<string name="share">Condividi</string>
|
||||
<string name="copy_to_clipboard">Copia negli appunti</string>
|
||||
<string name="put_into_clipboard">Metti negli appunti</string>
|
||||
<string name="open_url">Apri URL</string>
|
||||
<string name="cannot_resolve_action">Nessuna app può aprirlo</string>
|
||||
<string name="pick_search_engine">Scegli un motore di ricerca</string>
|
||||
<string name="format">Formato</string>
|
||||
<string name="size">Dimensioni in pixel</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Contenuto input qui</string>
|
||||
<string name="encode">CODIFICA</string>
|
||||
<string name="error_no_content">Contenuto mancante</string>
|
||||
<string name="error_encoding_barcode">Il codice a barre non può essere generato</string>
|
||||
<string name="view_barcode">Vedi codice a barre</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Cambia camera</string>
|
||||
<string name="history">Cronologia</string>
|
||||
<string name="preferences">Preferences</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="use_history">Salva cronologia delle scansioni</string>
|
||||
<string name="open_with_url">Apri dati sconosciuti con URL</string>
|
||||
<string name="really_remove_scan">Vuoi veramente rimuovere la scansione?</string>
|
||||
<string name="really_remove_all_scans">Vuoi veramente rimuovere tutte le scansioni?</string>
|
||||
<string name="clear_history">Pulisci cronologia</string>
|
||||
<string name="no_barcode_found">Nessun codice a barre trovato</string>
|
||||
<string name="pick_list_separator">Come separare elementi di una lista?</string>
|
||||
<string name="separator_line_break">Interruzione di linea</string>
|
||||
<string name="separator_ruler">Righello</string>
|
||||
<string name="save_as_file_name">Salvare come file?</string>
|
||||
<string name="file_name">Nome del file</string>
|
||||
<string name="error_saving_binary_data">Impossibile salvare il file</string>
|
||||
<string name="error_file_exists">Il File esiste già</string>
|
||||
<string name="connect_to_wifi">Connecti al WiFi</string>
|
||||
<string name="wifi_config_failed">Impossibile configurare il WiFi</string>
|
||||
<string name="wifi_added">WiFi aggiunto</string>
|
||||
<string name="sms_send">Invia SMS</string>
|
||||
<string name="sms_error">Impossibile inviare SMS</string>
|
||||
<string name="tel_dial">Componi numero</string>
|
||||
<string name="tel_error">Impossibile comporre il numero</string>
|
||||
<string name="mail_send">Invia mail</string>
|
||||
<string name="mail_error">Impossibile inviare la mail</string>
|
||||
<string name="vcard_add">Aggiungi ai contatti</string>
|
||||
<string name="vcard_failed">Impossibile aggiungere ai contatti</string>
|
||||
<string name="vevent_add">Aggiungi al calendario</string>
|
||||
<string name="vevent_failed">Impossibile aggiungere al calendario</string>
|
||||
<string name="otpauth_add">Aggiungi 2FA</string>
|
||||
<string name="otpauth_error">Impossibile aggiungre il 2FA</string>
|
||||
<string name="search_web">Cerca nel web</string>
|
||||
<string name="export_to_file">Esporta su file</string>
|
||||
<string name="csv_delimiter">Quale delimitatore usare (File-CSV)?</string>
|
||||
<string name="delimiter_comma">Virgola</string>
|
||||
<string name="delimiter_semicolon">Punto e virgola</string>
|
||||
<string name="csv_allow_binary">Permetti dati binari nel file di output?</string>
|
||||
<string name="no">No</string>
|
||||
<string name="csv_allow_binary_message">È consigliato non permetterlo, perché rompe il formato del file e probabilmente lo renderà non più leggibile dagli editor di testo.</string>
|
||||
<string name="saved_in_downloads">Salvato nei download</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
</resources>
|
||||
@@ -1,36 +1,73 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Deze app kan niet werken zonder cameratoegang. Tot ziens.</string>
|
||||
<string name="camera_error">Kan camera niet openen; probeer het opnieuw.</string>
|
||||
<string name="scan_code">Scan een scode</string>
|
||||
<string name="scan_code">Scan een code</string>
|
||||
<string name="compose_barcode">Scan een barcode</string>
|
||||
<string name="decode_barcode">Decode barcode</string>
|
||||
<string name="decode_barcode">Barcode decoderen</string>
|
||||
<string name="content">Inhoud</string>
|
||||
<string name="binary_data">(binaire data)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d character</item>
|
||||
<item quantity="other">%1$s\n%2$d characters</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">Toggle flash</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="toggle_flash">Flitslicht schakelen</string>
|
||||
<string name="share">Delen</string>
|
||||
<string name="copy_to_clipboard">Kopiëren naar klembord</string>
|
||||
<string name="put_into_clipboard">Kopieer de inhoud naar het klembord</string>
|
||||
<string name="open_url">URL openen</string>
|
||||
<string name="cannot_resolve_action">Er is geen app aangetroffen die dit kan openen</string>
|
||||
<string name="pick_search_engine">Een zoekmachine kiezen</string>
|
||||
<string name="format">Formaat</string>
|
||||
<string name="size">Grootte, in pixels</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Voer hier de inhoud in</string>
|
||||
<string name="encode">CODEREN</string>
|
||||
<string name="error_no_content">Inhoud ontbreekt</string>
|
||||
<string name="error_encoding_barcode">Barcode kan niet gegenereerd worden</string>
|
||||
<string name="view_barcode">Barcode bekijken</string>
|
||||
<string name="info">Informatie</string>
|
||||
<string name="switch_camera">Camera wisselen</string>
|
||||
<string name="history">History</string>
|
||||
<string name="use_history">Save scan history</string>
|
||||
<string name="really_remove_scan">Really remove scan?</string>
|
||||
<string name="really_remove_all_scans">Really remove all scans?</string>
|
||||
<string name="clear_history">Clear history</string>
|
||||
<string name="no_barcode_found">No barcode found</string>
|
||||
<string name="pick_list_separator">How to separate list items?</string>
|
||||
<string name="separator_line_break">Line break</string>
|
||||
<string name="separator_ruler">Ruler</string>
|
||||
<string name="history">Geschiedenis</string>
|
||||
<string name="preferences">Voorkeuren</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="use_history">Scan-geschiedenis behouden</string>
|
||||
<string name="really_remove_scan">Scan echt verwijderen?</string>
|
||||
<string name="really_remove_all_scans">Echt alle scans verwijderen?</string>
|
||||
<string name="clear_history">Geschiedenis wissen</string>
|
||||
<string name="no_barcode_found">Geen barcode gevonden</string>
|
||||
<string name="separator_line_break">Nieuwe regel</string>
|
||||
<string name="separator_ruler">Horizontale streep</string>
|
||||
<string name="save_as_file_name">Als bestand opslaan?</string>
|
||||
<string name="file_name">Naam van het bestand</string>
|
||||
<string name="error_file_exists">Bestand bestaat al</string>
|
||||
<string name="connect_to_wifi">Met WiFi verbinden</string>
|
||||
<string name="wifi_config_failed">Kon WiFi niet configureren</string>
|
||||
<string name="wifi_added">WiFi toegevoegd</string>
|
||||
<string name="sms_send">SMS versturen</string>
|
||||
<string name="sms_error">Kon SMS niet versturen</string>
|
||||
<string name="tel_dial">Nummer bellen</string>
|
||||
<string name="tel_error">Kon nummer niet bellen</string>
|
||||
<string name="mail_send">Mail versturen</string>
|
||||
<string name="mail_error">Kon mail niet versturen</string>
|
||||
<string name="vcard_add">Aan contacten toevoegen</string>
|
||||
<string name="vcard_failed">Kon niet aan contacten toevoegen</string>
|
||||
<string name="vevent_add">Aan kalender toevoegen</string>
|
||||
<string name="otpauth_add">2FA toevoegen</string>
|
||||
<string name="otpauth_error">2FA kon niet toegevoegd worden</string>
|
||||
<string name="search_web">Het net doorzoeken</string>
|
||||
<string name="export_to_file">Naar bestand exporteren</string>
|
||||
<string name="csv_delimiter">Wat voor separator wil je gebruiken (CSV-bestand)?</string>
|
||||
<string name="delimiter_comma">Komma</string>
|
||||
<string name="delimiter_semicolon">Kommapunt</string>
|
||||
<string name="csv_allow_binary">Binaire data in outputbestand toestaan?</string>
|
||||
<string name="no">Nee</string>
|
||||
<string name="csv_allow_binary_message">Waarschijnlijk wil je dit niet toestaan omdat daardoor het bestandsformaat breekt en misschien niet meer toor teksteditoren leesbaar is.</string>
|
||||
<string name="error_saving_binary_data">Kon bestand niet opslaan</string>
|
||||
<string name="open_with_url">Niet herkende data met URL openen</string>
|
||||
<string name="pick_list_separator">Hoe de lijstonderdelen te separeren?</string>
|
||||
<string name="saved_in_downloads">In downloads opgeslagen</string>
|
||||
<string name="vevent_failed">Kon niet aan kalender toevoegen</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Esse aplicativo não faz sentido de uso sem acesso para a câmera. Adeus.</string>
|
||||
<string name="camera_error">Sem acesso a câmera, por favor tente novamente</string>
|
||||
<string name="scan_code">Escaneie o código de barras</string>
|
||||
<string name="compose_barcode">Compor código de barras</string>
|
||||
<string name="decode_barcode">Decodificar código de barras</string>
|
||||
<string name="content">Conteúdo</string>
|
||||
<string name="binary_data">(binários)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d cacractere</item>
|
||||
<item quantity="other">%1$s\n%2$d caracteres</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">Ativar lanterna</string>
|
||||
<string name="share">Compartilhar</string>
|
||||
<string name="copy_to_clipboard">Copiar para área de transferência</string>
|
||||
<string name="put_into_clipboard">Colocar na área de transferência</string>
|
||||
<string name="open_url">Abrir url</string>
|
||||
<string name="cannot_resolve_action">Nenhuma aplicativo pode abrir isso</string>
|
||||
<string name="pick_search_engine">Escolha um mecanismo de busca</string>
|
||||
<string name="format">Formatar</string>
|
||||
<string name="size">Tamanho em pixels</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Insira o conteúdo aqui</string>
|
||||
<string name="encode">CODIFICAR</string>
|
||||
<string name="error_no_content">Conteúdo não disponível</string>
|
||||
<string name="error_encoding_barcode">Não foi possível gerar o código de barras</string>
|
||||
<string name="view_barcode">Ver código de barras</string>
|
||||
<string name="info">Informações</string>
|
||||
<string name="switch_camera">Trocar câmera</string>
|
||||
<string name="history">Histórico</string>
|
||||
<string name="preferences">Preferências</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="use_history">Salvar histórico de escaneamento</string>
|
||||
<string name="open_with_url">Abrir dados desconhecidos com URL</string>
|
||||
<string name="really_remove_scan">Realmente deseja remover o escaneamento?</string>
|
||||
<string name="really_remove_all_scans">Realmente deseja remover todos escaneamentos?</string>
|
||||
<string name="clear_history">Limpar histórico</string>
|
||||
<string name="no_barcode_found">Código de barras não encontrado</string>
|
||||
<string name="pick_list_separator">Como separar itens da lista?</string>
|
||||
<string name="separator_line_break">Quebra de linha</string>
|
||||
<string name="separator_ruler">Régua</string>
|
||||
<string name="save_as_file_name">Salvar como arquivo de imagem?</string>
|
||||
<string name="file_name">Nome do arquivo</string>
|
||||
<string name="error_saving_binary_data">Arquivo não pode ser salvo</string>
|
||||
<string name="error_file_exists">Arquivo já existe</string>
|
||||
<string name="connect_to_wifi">Conectar para o WiFi</string>
|
||||
<string name="wifi_config_failed">Não foi possível configurar o WiFi</string>
|
||||
<string name="wifi_added">WiFi adicionado</string>
|
||||
<string name="sms_send">Enviar SMS</string>
|
||||
<string name="sms_error">Não foi possível enviar o SMS</string>
|
||||
<string name="tel_dial">Discar número</string>
|
||||
<string name="tel_error">Não foi possível discar o número</string>
|
||||
<string name="mail_send">Enviar e-mail</string>
|
||||
<string name="mail_error">Não foi possível enviar o e-mail</string>
|
||||
<string name="vcard_add">Adicionar para os contatos</string>
|
||||
<string name="vcard_failed">NNão foi possível adicionar para os contatos</string>
|
||||
<string name="vevent_add">Adicionar ao calendário</string>
|
||||
<string name="vevent_failed">Não foi possível adicionar ao calendário</string>
|
||||
<string name="otpauth_add">Add 2FA</string>
|
||||
<string name="otpauth_error">Could not add 2FA</string>
|
||||
<string name="search_web">Search the web</string>
|
||||
<string name="export_to_file">Export to file</string>
|
||||
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
|
||||
<string name="delimiter_comma">Comma</string>
|
||||
<string name="delimiter_semicolon">Semicolon</string>
|
||||
<string name="csv_allow_binary">Allow binary data in output file?</string>
|
||||
<string name="no">No</string>
|
||||
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
|
||||
<string name="saved_in_downloads">Saved in downloads</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,69 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Это приложение не имеет никакого смысла без доступа к камере</string>
|
||||
<string name="camera_error">Не удается получить доступ к камере, пожалуйста, попробуйте еще раз</string>
|
||||
<string name="scan_code">Сканирование кода</string>
|
||||
<string name="compose_barcode">Создать штрих-код</string>
|
||||
<string name="decode_barcode">Декодировать штрих-код</string>
|
||||
<string name="content">Текст</string>
|
||||
<string name="binary_data">(двоичные данные)</string>
|
||||
<string name="toggle_flash">Включить вспышку</string>
|
||||
<string name="share">Отправить</string>
|
||||
<string name="copy_to_clipboard">Скопировать в буфер обмена</string>
|
||||
<string name="put_into_clipboard">Положить в буфер обмена</string>
|
||||
<string name="open_url">Открыть URL</string>
|
||||
<string name="cannot_resolve_action">Ни одно приложение не может открыть это</string>
|
||||
<string name="pick_search_engine">Выберите поисковую систему</string>
|
||||
<string name="format">Формат</string>
|
||||
<string name="size">Размер в пикселях</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Введите текст здесь</string>
|
||||
<string name="encode">Создать</string>
|
||||
<string name="error_no_content">Нет данных</string>
|
||||
<string name="error_encoding_barcode">Штрих-код не может быть создан</string>
|
||||
<string name="view_barcode">Просмотреть штрих-код</string>
|
||||
<string name="info">Информация</string>
|
||||
<string name="switch_camera">Переключить камеру</string>
|
||||
<string name="history">История</string>
|
||||
<string name="preferences">Настройки</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="use_history">Сохранить историю сканирования</string>
|
||||
<string name="open_with_url">Открыть неизвестные данные с помощью URL</string>
|
||||
<string name="really_remove_scan">Действительно удалить сканирование?</string>
|
||||
<string name="really_remove_all_scans">Действительно удалить всю историю сканирования?</string>
|
||||
<string name="clear_history">Очистить историю</string>
|
||||
<string name="no_barcode_found">Штрих-код не найден</string>
|
||||
<string name="pick_list_separator">Как разделить элементы списка?</string>
|
||||
<string name="separator_line_break">Разрыв строки</string>
|
||||
<string name="separator_ruler">Линейка</string>
|
||||
<string name="save_as_file_name">Сохранить как файл?</string>
|
||||
<string name="file_name">Имя файла</string>
|
||||
<string name="error_saving_binary_data">Не удается сохранить файл</string>
|
||||
<string name="error_file_exists">Файл уже существует</string>
|
||||
<string name="connect_to_wifi">Подключиться к WiFi</string>
|
||||
<string name="wifi_config_failed">Не удалось настроить WiFi</string>
|
||||
<string name="wifi_added">WiFi добавлен</string>
|
||||
<string name="sms_send">Отправить СМС</string>
|
||||
<string name="sms_error">Не удалось отправить СМС</string>
|
||||
<string name="tel_dial">Наберите номер</string>
|
||||
<string name="tel_error">Не удалось набрать номер</string>
|
||||
<string name="mail_send">Отправить письмо</string>
|
||||
<string name="mail_error">Не удалось отправить почту</string>
|
||||
<string name="vcard_add">Добавить в контакты</string>
|
||||
<string name="vcard_failed">Не удалось добавить в контакты</string>
|
||||
<string name="vevent_add">Добавить в календарь</string>
|
||||
<string name="vevent_failed">Не удалось добавить в календарь</string>
|
||||
<string name="otpauth_add">Добавить 2FA</string>
|
||||
<string name="otpauth_error">Не удалось добавить 2FA</string>
|
||||
<string name="search_web">Search the web</string>
|
||||
<string name="export_to_file">Export to file</string>
|
||||
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
|
||||
<string name="delimiter_comma">Comma</string>
|
||||
<string name="delimiter_semicolon">Semicolon</string>
|
||||
<string name="csv_allow_binary">Allow binary data in output file?</string>
|
||||
<string name="no">No</string>
|
||||
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
|
||||
<string name="saved_in_downloads">Saved in downloads</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,73 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">没有相机权限,这个软件有啥意义。再见!</string>
|
||||
<string name="camera_error">无法访问相机,请重试。</string>
|
||||
<string name="scan_code">Scan code</string>
|
||||
<string name="compose_barcode">制作条形码</string>
|
||||
<string name="decode_barcode">解码条形码</string>
|
||||
<string name="content">内容</string>
|
||||
<string name="binary_data">(二进制数据)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d 字符</item>
|
||||
<item quantity="other">%1$s\n%2$d 字符</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">切换闪光灯</string>
|
||||
<string name="share">分享</string>
|
||||
<string name="copy_to_clipboard">复制到剪切板</string>
|
||||
<string name="put_into_clipboard">复制到剪切板</string>
|
||||
<string name="open_url">打开链接</string>
|
||||
<string name="cannot_resolve_action">没有应用程序可以打开它</string>
|
||||
<string name="pick_search_engine">选择一个搜索引擎</string>
|
||||
<string name="format">编码格式</string>
|
||||
<string name="size">图片尺寸</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">在这里输入内容</string>
|
||||
<string name="encode">编码</string>
|
||||
<string name="error_no_content">缺少内容</string>
|
||||
<string name="error_encoding_barcode">无法生成条形码</string>
|
||||
<string name="view_barcode">查看条形码</string>
|
||||
<string name="info">信息</string>
|
||||
<string name="switch_camera">切换摄像头</string>
|
||||
<string name="history">历史</string>
|
||||
<string name="preferences">首选项</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="use_history">保存扫描历史</string>
|
||||
<string name="open_with_url">使用URL打开未知数据</string>
|
||||
<string name="really_remove_scan">确定删除记录?</string>
|
||||
<string name="really_remove_all_scans">确定删除全部记录?</string>
|
||||
<string name="clear_history">删除记录</string>
|
||||
<string name="no_barcode_found">未找到条形码</string>
|
||||
<string name="pick_list_separator">如何分隔列表项?</string>
|
||||
<string name="separator_line_break">无</string>
|
||||
<string name="separator_ruler">分割线</string>
|
||||
<string name="save_as_file_name">保存为文件?</string>
|
||||
<string name="file_name">文件名</string>
|
||||
<string name="error_saving_binary_data">无法保存文件</string>
|
||||
<string name="error_file_exists">文件已存在</string>
|
||||
<string name="connect_to_wifi">连接到WiFi</string>
|
||||
<string name="wifi_config_failed">无法配置WiFi</string>
|
||||
<string name="wifi_added">WiFi已添加</string>
|
||||
<string name="sms_send">发送短信</string>
|
||||
<string name="sms_error">无法发送短信</string>
|
||||
<string name="tel_dial">拨打号码</string>
|
||||
<string name="tel_error">无法拨打此号码</string>
|
||||
<string name="mail_send">发送邮件</string>
|
||||
<string name="mail_error">无法发送邮件</string>
|
||||
<string name="vcard_add">添加到通讯录</string>
|
||||
<string name="vcard_failed">无法添加到通讯录</string>
|
||||
<string name="vevent_add">添加到日历</string>
|
||||
<string name="vevent_failed">无法添加到日历</string>
|
||||
<string name="otpauth_add">Add 2FA</string>
|
||||
<string name="otpauth_error">Could not add 2FA</string>
|
||||
<string name="search_web">Search the web</string>
|
||||
<string name="export_to_file">Export to file</string>
|
||||
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
|
||||
<string name="delimiter_comma">Comma</string>
|
||||
<string name="delimiter_semicolon">Semicolon</string>
|
||||
<string name="csv_allow_binary">Allow binary data in output file?</string>
|
||||
<string name="no">No</string>
|
||||
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
|
||||
<string name="saved_in_downloads">Saved in downloads</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,73 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">沒有相機權限的話,這個程式根本什麽都做不了。再見!</string>
|
||||
<string name="camera_error">不能開啓相機,請重試</string>
|
||||
<string name="scan_code">掃描條碼</string>
|
||||
<string name="compose_barcode">製作條碼</string>
|
||||
<string name="decode_barcode">解讀條碼</string>
|
||||
<string name="content">内容</string>
|
||||
<string name="binary_data">(2進制碼)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d 個字符</item>
|
||||
<item quantity="other">%1$s\n%2$d 個字符</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">閃光燈</string>
|
||||
<string name="share">分享</string>
|
||||
<string name="copy_to_clipboard">複製</string>
|
||||
<string name="put_into_clipboard">複製</string>
|
||||
<string name="open_url">開啓</string>
|
||||
<string name="cannot_resolve_action">無應用程式可以開啓</string>
|
||||
<string name="pick_search_engine">選擇搜尋引擎</string>
|
||||
<string name="format">格式</string>
|
||||
<string name="size">大小(pixels)</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">在此輸入</string>
|
||||
<string name="encode">製作條碼</string>
|
||||
<string name="error_no_content">找不到内容</string>
|
||||
<string name="error_encoding_barcode">條碼製作失敗</string>
|
||||
<string name="view_barcode">檢視條碼</string>
|
||||
<string name="info">資訊</string>
|
||||
<string name="switch_camera">切換相機</string>
|
||||
<string name="history">歷史</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="use_history">儲存掃描歷史</string>
|
||||
<string name="really_remove_scan">真的要移除掃描記錄?</string>
|
||||
<string name="really_remove_all_scans">真的要移除所有掃描記錄?</string>
|
||||
<string name="clear_history">清除記錄</string>
|
||||
<string name="no_barcode_found">找不到條碼</string>
|
||||
<string name="pick_list_separator">如何分開列表項目?</string>
|
||||
<string name="separator_line_break">Line break</string>
|
||||
<string name="separator_ruler">直尺</string>
|
||||
<string name="save_as_file_name">儲存成檔案?</string>
|
||||
<string name="file_name">檔案名稱</string>
|
||||
<string name="error_saving_binary_data">不能儲存檔案</string>
|
||||
<string name="error_file_exists">檔案已存在</string>
|
||||
<string name="connect_to_wifi">連接至 WiFi</string>
|
||||
<string name="wifi_config_failed">WiFi連接設定失敗</string>
|
||||
<string name="wifi_added">成功添加WiFi</string>
|
||||
<string name="sms_send">傳送SMS</string>
|
||||
<string name="sms_error">不能傳送SMS</string>
|
||||
<string name="tel_dial">撥號</string>
|
||||
<string name="tel_error">不能撥號</string>
|
||||
<string name="mail_send">發送電郵</string>
|
||||
<string name="mail_error">不能發送電郵</string>
|
||||
<string name="vcard_add">添加至聯絡人</string>
|
||||
<string name="vcard_failed">不能添加至聯絡人</string>
|
||||
<string name="vevent_add">添加至行事曆</string>
|
||||
<string name="vevent_failed">不能添加至行事曆</string>
|
||||
<string name="preferences">Preferences</string>
|
||||
<string name="open_with_url">Preferences</string>
|
||||
<string name="otpauth_add">Add 2FA</string>
|
||||
<string name="otpauth_error">Could not add 2FA</string>
|
||||
<string name="search_web">Search the web</string>
|
||||
<string name="export_to_file">Export to file</string>
|
||||
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
|
||||
<string name="delimiter_comma">Comma</string>
|
||||
<string name="delimiter_semicolon">Semicolon</string>
|
||||
<string name="csv_allow_binary">Allow binary data in output file?</string>
|
||||
<string name="no">No</string>
|
||||
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
|
||||
<string name="saved_in_downloads">Saved in downloads</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
</resources>
|
||||
@@ -1,12 +1,12 @@
|
||||
<resources>
|
||||
<color name="primary">#222</color>
|
||||
<color name="primary_translucent">#8222</color>
|
||||
<color name="primary_dark">#111</color>
|
||||
<color name="primary_dark_translucent">#8111</color>
|
||||
<color name="accent">#b6d46f</color>
|
||||
<color name="accent_dark">#a6c45f</color>
|
||||
<color name="background_color">#111</color>
|
||||
<color name="background_progress">#8000</color>
|
||||
<color name="button_text">#fff</color>
|
||||
<color name="separator">#333</color>
|
||||
<color name="crop_bound">#fff</color>
|
||||
<color name="candidate">#80b6d46f</color>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="csv_delimiters_names">
|
||||
<item>@string/delimiter_comma</item>
|
||||
<item>@string/delimiter_semicolon</item>
|
||||
</string-array>
|
||||
<string-array name="csv_delimiters_values">
|
||||
<item>,</item>
|
||||
<item>;</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -1,3 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">Binary Eye</string>
|
||||
<string name="open_with_url_hint">http://example.com?data=</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<resources>
|
||||
<string-array name="search_engines_names">
|
||||
<item>Google</item>
|
||||
<item>DuckDuckGo</item>
|
||||
<item>Qwant</item>
|
||||
<item>OpenFoodFacts.org</item>
|
||||
<item>OpenBeautyFacts.org</item>
|
||||
<item>OpenPetFoodFacts.org</item>
|
||||
</string-array>
|
||||
<string-array name="search_engines_values">
|
||||
<item>https://www.google.com/search?q=</item>
|
||||
<item>https://duckduckgo.com/?q=</item>
|
||||
<item>https://www.qwant.com/?q=</item>
|
||||
<item>https://world.openfoodfacts.org/cgi/search.pl?search_terms=</item>
|
||||
<item>https://world.openbeautyfacts.org/cgi/search.pl?search_terms=</item>
|
||||
<item>https://world.openpetfoodfacts.org/cgi/search.pl?search_terms=</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -5,6 +5,7 @@
|
||||
<string name="compose_barcode">Compose barcode</string>
|
||||
<string name="decode_barcode">Decode barcode</string>
|
||||
<string name="content">Content</string>
|
||||
<string name="binary_data">(binary data)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d character</item>
|
||||
<item quantity="other">%1$s\n%2$d characters</item>
|
||||
@@ -15,17 +16,22 @@
|
||||
<string name="put_into_clipboard">Put into clipboard</string>
|
||||
<string name="open_url">Open url</string>
|
||||
<string name="cannot_resolve_action">No application can open that</string>
|
||||
<string name="pick_search_engine">Pick a search engine</string>
|
||||
<string name="format">Format</string>
|
||||
<string name="size">Size in pixels</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Input content here</string>
|
||||
<string name="encode">ENCODE</string>
|
||||
<string name="error_no_content">Missing content</string>
|
||||
<string name="error_encoding_barcode">Barcode cannot be generated</string>
|
||||
<string name="view_barcode">View barcode</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Switch camera</string>
|
||||
<string name="history">History</string>
|
||||
<string name="preferences">Preferences</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="use_history">Save scan history</string>
|
||||
<string name="open_with_url">Open unknown data with URL</string>
|
||||
<string name="really_remove_scan">Really remove scan?</string>
|
||||
<string name="really_remove_all_scans">Really remove all scans?</string>
|
||||
<string name="clear_history">Clear history</string>
|
||||
@@ -33,4 +39,35 @@
|
||||
<string name="pick_list_separator">How to separate list items?</string>
|
||||
<string name="separator_line_break">Line break</string>
|
||||
<string name="separator_ruler">Ruler</string>
|
||||
<string name="save_as_file_name">Save as in Downloads file?</string>
|
||||
<string name="file_name">File name</string>
|
||||
<string name="error_saving_binary_data">Cannot save file</string>
|
||||
<string name="error_file_exists">File already exists</string>
|
||||
<string name="connect_to_wifi">Connect to WiFi</string>
|
||||
<string name="wifi_config_failed">Could not configure WiFi</string>
|
||||
<string name="wifi_added">WiFi added</string>
|
||||
<string name="sms_send">Send SMS</string>
|
||||
<string name="sms_error">Could not send SMS</string>
|
||||
<string name="tel_dial">Dial number</string>
|
||||
<string name="tel_error">Could not dial number</string>
|
||||
<string name="mail_send">Send mail</string>
|
||||
<string name="mail_error">Could not send mail</string>
|
||||
<string name="vcard_add">Add to contacts</string>
|
||||
<string name="vcard_failed">Could not add to contacts</string>
|
||||
<string name="vevent_add">Add to calendar</string>
|
||||
<string name="vevent_failed">Could not add to calendar</string>
|
||||
<string name="otpauth_add">Add 2FA</string>
|
||||
<string name="otpauth_error">Could not add 2FA</string>
|
||||
<string name="search_web">Search the web</string>
|
||||
<string name="export_to_file">Export to file</string>
|
||||
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
|
||||
<string name="delimiter_comma">Comma</string>
|
||||
<string name="delimiter_semicolon">Semicolon</string>
|
||||
<string name="csv_allow_binary">Allow binary data in output file?</string>
|
||||
<string name="no">No</string>
|
||||
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
|
||||
<string name="saved_in_downloads">Saved in downloads</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
</resources>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
name="Button"
|
||||
parent="@android:style/Widget.Button">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">36dp</item>
|
||||
<item name="android:layout_height">48dp</item>
|
||||
<item name="android:layout_gravity">center_horizontal</item>
|
||||
<item name="android:background">@drawable/button_raised</item>
|
||||
<item name="android:textColor">@color/button_text</item>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma version(1)
|
||||
#pragma rs java_package_name(de.markusfisch.android.binaryeye.renderscript)
|
||||
#pragma rs_fp_relaxed
|
||||
|
||||
rs_allocation inImage;
|
||||
int inWidth;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package de.markusfisch.android.binaryeye
|
||||
|
||||
import junit.framework.TestCase.fail
|
||||
|
||||
fun simpleFail(message: String = "Unknown reason, but failed"): Nothing {
|
||||
fail(message)
|
||||
throw IllegalStateException("You should never have reached this point in the code, but anyways: $message")
|
||||
}
|
||||
|
||||
inline fun <reified E : Throwable> assertThrows(message: String = "Unknown reason, but didn't failed even though should fail", block: () -> Unit) {
|
||||
try {
|
||||
block()
|
||||
fail(message)
|
||||
} catch (e: Throwable) {
|
||||
if (e !is E) {
|
||||
fail("Got unexpected other Throwable: ${e::class.java.simpleName}\n$e")
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package de.markusfisch.android.binaryeye.actions.wifi
|
||||
|
||||
import de.markusfisch.android.binaryeye.simpleFail
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertFalse
|
||||
import junit.framework.TestCase.assertNull
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class WifiConfigurationFactoryTest {
|
||||
@Test
|
||||
fun notWifi() {
|
||||
assertNull(WifiConfigurationFactory.parseMap("asdfz"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wep() {
|
||||
val info = simpleDataAccessor("WIFI:T:WEP;S:asdfz;P:password;;")
|
||||
|
||||
assertEquals("WEP", info.securityType)
|
||||
assertEquals("\"asdfz\"", info.ssid)
|
||||
assertEquals("\"password\"", info.password)
|
||||
assertFalse(info.hidden)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hidden() {
|
||||
val info = simpleDataAccessor("WIFI:T:WPA;S:asdfz;P:password;H:true;;")
|
||||
|
||||
assertEquals("WPA", info.securityType)
|
||||
assertEquals("\"asdfz\"", info.ssid)
|
||||
assertEquals("\"password\"", info.password)
|
||||
assertTrue(info.hidden)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nopass() {
|
||||
val info = simpleDataAccessor("WIFI:T:nopass;S:asdfz;;")
|
||||
|
||||
assertEquals("nopass", info.securityType)
|
||||
assertEquals("\"asdfz\"", info.ssid)
|
||||
assertNull(info.password)
|
||||
assertFalse(info.hidden)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun plainUnsecured() {
|
||||
val info = simpleDataAccessor("WIFI:S:asdfz;;")
|
||||
|
||||
assertEquals("", info.securityType)
|
||||
assertEquals("\"asdfz\"", info.ssid)
|
||||
assertNull(info.password)
|
||||
assertFalse(info.hidden)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hex() {
|
||||
val info = simpleDataAccessor("WIFI:T:WEP;S:d34dbeef;P:d34dbeef;;")
|
||||
|
||||
assertEquals("WEP", info.securityType)
|
||||
assertEquals("d34dbeef", info.ssid)
|
||||
assertEquals("d34dbeef", info.password)
|
||||
assertFalse(info.hidden)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun escaping() {
|
||||
val info = simpleDataAccessor("""WIFI:S:\"ssid\\\;stillSSID\:\;x;;""")
|
||||
|
||||
assertEquals("", info.securityType)
|
||||
assertEquals("""""ssid\;stillSSID:;x"""", info.ssid)
|
||||
assertNull(info.password)
|
||||
assertFalse(info.hidden)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wrongEscaping() {
|
||||
val info = simpleDataAccessor("""WIFI:S:\SSID":\;x;""")
|
||||
|
||||
assertEquals("", info.securityType)
|
||||
assertEquals(""""\SSID":;x"""", info.ssid)
|
||||
assertNull(info.password)
|
||||
assertFalse(info.hidden)
|
||||
}
|
||||
|
||||
private fun simpleDataAccessor(wifiString: String): WifiConfigurationFactory.SimpleDataAccessor {
|
||||
val map = WifiConfigurationFactory.parseMap(wifiString)
|
||||
?: simpleFail("parsing map of valid string fails ($wifiString)")
|
||||
return WifiConfigurationFactory.SimpleDataAccessor.of(map)
|
||||
?: simpleFail("could not create SimpleDataAccessor of (potentially) valid map ($map of $wifiString)")
|
||||
}
|
||||
}
|
||||
+4
-13
@@ -1,34 +1,25 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.21'
|
||||
ext.tools_version = '3.3.2'
|
||||
ext.kotlin_version = '1.3.50'
|
||||
ext.tools_version = '3.5.2'
|
||||
ext.build_tools_version = '28.0.3'
|
||||
ext.sdk_version = 28
|
||||
ext.support_version = '25.3.1'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$tools_version"
|
||||
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'org.sonarqube' version '2.6.2'
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
kotlin.code.style=official
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,6 +1,6 @@
|
||||
#Wed Apr 10 15:27:10 PDT 2013
|
||||
#Tue May 07 14:37:45 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user