Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9986a4ffb5 | ||
|
|
c464b7de02 | ||
|
|
e588405413 | ||
|
|
fdaf20fd45 | ||
|
|
7c8fdb8ea1 | ||
|
|
bb9d556c16 | ||
|
|
9e395d1928 | ||
|
|
95f12e7c31 | ||
|
|
9dac0b13fb | ||
|
|
7939a728bc | ||
|
|
b6ca20d813 | ||
|
|
fe7e36804e | ||
|
|
8cf1c35029 | ||
|
|
62ca2299a0 | ||
|
|
2423586c8c | ||
|
|
29b1f44bb9 | ||
|
|
acee18e016 | ||
|
|
f798dce832 | ||
|
|
97838ec7c4 | ||
|
|
767860ab07 | ||
|
|
f8fd00f21b | ||
|
|
2cf6aa0812 | ||
|
|
197554fd1b | ||
|
|
3dc35ac60d | ||
|
|
fd05209f57 | ||
|
|
6d5aed816f | ||
|
|
268c395043 | ||
|
|
867dc36811 | ||
|
|
e6e9b912b6 | ||
|
|
152b4e50fc | ||
|
|
93070f38c9 | ||
|
|
7a162d6fb0 | ||
|
|
9c03e83d3a | ||
|
|
4be9421b44 | ||
|
|
b895997967 | ||
|
|
b5bbe659db | ||
|
|
bb7715455a | ||
|
|
5283acee8f | ||
|
|
315abfb1dd | ||
|
|
05c776168e | ||
|
|
880b8e0b24 | ||
|
|
1597adb284 | ||
|
|
1c356aeaed | ||
|
|
fe934c5d6e | ||
|
|
5091565ff0 | ||
|
|
c7eb664958 | ||
|
|
c763c522e7 | ||
|
|
dc1beb5f28 | ||
|
|
76ea46cea8 | ||
|
|
0991fb85f0 | ||
|
|
09f0d2e1db | ||
|
|
bc0655788c | ||
|
|
739a801cb4 | ||
|
|
c18f1b8770 | ||
|
|
b0df09d76f | ||
|
|
e8b8e578cb | ||
|
|
09ce14125e | ||
|
|
446d1543c3 | ||
|
|
02a360beaf | ||
|
|
9742e5b790 | ||
|
|
9c08aa1184 | ||
|
|
ce8a5f89cd | ||
|
|
50795afeb4 | ||
|
|
b8c941ee62 | ||
|
|
5b1738342d | ||
|
|
004738440c | ||
|
|
a90aee570a | ||
|
|
17f2f88b7f | ||
|
|
071ea1500f | ||
|
|
25b142407b | ||
|
|
848b4bbaa8 | ||
|
|
c2dc8984c1 | ||
|
|
e37a4d21ea | ||
|
|
da2a20ba1f | ||
|
|
5fe69e5a3d | ||
|
|
f50e1dab0a | ||
|
|
b165192377 | ||
|
|
c0a4d67cc5 | ||
|
|
ef36a2f7e0 | ||
|
|
40b90e0be2 | ||
|
|
680030e89e | ||
|
|
b4fefad54f | ||
|
|
f65e942100 | ||
|
|
95d7a84648 | ||
|
|
1e4a0fde7a | ||
|
|
a6785d500e | ||
|
|
d77e464554 | ||
|
|
d7198934d7 | ||
|
|
1a3b3f263f | ||
|
|
d4c5c6a1dc | ||
|
|
8618a5b482 | ||
|
|
6cb65635e3 | ||
|
|
5e37ff41d6 | ||
|
|
03ac3ff0e9 | ||
|
|
480a248e71 | ||
|
|
b4060d70ce | ||
|
|
7ceb97e942 | ||
|
|
7100f79efc | ||
|
|
00a1ec877b | ||
|
|
89e029687b | ||
|
|
5f4f18a062 | ||
|
|
69ec2cda04 | ||
|
|
4aba885fb6 | ||
|
|
9dbb78ce67 | ||
|
|
47c252ab18 | ||
|
|
10e02b3164 | ||
|
|
7e82dbe730 | ||
|
|
e91c30f7c2 | ||
|
|
30a36e7e07 | ||
|
|
5f91fe2f02 | ||
|
|
cab8d6c038 | ||
|
|
4aac5dc61b | ||
|
|
fbbc20d327 | ||
|
|
5595b68ff4 | ||
|
|
c492897fa9 | ||
|
|
647c08bfb8 | ||
|
|
e5025ef58c | ||
|
|
74bb421b2d | ||
|
|
4ed8d4614a | ||
|
|
f038eb989b | ||
|
|
c5722a3890 | ||
|
|
41108007ad | ||
|
|
867f179a37 | ||
|
|
892916b0d0 | ||
|
|
3bd440fdb7 | ||
|
|
a4c8551be8 | ||
|
|
316644d3ae | ||
|
|
9fb2eb2d2b | ||
|
|
dd046a5d52 | ||
|
|
f3ddd458cb | ||
|
|
157d928620 | ||
|
|
94cb509f0b | ||
|
|
df18f8ebd3 | ||
|
|
705095472f | ||
|
|
f897973b3f | ||
|
|
c88d5cf4cd | ||
|
|
8ecd49731f | ||
|
|
6f3b4dbdab | ||
|
|
3c8c0737c4 | ||
|
|
9baf6d621a | ||
|
|
f0d0ee04af | ||
|
|
4d38da045a | ||
|
|
af70d9507b | ||
|
|
273f38c36c | ||
|
|
210cdfc171 | ||
|
|
3ec8c4b5d7 | ||
|
|
d99f0ca039 | ||
|
|
4a9e700d20 | ||
|
|
40e339ce89 | ||
|
|
9d476b3491 | ||
|
|
22b58265a5 | ||
|
|
bd4576b7ac | ||
|
|
a57f6d1904 | ||
|
|
842b38c986 | ||
|
|
b1fae727a9 | ||
|
|
db24f608d5 | ||
|
|
9f49a00bae | ||
|
|
947bbf3031 | ||
|
|
b3b57a7ba8 | ||
|
|
1627f3ead9 | ||
|
|
4343f5da82 | ||
|
|
67395a067d | ||
|
|
0de571f3c1 | ||
|
|
d83cd77a87 | ||
|
|
5ca7aff5df | ||
|
|
e4cc516a9b | ||
|
|
f160cf0953 | ||
|
|
ef5b5aa34a | ||
|
|
536ee5bc3d | ||
|
|
0becada572 | ||
|
|
0c773d6f05 | ||
|
|
f2df364840 | ||
|
|
5740b43568 | ||
|
|
b384b99b01 | ||
|
|
6affa4ce25 | ||
|
|
455d329fff | ||
|
|
792be87283 | ||
|
|
381e10bddc | ||
|
|
220d1b9d4c | ||
|
|
020cd2c192 | ||
|
|
e8766a0fd1 | ||
|
|
69c6d1324e | ||
|
|
2dd0c48225 | ||
|
|
faa536274b | ||
|
|
0a944f9fb8 | ||
|
|
a31982646a | ||
|
|
4320ee8fcc | ||
|
|
d890bf28fa | ||
|
|
6347bd568c | ||
|
|
9de11e2ae8 | ||
|
|
57c381e310 | ||
|
|
a11650eaa5 | ||
|
|
e85b006e40 | ||
|
|
a6c25be316 | ||
|
|
2b011325a6 | ||
|
|
fb96d5ecf8 | ||
|
|
897d61a646 | ||
|
|
ac67db6491 | ||
|
|
84bc7dad30 | ||
|
|
b21af65b68 | ||
|
|
a7adec7177 | ||
|
|
fc6fcfa743 | ||
|
|
47f95fc67a | ||
|
|
deccc9eb36 | ||
|
|
6505312b44 | ||
|
|
cb82fc3252 | ||
|
|
636ac2386a | ||
|
|
49d32ff95c | ||
|
|
fed7d561c5 | ||
|
|
8e22290c5f | ||
|
|
b33e474d12 | ||
|
|
40a05a1fc0 | ||
|
|
d947764882 | ||
|
|
1a5a8e0fa1 | ||
|
|
e6d9d54866 | ||
|
|
538155bf04 | ||
|
|
3266a58041 | ||
|
|
d594a43d6d | ||
|
|
0aa1f80451 | ||
|
|
36eff75546 | ||
|
|
8aef9c22dd | ||
|
|
1a159b35ee | ||
|
|
05ab87caa8 | ||
|
|
a3f0185f08 | ||
|
|
104f409486 | ||
|
|
eb8f578bd8 | ||
|
|
a8e420fc95 | ||
|
|
7120e18670 | ||
|
|
9fb34965f5 | ||
|
|
c5cbd1140b | ||
|
|
fecdcd7deb | ||
|
|
fd3d2f9934 | ||
|
|
35b6ead662 |
@@ -1,5 +1,128 @@
|
||||
# Change Log
|
||||
|
||||
## 1.49.2
|
||||
* Update design of app icon
|
||||
* Update pt-br translation
|
||||
|
||||
## 1.49.1
|
||||
* Update Simplified Chinese translation
|
||||
* Return directly to caller for deep links
|
||||
* Fix metadata for deep links
|
||||
|
||||
## 1.49.0
|
||||
* Add a setting to quickly enable/disable forwarding
|
||||
* Stop playing error beep when in silent mode
|
||||
* Improve enabling/disabling torch mode
|
||||
* Update to latest ZXing version
|
||||
* Update German translation
|
||||
* Update Russian translation
|
||||
|
||||
## 1.48.1
|
||||
* Fix broken detection of binary content
|
||||
|
||||
## 1.48.0
|
||||
* Add a menu item to temporarily scan a certain format only
|
||||
* Add a monochome launcher icon for Android 13
|
||||
* Show alpha numeric characters in binary data
|
||||
* Fix showing binary data label for scans from the history
|
||||
* Fix handling empty meta strings
|
||||
* Update Turkish translation
|
||||
* Update Ukrainian translation
|
||||
* Update French translation
|
||||
* Update Polish translation
|
||||
* Update Russian translation
|
||||
|
||||
## 1.47.0
|
||||
* Add forwarding into external browser
|
||||
* Fix visibility of zoom slider when switching cameras
|
||||
|
||||
## 1.46.3
|
||||
* Fix cropping still images
|
||||
* Improve usability of zoom slider
|
||||
* Update Dutch translation
|
||||
* Update Chinese translation
|
||||
|
||||
## 1.46.2
|
||||
* Fix saving external files on Android Q and newer
|
||||
|
||||
## 1.46.1
|
||||
* Use cropping limiter settings for image picking too
|
||||
* Remove extra link on result screen
|
||||
* Update Turkish translation
|
||||
|
||||
## 1.46.0
|
||||
* Support deep links
|
||||
* Support "A:" in WIFI codes
|
||||
* Allow multiple query arguments in custom URL
|
||||
* Add Wi-Fi info data section
|
||||
* Add Vietnamese translation
|
||||
* Update Brazilian Portuguese translation
|
||||
* Update Hungarian translation
|
||||
* Update Ukrainian translation
|
||||
* Update Russian translation
|
||||
|
||||
## 1.45.0
|
||||
* Add a setting to set the delay for bulk mode
|
||||
* Add support for MECARD contact format
|
||||
* Remove unused permissions on Android Q
|
||||
|
||||
## 1.44.1
|
||||
* Update Spanish translation
|
||||
* Update Japanese translation
|
||||
* Fix in-app language switching for App Bundles
|
||||
|
||||
## 1.44.0
|
||||
* Add a setting to choose barcode formats to scan
|
||||
* Ask before removing network suggestions
|
||||
* Update Traditional Chinese Translation
|
||||
* Update Russian language
|
||||
* Update Dutch translation
|
||||
|
||||
## 1.43.0
|
||||
* Add a preference item to remove WiFi network suggestions
|
||||
* Update Traditional Chinese Translation
|
||||
* Update Russian language
|
||||
|
||||
## 1.42.0
|
||||
* Add a setting to pick a default search engine
|
||||
* Update Traditional Chinese
|
||||
* Update Dutch translation
|
||||
|
||||
## 1.41.1
|
||||
* Update Bengali translation
|
||||
* Update Simplified Chinese
|
||||
|
||||
## 1.41.0
|
||||
* Use cropping limiter by default for new installations
|
||||
* Truncate all toasts with strings from the outside
|
||||
* Add Bangla translation
|
||||
* Add missing Japanese locale
|
||||
* Update Hungarian translation
|
||||
* Update Russian app description
|
||||
|
||||
## 1.40.0
|
||||
* Add a setting to show/hide scanned data when scanning continuously
|
||||
* Only show the first 50 characters when scanning continuously
|
||||
* Share binary data as hex dump
|
||||
* Update Russian language
|
||||
|
||||
## 1.39.0
|
||||
* Fix adding contacts and calendar entries on Android 11+
|
||||
* Add a setting to always start in bulk mode
|
||||
* Add Czech translation
|
||||
* Update Spanish translation
|
||||
* Update Czech translation
|
||||
|
||||
## 1.38.0
|
||||
* Add a quick settings tile to scan from settings
|
||||
* Make custom background requests time out after 5 seconds
|
||||
* Add Japanese translation
|
||||
* Updated French translation
|
||||
|
||||
## 1.37.0
|
||||
* Improve formatting date and time in history listing
|
||||
* Add haptic and audible feedback for background request errors
|
||||
|
||||
## 1.36.0
|
||||
* Add a setting to automatically return to scanning screen after copying
|
||||
* Add a button to test the background request
|
||||
|
||||
@@ -23,6 +23,19 @@ test:
|
||||
cat:
|
||||
./gradlew test cAT
|
||||
|
||||
testview:
|
||||
adb shell am start -W -a android.intent.action.VIEW -d 'binaryeye://scan'
|
||||
|
||||
testscan:
|
||||
adb shell am start -a android.intent.action.VIEW \
|
||||
-c android.intent.category.BROWSABLE \
|
||||
-d 'binaryeye://scan/?ret=http%3A%2F%2Fmarkusfisch.de%2F%3Fresult%3D{RESULT}'
|
||||
|
||||
testurl:
|
||||
adb shell am start -a android.intent.action.VIEW \
|
||||
-c android.intent.category.BROWSABLE \
|
||||
-d 'http://markusfisch.de/BinaryEye?ret=http%3A%2F%2Fmarkusfisch.de%2F%3Fresult%3D{RESULT}'
|
||||
|
||||
install:
|
||||
adb $(TARGET) install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ hesitate to contact me.
|
||||
|
||||
## Permissions
|
||||
|
||||
### [ACCESS_FINE_LOCATION](https://developer.android.com/reference/android/Manifest.permission#ACCESS_FINE_LOCATION)
|
||||
### [ACCESS_FINE_LOCATION][ACCESS_FINE_LOCATION]
|
||||
|
||||
Required to add a WiFi network from a barcode (usually a QR Code).
|
||||
|
||||
@@ -17,23 +17,23 @@ which requires `ACCESS_FINE_LOCATION`.
|
||||
|
||||
On Android Q an better, this permission is not requested nor required.
|
||||
|
||||
### [CAMERA](https://developer.android.com/reference/android/Manifest.permission#CAMERA)
|
||||
### [CAMERA][CAMERA]
|
||||
|
||||
Required to read a barcode from the camera image.
|
||||
|
||||
### [INTERNET](https://developer.android.com/reference/android/Manifest.permission#INTERNET)
|
||||
### [INTERNET][INTERNET]
|
||||
|
||||
Required to send a scanned code to a custom URL you can specify in settings.
|
||||
|
||||
### [VIBRATE](https://developer.android.com/reference/android/Manifest.permission#VIBRATE)
|
||||
### [VIBRATE][VIBRATE]
|
||||
|
||||
Required to vibrate on detection.
|
||||
|
||||
### [WRITE_EXTERNAL_STORAGE](https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE)
|
||||
### [WRITE_EXTERNAL_STORAGE][WRITE_EXTERNAL_STORAGE]
|
||||
|
||||
Required to save generated barcodes as files before Android Q.
|
||||
|
||||
### [CHANGE_WIFI_STATE](https://developer.android.com/reference/android/Manifest.permission#CHANGE_WIFI_STATE)
|
||||
### [CHANGE_WIFI_STATE][CHANGE_WIFI_STATE]
|
||||
|
||||
Required to add a WiFi network from a barcode (usually a QR Code).
|
||||
|
||||
@@ -41,7 +41,7 @@ On Android Q and better, this feature requires using
|
||||
[WifiManager.addNetworkSuggestions][addNetworkSuggestions]
|
||||
which requires `CHANGE_WIFI_STATE`.
|
||||
|
||||
### [ACCESS_WIFI_STATE](https://developer.android.com/reference/android/Manifest.permission#ACCESS_FINE_LOCATION)
|
||||
### [ACCESS_WIFI_STATE][ACCESS_FINE_LOCATION]
|
||||
|
||||
Required to add a WiFi network from a barcode (usually a QR Code).
|
||||
|
||||
@@ -49,5 +49,12 @@ Before Android Q, this feature requires using
|
||||
[WifiManager.getConfiguredNetworks][configuredNetworks]
|
||||
which requires `ACCESS_WIFI_STATE`.
|
||||
|
||||
[ACCESS_FINE_LOCATION]: https://developer.android.com/reference/android/Manifest.permission#ACCESS_FINE_LOCATION
|
||||
[CAMERA]: https://developer.android.com/reference/android/Manifest.permission#CAMERA
|
||||
[INTERNET]: https://developer.android.com/reference/android/Manifest.permission#INTERNET
|
||||
[VIBRATE]: https://developer.android.com/reference/android/Manifest.permission#VIBRATE
|
||||
[WRITE_EXTERNAL_STORAGE]: https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE
|
||||
[CHANGE_WIFI_STATE]: https://developer.android.com/reference/android/Manifest.permission#CHANGE_WIFI_STATE
|
||||
[ACCESS_WIFI_STATE]: https://developer.android.com/reference/android/Manifest.permission#ACCESS_FINE_LOCATION
|
||||
[configuredNetworks]: https://developer.android.com/reference/android/net/wifi/WifiManager#getConfiguredNetworks()
|
||||
[addNetworkSuggestions]: https://developer.android.com/reference/android/net/wifi/WifiManager#addNetworkSuggestions(java.util.List%3Candroid.net.wifi.WifiNetworkSuggestion%3E)
|
||||
|
||||
@@ -64,6 +64,54 @@ ZXing can generate the following barcode formats:
|
||||
* [QR CODE][qr_code]
|
||||
* [UPC A][upc_a]
|
||||
|
||||
## Deep Links and Intents
|
||||
|
||||
### Deep Links
|
||||
|
||||
You can invoke Binary Eye with a web URI intent from anything that can
|
||||
open URIs. There are two options:
|
||||
|
||||
1. [binaryeye://scan](binaryeye://scan)
|
||||
2. [http(s)://markusfisch.de/BinaryEye](http://markusfisch.de/BinaryEye)
|
||||
|
||||
If you want to get the scanned contents, you can add a `ret` query
|
||||
argument with a (URL encoded) URI template. For example:
|
||||
|
||||
[http://markusfisch.de/BinaryEye?ret=http%3A%2F%2Fexample.com%2F%3Fresult%3D{RESULT}](http://markusfisch.de/BinaryEye?ret=http%3A%2F%2Fexample.com%2F%3Fresult%3D{RESULT})
|
||||
|
||||
Supported symbols are:
|
||||
|
||||
* `RESULT` - scanned content
|
||||
* `RESULT_BYTES` - raw result as a hex string
|
||||
* `FORMAT` - barcode format
|
||||
* `META` - the meta data, if available
|
||||
|
||||
### SCAN Intent
|
||||
|
||||
You can also use Binary Eye from other apps by using the
|
||||
`com.google.zxing.client.android.SCAN` Intent with `startActivityForResult()`
|
||||
like this:
|
||||
|
||||
startActivityForResult(
|
||||
Intent("com.google.zxing.client.android.SCAN"),
|
||||
SOME_NUMBER
|
||||
)
|
||||
|
||||
And process the result in `onActivityResult()` of your `Activity`:
|
||||
|
||||
override fun onActivityResult(
|
||||
requestCode: Int,
|
||||
resultCode: Int,
|
||||
data: Intent?
|
||||
) {
|
||||
when (requestCode) {
|
||||
SOME_NUMBER -> if (resultCode == RESULT_OK) {
|
||||
val result = data.getStringExtra("SCAN_RESULT")
|
||||
…
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## RenderScript
|
||||
|
||||
This app uses [RenderScript][rs] to resize and rotate the camera image.
|
||||
|
||||
@@ -3,17 +3,19 @@ apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion sdk_version
|
||||
buildToolsVersion build_tools_version
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion sdk_version
|
||||
|
||||
versionCode 75
|
||||
versionName '1.36.0'
|
||||
versionCode 96
|
||||
versionName '1.49.2'
|
||||
|
||||
// it's recommended to set this value to the lowest API level
|
||||
// able to provide all the functionality
|
||||
// Required for desugaring.
|
||||
multiDexEnabled true
|
||||
|
||||
// It's recommended to set this value to the lowest API level
|
||||
// able to provide all the functionality.
|
||||
renderscriptTargetApi 24
|
||||
renderscriptSupportModeEnabled true
|
||||
renderscriptSupportModeBlasEnabled true
|
||||
@@ -22,7 +24,7 @@ android {
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
config {
|
||||
release {
|
||||
keyAlias System.getenv('ANDROID_KEY_ALIAS')
|
||||
keyPassword System.getenv('ANDROID_KEY_PASSWORD')
|
||||
storePassword System.getenv('ANDROID_STORE_PASSWORD')
|
||||
@@ -46,14 +48,33 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
signingConfig signingConfigs.config
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
|
||||
bundle {
|
||||
language {
|
||||
// To make the app bundle contain all language resources
|
||||
// so the in-app language setting works. Another solution
|
||||
// would be to use the PlayCore API to download language
|
||||
// resources on demand, but it makes much more sense to
|
||||
// simply include the ~60 kb than adding fragile code and
|
||||
// dependencies.
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
// To desugar fancy Java features in ZXing 3.4.+ that don't
|
||||
// work on SDK < 24. See:
|
||||
// https://github.com/zxing/zxing/issues/1170
|
||||
coreLibraryDesugaringEnabled true
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
// because at the time of writing, even the latest version
|
||||
// Because at the time of writing, even the latest version
|
||||
// of RenderScript is raising this error no matter if
|
||||
// renderscriptTargetApi matches targetSdkVersion
|
||||
// renderscriptTargetApi matches targetSdkVersion.
|
||||
disable 'GradleCompatible'
|
||||
}
|
||||
}
|
||||
@@ -65,17 +86,16 @@ dependencies {
|
||||
androidTestImplementation('com.android.support.test:rules:1.0.2') {
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
|
||||
implementation "com.android.support:appcompat-v7:$support_version"
|
||||
implementation "com.android.support:design:$support_version"
|
||||
implementation "com.android.support:preference-v7:$support_version"
|
||||
implementation "com.android.support:preference-v14:$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.google.zxing:core:3.4.1'
|
||||
implementation 'com.github.markusfisch:CameraView:1.9.1'
|
||||
implementation 'com.github.markusfisch:ScalingImageView:1.3.0'
|
||||
}
|
||||
|
||||
@@ -9,4 +9,14 @@
|
||||
<issue id="UnusedResources">
|
||||
<ignore path="*/res/rs/debug/raw/bc*" />
|
||||
</issue>
|
||||
<!-- Android 12 requires apps that request ACCESS_FINE_LOCATION
|
||||
to also request ACCESS_COARSE_LOCATION. But this app doesn't
|
||||
request nor requires ACCESS_FINE_LOCATION above SDK 28. So
|
||||
this is a false positive. -->
|
||||
<issue id="CoarseFineLocation" severity="ignore"/>
|
||||
<!-- Androidx SplashScreen compat library would require minSdk 21,
|
||||
make the app unnecessarily larger and Android 12 automatically
|
||||
replaces our custom splash screen anyway, which is exactly what
|
||||
we want. So there is no reason to complain. -->
|
||||
<issue id="CustomSplashScreen" severity="ignore"/>
|
||||
</lint>
|
||||
|
||||
@@ -16,14 +16,17 @@ import java.util.regex.Pattern
|
||||
class PreprocessorTest {
|
||||
@Test
|
||||
fun processSamples() {
|
||||
val zxing = Zxing()
|
||||
val assets = InstrumentationRegistry.getInstrumentation()
|
||||
.context.assets
|
||||
val samples = assets.list("samples")
|
||||
checkNotNull(samples) { "no samples found" }
|
||||
val pattern = Pattern.compile(
|
||||
"[0-9]+-([0-9]+)deg.jpg"
|
||||
)
|
||||
val samples = assets.list("samples")
|
||||
checkNotNull(samples) { "no samples found" }
|
||||
val rs = RenderScript.create(
|
||||
InstrumentationRegistry.getTargetContext(),
|
||||
)
|
||||
val zxing = Zxing()
|
||||
for (sample in samples) {
|
||||
val m = pattern.matcher(sample)
|
||||
if (!m.find() || m.groupCount() < 1) {
|
||||
@@ -36,9 +39,6 @@ class PreprocessorTest {
|
||||
val frameWidth = bitmap.width
|
||||
val frameHeight = bitmap.height
|
||||
val frameOrientation = m.group(1) ?: return
|
||||
val rs = RenderScript.create(
|
||||
InstrumentationRegistry.getTargetContext(),
|
||||
)
|
||||
val preprocessor = Preprocessor(
|
||||
rs,
|
||||
frameWidth,
|
||||
@@ -59,10 +59,9 @@ class PreprocessorTest {
|
||||
}
|
||||
val result = zxing.decode(frameData, outWidth, outHeight)
|
||||
preprocessor.destroy()
|
||||
rs.destroy()
|
||||
|
||||
checkNotNull(result) { "no barcode found in $sample" }
|
||||
}
|
||||
rs.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,21 +80,21 @@ private fun encodeYUV420SP(argb: IntArray, width: Int, height: Int): ByteArray {
|
||||
var r: Int
|
||||
var g: Int
|
||||
var b: Int
|
||||
var Y: Int
|
||||
var y: Int
|
||||
var u: Int
|
||||
var v: Int
|
||||
var index = 0
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
for (t in 0 until height) {
|
||||
for (s in 0 until width) {
|
||||
val pixel = argb[index]
|
||||
r = pixel and 0xff0000 shr 16
|
||||
g = pixel and 0xff00 shr 8
|
||||
b = pixel and 0xff
|
||||
Y = (66 * r + 129 * g + 25 * b + 128 shr 8) + 16
|
||||
y = (66 * r + 129 * g + 25 * b + 128 shr 8) + 16
|
||||
u = (-38 * r - 74 * g + 112 * b + 128 shr 8) + 128
|
||||
v = (112 * r - 94 * g - 18 * b + 128 shr 8) + 128
|
||||
yuv[yIndex++] = (if (Y < 0) 0 else if (Y > 255) 255 else Y).toByte()
|
||||
if (y % 2 == 0 && index % 2 == 0) {
|
||||
yuv[yIndex++] = (if (y < 0) 0 else if (y > 255) 255 else y).toByte()
|
||||
if (t % 2 == 0 && index % 2 == 0) {
|
||||
yuv[uvIndex++] = (if (v < 0) 0 else if (v > 255) 255 else v).toByte()
|
||||
yuv[uvIndex++] = (if (u < 0) 0 else if (u > 255) 255 else u).toByte()
|
||||
}
|
||||
|
||||
@@ -4,35 +4,7 @@
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M32.002,-24.974h3.127v7.656h-3.127z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:pathData="m54.826,27.893c-6.851,0 -13.701,2.581 -18.924,7.745 -10.445,10.328 -10.413,27.062 0,37.423l36.218,36.036 37.879,0.902 -0.07,-38.363 -36.18,-35.998c-5.207,-5.18 -12.073,-7.745 -18.924,-7.745z"
|
||||
android:strokeAlpha="1"
|
||||
android:pathData="M54.826,27.894C47.976,27.894 41.125,30.475 35.903,35.639C25.457,45.967 25.489,62.701 35.903,73.062L72.121,109.098L108,108L109.93,71.637L73.75,35.639C68.543,30.459 61.677,27.894 54.826,27.894Z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="8.60725021"
|
||||
android:fillColor="#000000"
|
||||
@@ -41,43 +13,25 @@
|
||||
android:fillAlpha="0.1254902"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M28.001,54.118a26.525,26.227 0,1 0,53.049 0a26.525,26.227 0,1 0,-53.049 0z"
|
||||
android:strokeAlpha="1"
|
||||
android:pathData="M27.475,54a26.525,26.5 0,1 0,53.05 0a26.525,26.5 0,1 0,-53.05 0z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M39.26,54.988a14.509,14.346 0,1 0,29.019 0a14.509,14.346 0,1 0,-29.019 0z"
|
||||
android:strokeAlpha="1"
|
||||
android:pathData="M72.745,54.001C72.745,58.588 71.099,62.791 68.362,66.049L68.362,41.953C71.099,45.21 72.745,49.412 72.745,54.001ZM54.001,72.745C53.463,72.745 52.931,72.723 52.406,72.679L52.406,35.322C52.931,35.278 53.463,35.255 54.001,35.255C54.538,35.255 55.071,35.278 55.596,35.322L55.596,72.679C55.071,72.723 54.538,72.745 54.001,72.745ZM35.255,54.001C35.255,49.412 36.903,45.21 39.638,41.953L39.638,66.049C36.903,62.791 35.255,58.588 35.255,54.001ZM61.98,70.969C60.961,71.447 59.895,71.838 58.789,72.13L58.789,35.873C59.895,36.164 60.961,36.553 61.98,37.032L61.98,70.969ZM49.213,72.13C46.877,71.514 44.717,70.458 42.831,69.056L42.831,38.945C44.717,37.543 46.877,36.487 49.213,35.873L49.213,72.13Z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M54.034,44.513a9.535,9.428 0,1 0,19.07 0a9.535,9.428 0,1 0,-19.07 0z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="10"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M52,-884.362h29v71h-29z"/>
|
||||
<path
|
||||
android:pathData="m62.745,24.783c-2.055,1.011 -2.87,3.415 -1.847,5.446 0.588,1.168 1.642,1.916 2.839,2.168 -0.564,0.884 -0.835,1.972 -0.925,3.156 -1.782,-0.45 -3.735,-0.191 -5.25,0.639a0.799,0.808 90,0 0,-0.132 0.172,0.799 0.808,90 0,0 -0.132,0.172 0.799,0.808 90,0 0,-0.132 0.39,0.799 0.808,90 0,0 0.044,0.304 0.799,0.808 90,0 0,0.437 0.433,0.799 0.808,90 0,0 0.131,0.043 0.799,0.808 90,0 0,0.131 0.043,0.799 0.808,90 0,0 0.218,-0 0.799,0.808 90,0 0,0.263 -0.129c1.29,-0.707 2.97,-0.892 4.375,-0.425 0.096,0.882 0.341,1.782 0.695,2.683 -0.456,0.072 -0.936,0.177 -1.357,0.343 -1.207,0.48 -2.245,1.302 -3.022,2.244a0.799,0.808 90,0 0,-0.044 0.129,0.799 0.808,90 0,0 -0.088,0.258 0.799,0.808 90,0 0,0.044 0.304,0.799 0.808,90 0,0 0.393,0.563 0.799,0.808 90,0 0,0.394 0.131,0.799 0.808,90 0,0 0.175,-0.086 0.799,0.808 90,0 0,0.175 -0.086,0.799 0.808,90 0,0 0.219,-0.215c0.922,-1.115 2.409,-1.925 3.897,-2.025 0.494,0.845 1.016,1.575 1.658,2.209 -1.232,1.149 -2.043,2.664 -2.239,4.234a0.799,0.808 90,0 0,-0.001 0.215,0.799 0.808,90 0,0 0.174,0.346 0.799,0.808 90,0 0,0.043 0.086,0.799 0.808,90 0,0 0.219,0.217 0.799,0.808 90,0 0,0.131 0.043,0.799 0.808,90 0,0 0.131,0.043 0.799,0.808 90,0 0,0.218 -0,0.799 0.808,90 0,0 0.218,-0 0.799,0.808 90,0 0,0.132 -0.172,0.799 0.808,90 0,0 0.175,-0.086 0.799,0.808 90,0 0,0.044 -0.129,0.799 0.808,90 0,0 0.088,-0.258 0.799,0.808 90,0 0,0.044 -0.129c0.159,-1.272 0.816,-2.493 1.799,-3.371 1.966,1.394 4.252,1.797 6.122,0.876 1.892,-0.931 2.903,-3.018 2.941,-5.444 1.327,-0.259 2.813,0.014 3.936,0.656a0.799,0.808 90,0 0,0.174 0.129,0.799 0.808,90 0,0 0.131,0.043 0.799,0.808 90,0 0,0.088 -0.043,0.799 0.808,90 0,0 0.218,-0 0.799,0.808 90,0 0,0.087 -0.043,0.799 0.808,90 0,0 0.175,-0.086 0.799,0.808 90,0 0,0.132 -0.172,0.799 0.808,90 0,0 0.132,-0.172 0.799,0.808 90,0 0,0.045 -0.562,0.799 0.808,90 0,0 -0.043,-0.086 0.799,0.808 90,0 0,-0.262 -0.303,0.799 0.808,90 0,0 -0.131,-0.043c-1.386,-0.792 -3.097,-1.066 -4.766,-0.787 -0.125,-0.89 -0.309,-1.783 -0.695,-2.683 0.982,-1.073 2.47,-1.756 3.896,-1.81a0.799,0.808 90,0 0,0.131 0.043,0.799 0.808,90 0,0 0.087,-0.043 0.799,0.808 90,0 0,0.482 -0.345,0.799 0.808,90 0,0 0.044,-0.129 0.799,0.808 90,0 0,0.088 -0.476,0.799 0.808,90 0,0 -0.043,-0.086 0.799,0.808 90,0 0,-0.262 -0.303,0.799 0.808,90 0,0 -0.043,-0.086 0.799,0.808 90,0 0,-0.394 -0.131,0.799 0.808,90 0,0 -0.131,-0.043c-1.227,0.048 -2.518,0.369 -3.633,1.031 -0.388,0.233 -0.762,0.548 -1.095,0.864 -0.513,-0.821 -1.093,-1.559 -1.746,-2.166 0.47,-1.39 1.632,-2.605 2.98,-3.195a0.799,0.808 90,0 0,0.175 -0.086,0.799 0.808,90 0,0 0.132,-0.172 0.799,0.808 90,0 0,0.132 -0.172,0.799 0.808,90 0,0 0.132,-0.39 0.799,0.808 90,0 0,-0.043 -0.086,0.799 0.808,90 0,0 -0.174,-0.346 0.799,0.808 90,0 0,-0.043 -0.086,0.799 0.808,90 0,0 -0.437,-0.217 0.799,0.808 90,0 0,-0.175 0.086,0.799 0.808,90 0,0 -0.131,-0.043 0.799,0.808 90,0 0,-0.087 0.043,0.799 0.808,90 0,0 -0.131,-0.043c-1.584,0.694 -2.97,2.079 -3.681,3.756 -1.012,-0.644 -2.048,-1.087 -3.104,-1.173 0.53,-1.101 0.642,-2.414 0.05,-3.59 -1.023,-2.031 -3.541,-2.795 -5.596,-1.783z"
|
||||
android:pathData="m62.745,28.783c-2.055,1.011 -2.87,3.415 -1.847,5.446 0.588,1.168 1.642,1.916 2.839,2.168 -0.564,0.884 -0.835,1.972 -0.925,3.156 -1.782,-0.45 -3.735,-0.191 -5.25,0.639a0.799,0.808 90.001,0 0,-0.132 0.172,0.799 0.808,90.001 0,0 -0.132,0.172 0.799,0.808 90.001,0 0,-0.132 0.39,0.799 0.808,90.001 0,0 0.044,0.304 0.799,0.808 90.001,0 0,0.437 0.433,0.799 0.808,90.001 0,0 0.131,0.043 0.799,0.808 90.001,0 0,0.131 0.043,0.799 0.808,90.001 0,0 0.218,-0 0.799,0.808 90.001,0 0,0.263 -0.129c1.29,-0.707 2.97,-0.892 4.375,-0.425 0.096,0.882 0.341,1.782 0.695,2.683 -0.456,0.072 -0.936,0.177 -1.357,0.343 -1.207,0.48 -2.245,1.302 -3.022,2.244a0.799,0.808 90.001,0 0,-0.044 0.129,0.799 0.808,90.001 0,0 -0.088,0.258 0.799,0.808 90.001,0 0,0.044 0.304,0.799 0.808,90.001 0,0 0.393,0.563 0.799,0.808 90.001,0 0,0.394 0.131,0.799 0.808,90.001 0,0 0.175,-0.086 0.799,0.808 90.001,0 0,0.175 -0.086,0.799 0.808,90.001 0,0 0.219,-0.215c0.922,-1.115 2.409,-1.925 3.897,-2.025 0.494,0.845 1.016,1.575 1.658,2.209 -1.232,1.149 -2.043,2.664 -2.239,4.234a0.799,0.808 90.001,0 0,-0.001 0.215,0.799 0.808,90.001 0,0 0.174,0.346 0.799,0.808 90.001,0 0,0.043 0.086,0.799 0.808,90.001 0,0 0.219,0.217 0.799,0.808 90.001,0 0,0.131 0.043,0.799 0.808,90.001 0,0 0.131,0.043 0.799,0.808 90.001,0 0,0.218 -0,0.799 0.808,90.001 0,0 0.218,-0 0.799,0.808 90.001,0 0,0.132 -0.172,0.799 0.808,90.001 0,0 0.175,-0.086 0.799,0.808 90.001,0 0,0.044 -0.129,0.799 0.808,90.001 0,0 0.088,-0.258 0.799,0.808 90.001,0 0,0.044 -0.129c0.159,-1.272 0.816,-2.493 1.799,-3.371 1.966,1.394 4.252,1.797 6.122,0.876 1.892,-0.931 2.903,-3.018 2.941,-5.444 1.327,-0.259 2.813,0.014 3.936,0.656a0.799,0.808 90.001,0 0,0.174 0.129,0.799 0.808,90.001 0,0 0.131,0.043 0.799,0.808 90.001,0 0,0.088 -0.043,0.799 0.808,90.001 0,0 0.218,-0 0.799,0.808 90.001,0 0,0.087 -0.043,0.799 0.808,90.001 0,0 0.175,-0.086 0.799,0.808 90.001,0 0,0.132 -0.172,0.799 0.808,90.001 0,0 0.132,-0.172 0.799,0.808 90.001,0 0,0.045 -0.562,0.799 0.808,90.001 0,0 -0.043,-0.086 0.799,0.808 90.001,0 0,-0.262 -0.303,0.799 0.808,90.001 0,0 -0.131,-0.043c-1.386,-0.792 -3.097,-1.066 -4.766,-0.787 -0.125,-0.89 -0.309,-1.783 -0.695,-2.683 0.982,-1.073 2.47,-1.756 3.896,-1.81a0.799,0.808 90.001,0 0,0.131 0.043,0.799 0.808,90.001 0,0 0.087,-0.043 0.799,0.808 90.001,0 0,0.482 -0.345,0.799 0.808,90.001 0,0 0.044,-0.129 0.799,0.808 90.001,0 0,0.088 -0.476,0.799 0.808,90.001 0,0 -0.043,-0.086 0.799,0.808 90.001,0 0,-0.262 -0.303,0.799 0.808,90.001 0,0 -0.043,-0.086 0.799,0.808 90.001,0 0,-0.394 -0.131,0.799 0.808,90.001 0,0 -0.131,-0.043c-1.227,0.048 -2.518,0.369 -3.633,1.031 -0.388,0.233 -0.762,0.548 -1.095,0.864 -0.513,-0.821 -1.093,-1.559 -1.746,-2.166 0.47,-1.39 1.632,-2.605 2.98,-3.195a0.799,0.808 90.001,0 0,0.175 -0.086,0.799 0.808,90.001 0,0 0.132,-0.172 0.799,0.808 90.001,0 0,0.132 -0.172,0.799 0.808,90.001 0,0 0.132,-0.39 0.799,0.808 90.001,0 0,-0.043 -0.086,0.799 0.808,90.001 0,0 -0.174,-0.346 0.799,0.808 90.001,0 0,-0.043 -0.086,0.799 0.808,90.001 0,0 -0.437,-0.217 0.799,0.808 90.001,0 0,-0.175 0.086,0.799 0.808,90.001 0,0 -0.131,-0.043 0.799,0.808 90.001,0 0,-0.087 0.043,0.799 0.808,90.001 0,0 -0.131,-0.043c-1.584,0.694 -2.97,2.079 -3.681,3.756 -1.012,-0.644 -2.048,-1.087 -3.104,-1.173 0.53,-1.101 0.642,-2.414 0.05,-3.59 -1.023,-2.031 -3.541,-2.795 -5.596,-1.783z"
|
||||
android:fillColor="#ff2a2a"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"/>
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m66.624,32.489c0.281,-0.138 1.896,2.465 3.62,5.889 1.724,3.424 2.893,6.354 2.613,6.492 -0.281,0.138 -1.94,-2.551 -3.663,-5.975 -1.724,-3.424 -2.85,-6.268 -2.569,-6.406z"
|
||||
android:pathData="m66.624,36.489c0.281,-0.138 1.896,2.465 3.62,5.889 1.724,3.424 2.893,6.354 2.613,6.492 -0.281,0.138 -1.94,-2.551 -3.663,-5.975 -1.724,-3.424 -2.85,-6.268 -2.569,-6.406z"
|
||||
android:fillColor="#d40000"
|
||||
android:strokeColor="#00000000"
|
||||
android:fillAlpha="1"/>
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@@ -5,13 +5,16 @@
|
||||
<supports-screens
|
||||
android:largeScreens="true"
|
||||
android:xlargeScreens="true"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
|
||||
android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"
|
||||
android:maxSdkVersion="28"/>
|
||||
<uses-feature android:name="android.hardware.camera"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus"/>
|
||||
<uses-sdk tools:overrideLibrary="android.support.v14.preference"/>
|
||||
@@ -31,6 +34,7 @@
|
||||
android:usesCleartextTraffic="true"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:dataExtractionRules="@xml/extraction_rules"
|
||||
android:supportsRtl="true"
|
||||
android:resizeableActivity="true"
|
||||
android:label="@string/app_name"
|
||||
@@ -38,8 +42,8 @@
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".activity.SplashActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/SplashTheme">
|
||||
android:theme="@style/SplashTheme"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
@@ -50,7 +54,8 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.CameraActivity"
|
||||
android:label="@string/scan_code">
|
||||
android:label="@string/scan_code"
|
||||
android:exported="true">
|
||||
<intent-filter android:label="@string/compose_barcode">
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
@@ -64,15 +69,22 @@
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data
|
||||
android:host="www.markusfisch.de"
|
||||
android:pathPrefix="/BinaryEye"
|
||||
android:scheme="http"/>
|
||||
<data android:host="markusfisch.de"/>
|
||||
<data android:pathPrefix="/BinaryEye"/>
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="binaryeye" android:host="scan"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.PickActivity"
|
||||
android:label="@string/pick_code_to_scan"
|
||||
android:exported="true"
|
||||
tools:ignore="AppLinkUrlError">
|
||||
<intent-filter android:label="@string/decode_barcode">
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
@@ -87,8 +99,17 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true"/>
|
||||
<service
|
||||
android:name=".service.ScanTileService"
|
||||
android:icon="@drawable/ic_action_scan"
|
||||
android:label="@string/scan_code"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
|
||||
@@ -2,8 +2,8 @@ package de.markusfisch.android.binaryeye.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import de.markusfisch.android.binaryeye.app.execShareIntent
|
||||
import de.markusfisch.android.binaryeye.app.parseAndNormalizeUri
|
||||
import de.markusfisch.android.binaryeye.content.execShareIntent
|
||||
import de.markusfisch.android.binaryeye.content.openUrl
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
|
||||
interface IAction {
|
||||
@@ -18,31 +18,36 @@ abstract class IntentAction : IAction {
|
||||
abstract val errorMsg: Int
|
||||
|
||||
final override suspend fun execute(context: Context, data: ByteArray) {
|
||||
val intent = createIntent(context, data) ?: return context.toast(
|
||||
errorMsg
|
||||
)
|
||||
execShareIntent(context, intent)
|
||||
val intent = createIntent(context, data)
|
||||
if (intent == null) {
|
||||
context.toast(errorMsg)
|
||||
} else {
|
||||
context.execShareIntent(intent)
|
||||
}
|
||||
}
|
||||
|
||||
abstract suspend fun createIntent(context: Context, data: ByteArray): 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))
|
||||
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))
|
||||
context.openUrl(String(data))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package de.markusfisch.android.binaryeye.actions.search
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
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.urlEncode
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import java.net.URLEncoder
|
||||
import de.markusfisch.android.binaryeye.content.openUrl
|
||||
|
||||
object OpenOrSearchAction : IAction {
|
||||
override val iconResId: Int = R.drawable.ic_action_search
|
||||
@@ -18,28 +15,39 @@ object OpenOrSearchAction : IAction {
|
||||
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)
|
||||
view(context, String(data), true)
|
||||
}
|
||||
|
||||
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 -> context.toast(R.string.cannot_resolve_action)
|
||||
private suspend fun view(
|
||||
context: Context,
|
||||
url: String,
|
||||
search: Boolean
|
||||
) {
|
||||
if (!context.openUrl(url) && search) {
|
||||
openSearch(context, url)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun getSearchIntent(context: Context, query: String): Intent? {
|
||||
private suspend fun openSearch(context: Context, query: String) {
|
||||
val defaultSearchUrl = prefs.defaultSearchUrl
|
||||
if (defaultSearchUrl.isNotEmpty()) {
|
||||
view(
|
||||
context,
|
||||
defaultSearchUrl + query.urlEncode(),
|
||||
false
|
||||
)
|
||||
return
|
||||
}
|
||||
val names = context.resources.getStringArray(
|
||||
R.array.search_engines_names
|
||||
).toMutableList()
|
||||
val urls = context.resources.getStringArray(
|
||||
R.array.search_engines_values
|
||||
).toMutableList()
|
||||
// Remove the "Always ask" entry. The arrays search_engines_*
|
||||
// are used in the preferences too.
|
||||
names.removeFirst()
|
||||
urls.removeFirst()
|
||||
if (prefs.openWithUrl.isNotEmpty()) {
|
||||
names.add(prefs.openWithUrl)
|
||||
urls.add(prefs.openWithUrl)
|
||||
@@ -47,9 +55,9 @@ object OpenOrSearchAction : IAction {
|
||||
val queryUri = alertDialog<String>(context) { resume ->
|
||||
setTitle(R.string.pick_search_engine)
|
||||
setItems(names.toTypedArray()) { _, which ->
|
||||
resume(urls[which] + URLEncoder.encode(query, "utf-8"))
|
||||
resume(urls[which] + query.urlEncode())
|
||||
}
|
||||
} ?: return null
|
||||
return openUri(context, queryUri, false)
|
||||
} ?: return
|
||||
view(context, queryUri, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ 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)
|
||||
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
|
||||
@@ -28,7 +29,9 @@ object SmsAction : IntentAction() {
|
||||
Intent.ACTION_SENDTO,
|
||||
Uri.parse("smsto:$number")
|
||||
).apply {
|
||||
if (message.isNotEmpty()) putExtra("sms_body", message)
|
||||
if (message.isNotEmpty()) {
|
||||
putExtra("sms_body", message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ 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)
|
||||
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
|
||||
@@ -17,7 +19,11 @@ object TelAction : IntentAction() {
|
||||
return String(data).matches(telRegex)
|
||||
}
|
||||
|
||||
override suspend fun createIntent(context: Context, data: ByteArray): Intent {
|
||||
return Intent(Intent.ACTION_DIAL, parseAndNormalizeUri(String(data)))
|
||||
}
|
||||
override suspend fun createIntent(
|
||||
context: Context,
|
||||
data: ByteArray
|
||||
): Intent = Intent(
|
||||
Intent.ACTION_DIAL,
|
||||
String(data).parseAndNormalizeUri()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ object VTypeParser {
|
||||
|
||||
fun parseVType(data: String): String? = vTypeRegex.matchEntire(
|
||||
data
|
||||
)?.groupValues?.get(1)?.toUpperCase(Locale.US)
|
||||
)?.groupValues?.get(1)?.uppercase(Locale.US)
|
||||
|
||||
fun parseMap(
|
||||
data: String
|
||||
@@ -26,7 +26,7 @@ object VTypeParser {
|
||||
it.groupValues[2]
|
||||
)
|
||||
}
|
||||
}.groupBy({ it.first.toUpperCase(Locale.US) }) { it.second }
|
||||
}.groupBy({ it.first.uppercase(Locale.US) }) { it.second }
|
||||
}
|
||||
|
||||
data class VTypeProperty(
|
||||
|
||||
@@ -20,10 +20,19 @@ object VCardAction : IntentAction() {
|
||||
get() = R.string.vcard_failed
|
||||
|
||||
override fun canExecuteOn(data: ByteArray): Boolean {
|
||||
return VTypeParser.parseVType(String(data)) == "VCARD"
|
||||
var stringData = String(data)
|
||||
// Quick & dirty MECARD support: simply make it a VCARD.
|
||||
if (stringData.startsWith("MECARD:")) {
|
||||
stringData = stringData
|
||||
.trim()
|
||||
.replace("MECARD:", "BEGIN:VCARD\n")
|
||||
.replace(";;", "\nEND:VCARD\n")
|
||||
.replace(";", "\n")
|
||||
}
|
||||
return VTypeParser.parseVType(stringData) == "VCARD"
|
||||
}
|
||||
|
||||
override suspend fun createIntent(context: Context, data: ByteArray): Intent? {
|
||||
override suspend fun createIntent(context: Context, data: ByteArray): Intent {
|
||||
val info = VTypeParser.parseMap(String(data))
|
||||
|
||||
return Intent(Intent.ACTION_INSERT_OR_EDIT).apply {
|
||||
@@ -50,7 +59,7 @@ object VCardAction : IntentAction() {
|
||||
}
|
||||
putExtra(
|
||||
extraPhone,
|
||||
phoneProperty.value.toLowerCase(Locale.US).removePrefix("tel:")
|
||||
phoneProperty.value.lowercase(Locale.US).removePrefix("tel:")
|
||||
)
|
||||
}
|
||||
info["EMAIL"]?.forEachIndexed { index, mailProperty ->
|
||||
@@ -142,7 +151,7 @@ object VCardAction : IntentAction() {
|
||||
} ?: this
|
||||
|
||||
private val String.mailType: Int?
|
||||
get() = when (this.toUpperCase(Locale.US)) {
|
||||
get() = when (this.uppercase(Locale.US)) {
|
||||
"HOME" -> ContactsContract.CommonDataKinds.Email.TYPE_HOME
|
||||
"WORK" -> ContactsContract.CommonDataKinds.Email.TYPE_WORK
|
||||
"OTHER" -> ContactsContract.CommonDataKinds.Email.TYPE_OTHER
|
||||
@@ -151,7 +160,7 @@ object VCardAction : IntentAction() {
|
||||
}
|
||||
|
||||
private val String.phoneType: Int?
|
||||
get() = when (this.toUpperCase(Locale.US)) {
|
||||
get() = when (this.uppercase(Locale.US)) {
|
||||
"HOME" -> ContactsContract.CommonDataKinds.Phone.TYPE_HOME
|
||||
"MOBILE", "CELL" -> ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE
|
||||
"WORK" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK
|
||||
|
||||
@@ -56,7 +56,8 @@ object VEventAction : IntentAction() {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat") // we definitely don't wan't the local format
|
||||
// We definitely don't wan't the local format.
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
private val dateFormats = listOf(
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"),
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"),
|
||||
|
||||
@@ -10,6 +10,12 @@ import android.support.annotation.RequiresApi
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import java.util.*
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
fun removeNetworkSuggestions(context: Context): Int =
|
||||
(context.applicationContext.getSystemService(
|
||||
Context.WIFI_SERVICE
|
||||
) as WifiManager).removeNetworkSuggestions(listOf())
|
||||
|
||||
/**
|
||||
* Normal:
|
||||
* WIFI:S:[network SSID];T:<WPA|WEP|nopass|>;P:[network password];H:<true|false|>;;
|
||||
@@ -45,7 +51,7 @@ object WifiConnector {
|
||||
invoke(parsedData.password)
|
||||
}
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
// WifiConfiguration is deprecated in Android Q
|
||||
// WifiConfiguration is deprecated in Android Q.
|
||||
@Suppress("DEPRECATION")
|
||||
WifiConfiguration().apply(parsedData)
|
||||
} else {
|
||||
@@ -58,7 +64,7 @@ object WifiConnector {
|
||||
Context.WIFI_SERVICE
|
||||
) as WifiManager
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
// WifiConfiguration is deprecated in Android Q
|
||||
// WifiConfiguration is deprecated in Android Q.
|
||||
@Suppress("DEPRECATION")
|
||||
val wifiConfig = config as WifiConfiguration
|
||||
if (wifiManager.enableWifi() &&
|
||||
@@ -72,7 +78,7 @@ object WifiConnector {
|
||||
} else {
|
||||
val suggestion = (config as WifiNetworkSuggestion.Builder).build()
|
||||
val suggestions = listOf(suggestion)
|
||||
// remove previous conflicting network suggestion
|
||||
// Remove previous conflicting network suggestion.
|
||||
wifiManager.removeNetworkSuggestions(suggestions)
|
||||
val result = wifiManager.addNetworkSuggestions(suggestions)
|
||||
if (result == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
|
||||
@@ -84,17 +90,17 @@ object WifiConnector {
|
||||
}
|
||||
|
||||
internal fun parseMap(string: String): Map<String, String>? {
|
||||
// normally those codes should have the last semicolon, but many
|
||||
// generators don't add it
|
||||
// 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 Code creators don't escape properly
|
||||
// Should be: ^(.+):((?:[^\\;,":]|\\.)*);$ but allows unescaped , "
|
||||
// and : because many QR Code creators don't escape properly.
|
||||
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]
|
||||
pair.groupValues[1].uppercase(Locale.US) to pair.groupValues[2]
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
@@ -102,7 +108,7 @@ object WifiConnector {
|
||||
internal class SimpleDataAccessor private constructor(
|
||||
private val inputMap: Map<String, String>
|
||||
) {
|
||||
// this should be private but because of testing it isn't possible
|
||||
// This should be private but because of testing it isn't possible.
|
||||
internal val ssid: String
|
||||
get() = inputMap.getValue("S").unescape
|
||||
internal val securityType: String
|
||||
@@ -112,7 +118,7 @@ object WifiConnector {
|
||||
internal val hidden: Boolean
|
||||
get() = inputMap["H"]?.unescape == "true"
|
||||
internal val anonymousIdentity: String
|
||||
get() = inputMap["AI"]?.unescape ?: ""
|
||||
get() = inputMap["AI"]?.unescape ?: inputMap["A"]?.unescape ?: ""
|
||||
internal val identity: String
|
||||
get() = inputMap["I"]?.unescape ?: ""
|
||||
internal val eapMethod: Int?
|
||||
@@ -164,7 +170,7 @@ object WifiConnector {
|
||||
): WifiNetworkSuggestion.Builder? {
|
||||
fun WifiNetworkSuggestion.Builder.applyCommon(
|
||||
data: SimpleDataAccessor
|
||||
): WifiNetworkSuggestion.Builder? {
|
||||
): WifiNetworkSuggestion.Builder {
|
||||
setSsid(data.ssid)
|
||||
return this
|
||||
}
|
||||
@@ -172,47 +178,44 @@ object WifiConnector {
|
||||
fun WifiNetworkSuggestion.Builder.applySecurity(
|
||||
data: SimpleDataAccessor
|
||||
): WifiNetworkSuggestion.Builder? {
|
||||
when (data.securityType) {
|
||||
"WPA", "WPA2" -> {
|
||||
data.password?.let {
|
||||
setWpa2Passphrase(it)
|
||||
try {
|
||||
when (data.securityType) {
|
||||
"WPA", "WPA2" -> {
|
||||
data.password?.let { setWpa2Passphrase(it) }
|
||||
}
|
||||
"WPA2-EAP" -> {
|
||||
setWpa2EnterpriseConfig(WifiEnterpriseConfig().apply {
|
||||
identity = data.identity
|
||||
anonymousIdentity = data.anonymousIdentity
|
||||
password = data.password
|
||||
data.eapMethod?.let { eapMethod = it }
|
||||
data.phase2Method?.let { phase2Method = it }
|
||||
})
|
||||
}
|
||||
"WPA3" -> {
|
||||
data.password?.let { setWpa3Passphrase(it) }
|
||||
}
|
||||
"WPA3-EAP" -> {
|
||||
setWpa3EnterpriseConfig(WifiEnterpriseConfig().apply {
|
||||
identity = data.identity
|
||||
anonymousIdentity = data.anonymousIdentity
|
||||
password = data.password
|
||||
data.eapMethod?.let { eapMethod = it }
|
||||
data.phase2Method?.let { phase2Method = it }
|
||||
})
|
||||
}
|
||||
}
|
||||
"WPA2-EAP" -> {
|
||||
data.password?.let {
|
||||
setWpa2Passphrase(it)
|
||||
}
|
||||
setWpa2EnterpriseConfig(WifiEnterpriseConfig().apply {
|
||||
identity = data.identity
|
||||
anonymousIdentity = data.anonymousIdentity
|
||||
password = data.password
|
||||
data.eapMethod?.let { eapMethod = it }
|
||||
data.phase2Method?.let { phase2Method = it }
|
||||
})
|
||||
}
|
||||
"WPA3" -> {
|
||||
data.password?.let {
|
||||
setWpa3Passphrase(it)
|
||||
}
|
||||
}
|
||||
"WPA3-EAP" -> {
|
||||
data.password?.let { setWpa3Passphrase(it) }
|
||||
setWpa3EnterpriseConfig(WifiEnterpriseConfig().apply {
|
||||
identity = data.identity
|
||||
anonymousIdentity = data.anonymousIdentity
|
||||
password = data.password
|
||||
data.eapMethod?.let { eapMethod = it }
|
||||
data.phase2Method?.let { phase2Method = it }
|
||||
})
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// Don't accept passphrases that aren't ASCII encodeable.
|
||||
return null
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
return this.applyCommon(data)?.applySecurity(data)
|
||||
return this.applyCommon(data).applySecurity(data)
|
||||
}
|
||||
|
||||
// WifiConfiguration is deprecated in Android Q
|
||||
// WifiConfiguration is deprecated in Android Q.
|
||||
@Suppress("DEPRECATION")
|
||||
private fun WifiConfiguration.apply(
|
||||
data: SimpleDataAccessor
|
||||
@@ -222,7 +225,7 @@ object WifiConnector {
|
||||
|
||||
fun WifiConfiguration.applyCommon(
|
||||
data: SimpleDataAccessor
|
||||
): WifiConfiguration? {
|
||||
): WifiConfiguration {
|
||||
allowedAuthAlgorithms.clear()
|
||||
allowedGroupCiphers.clear()
|
||||
allowedKeyManagement.clear()
|
||||
@@ -274,7 +277,7 @@ object WifiConnector {
|
||||
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
|
||||
@Suppress("DEPRECATION") // TKIP is insecure and has bad performance.
|
||||
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
|
||||
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
|
||||
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)
|
||||
@@ -292,7 +295,7 @@ object WifiConnector {
|
||||
return this
|
||||
}
|
||||
|
||||
return this.applyCommon(data)?.applySecurity(data)
|
||||
return this.applyCommon(data).applySecurity(data)
|
||||
}
|
||||
|
||||
private inline fun <T> requireSdk(version: Int, block: () -> T): T? {
|
||||
@@ -300,8 +303,8 @@ object WifiConnector {
|
||||
}
|
||||
}
|
||||
|
||||
// keep possibility of wrongly unescaped \ by explicitly searching
|
||||
// for special chars
|
||||
// Keep possibility of wrongly unescaped \ by explicitly searching
|
||||
// for special chars.
|
||||
private val escapedRegex = """\\([\\;,":])""".toRegex()
|
||||
private val String.unescape: String
|
||||
get() = this.replace(escapedRegex) { escaped ->
|
||||
@@ -327,12 +330,12 @@ private fun String.isHex() = length == 64 && matches(hexRegex)
|
||||
|
||||
private fun WifiManager.enableWifi(): Boolean {
|
||||
// setWifiEnabled() will always return false for Android Q
|
||||
// because Q doesn't allow apps to enable/disable Wi-Fi anymore
|
||||
// because Q doesn't allow apps to enable/disable Wi-Fi anymore.
|
||||
@Suppress("DEPRECATION")
|
||||
return isWifiEnabled || setWifiEnabled(true)
|
||||
}
|
||||
|
||||
// WifiConfiguration is deprecated in Android Q
|
||||
// WifiConfiguration is deprecated in Android Q.
|
||||
@Suppress("DEPRECATION")
|
||||
private fun WifiManager.removeOldNetwork(
|
||||
wifiConfig: WifiConfiguration
|
||||
@@ -345,13 +348,13 @@ private fun WifiManager.removeOldNetwork(
|
||||
removeNetwork(it)
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
// the user didn't allow ACCESS_FINE_LOCATION which is
|
||||
// required to access configuredNetworks and that's fine
|
||||
// The user didn't allow ACCESS_FINE_LOCATION which is
|
||||
// required to access configuredNetworks and that's fine.
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// WifiConfiguration is deprecated in Android Q
|
||||
// WifiConfiguration is deprecated in Android Q.
|
||||
@Suppress("DEPRECATION")
|
||||
private fun WifiManager.enableNewNetwork(
|
||||
wifiConfig: WifiConfiguration
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package de.markusfisch.android.binaryeye.activity
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.hardware.Camera
|
||||
import android.media.AudioManager
|
||||
import android.media.ToneGenerator
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Vibrator
|
||||
@@ -22,15 +26,21 @@ import android.view.View
|
||||
import android.widget.SeekBar
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.ResultMetadataType
|
||||
import com.google.zxing.ResultPointCallback
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.adapter.prettifyFormatName
|
||||
import de.markusfisch.android.binaryeye.app.*
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
import de.markusfisch.android.binaryeye.graphics.Mapping
|
||||
import de.markusfisch.android.binaryeye.graphics.frameToView
|
||||
import de.markusfisch.android.binaryeye.graphics.isPortrait
|
||||
import de.markusfisch.android.binaryeye.content.copyToClipboard
|
||||
import de.markusfisch.android.binaryeye.content.execShareIntent
|
||||
import de.markusfisch.android.binaryeye.content.openUrl
|
||||
import de.markusfisch.android.binaryeye.database.toScan
|
||||
import de.markusfisch.android.binaryeye.graphics.getFrameToViewMatrix
|
||||
import de.markusfisch.android.binaryeye.graphics.map
|
||||
import de.markusfisch.android.binaryeye.net.sendAsync
|
||||
import de.markusfisch.android.binaryeye.os.error
|
||||
import de.markusfisch.android.binaryeye.os.getVibrator
|
||||
import de.markusfisch.android.binaryeye.os.vibrate
|
||||
import de.markusfisch.android.binaryeye.rs.Preprocessor
|
||||
import de.markusfisch.android.binaryeye.view.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.widget.DetectorView
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
@@ -41,15 +51,18 @@ import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class CameraActivity : AppCompatActivity() {
|
||||
private val zxing = Zxing(ResultPointCallback { point ->
|
||||
point?.let {
|
||||
getMapping()?.map(it)?.let {
|
||||
detectorView.post {
|
||||
detectorView.mark(listOf(it))
|
||||
}
|
||||
}
|
||||
private val zxing = Zxing { point ->
|
||||
point ?: return@Zxing
|
||||
val matrix = getMappingMatrix() ?: return@Zxing
|
||||
detectorView.post {
|
||||
detectorView.update(
|
||||
matrix.map(
|
||||
arrayOf(point),
|
||||
detectorView.coordinates
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private lateinit var rs: RenderScript
|
||||
private lateinit var vibrator: Vibrator
|
||||
@@ -59,16 +72,18 @@ class CameraActivity : AppCompatActivity() {
|
||||
private lateinit var flashFab: FloatingActionButton
|
||||
|
||||
private var preprocessor: Preprocessor? = null
|
||||
private var nativeMapping: Mapping? = null
|
||||
private var rotatedMapping: Mapping? = null
|
||||
private var nativeMappingMatrix: Matrix? = null
|
||||
private var rotatedMappingMatrix: Matrix? = null
|
||||
private var recreatePreprocessor = false
|
||||
private var rotate = false
|
||||
private var invert = false
|
||||
private var flash = false
|
||||
private var decoding = true
|
||||
private var returnResult = false
|
||||
private var returnUrlTemplate: String? = null
|
||||
private var finishAfterShowingResult = false
|
||||
private var frontFacing = false
|
||||
private var bulkMode = false
|
||||
private var bulkMode = prefs.bulkMode
|
||||
private var restrictFormat: String? = null
|
||||
private var ignoreNext: String? = null
|
||||
private var fallbackBuffer: IntArray? = null
|
||||
|
||||
@@ -113,11 +128,12 @@ class CameraActivity : AppCompatActivity() {
|
||||
super.onCreate(state)
|
||||
setContentView(R.layout.activity_camera)
|
||||
|
||||
// necessary to get the right translation after setting a custom locale
|
||||
// Necessary to get the right translation after setting a
|
||||
// custom locale.
|
||||
setTitle(R.string.scan_code)
|
||||
|
||||
rs = RenderScript.create(this)
|
||||
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
vibrator = getVibrator()
|
||||
|
||||
initSystemBars(this)
|
||||
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
|
||||
@@ -143,20 +159,52 @@ class CameraActivity : AppCompatActivity() {
|
||||
resetPreProcessor()
|
||||
fallbackBuffer = null
|
||||
saveZoom()
|
||||
saveCropHandlePos()
|
||||
detectorView.saveCropHandlePos()
|
||||
rs.destroy()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
System.gc()
|
||||
zxing.updateHints(prefs.tryHarder)
|
||||
returnResult = "com.google.zxing.client.android.SCAN" == intent.action
|
||||
if (hasCameraPermission(this)) {
|
||||
updateHints()
|
||||
if (prefs.bulkMode && bulkMode != prefs.bulkMode) {
|
||||
bulkMode = prefs.bulkMode
|
||||
invalidateOptionsMenu()
|
||||
ignoreNext = null
|
||||
}
|
||||
setReturnTarget(intent)
|
||||
if (hasCameraPermission()) {
|
||||
openCamera()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateHints() {
|
||||
val restriction = restrictFormat
|
||||
val formats = if (restriction != null) {
|
||||
title = getString(
|
||||
R.string.scan_format,
|
||||
prettifyFormatName(restriction)
|
||||
)
|
||||
setOf(restriction)
|
||||
} else {
|
||||
setTitle(R.string.scan_code)
|
||||
prefs.barcodeFormats
|
||||
}
|
||||
zxing.updateHints(prefs.tryHarder, formats)
|
||||
}
|
||||
|
||||
private fun setReturnTarget(intent: Intent?) {
|
||||
when {
|
||||
intent?.action == "com.google.zxing.client.android.SCAN" -> {
|
||||
returnResult = true
|
||||
}
|
||||
intent?.dataString?.isReturnUrl() == true -> {
|
||||
finishAfterShowingResult = true
|
||||
returnUrlTemplate = intent.data?.getQueryParameter("ret")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetPreProcessor() {
|
||||
preprocessor?.destroy()
|
||||
preprocessor = null
|
||||
@@ -182,8 +230,6 @@ class CameraActivity : AppCompatActivity() {
|
||||
|
||||
private fun closeCamera() {
|
||||
cameraView.close()
|
||||
// closing the camera will also shut off the flash
|
||||
flash = false
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedState: Bundle) {
|
||||
@@ -192,6 +238,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
zoomBar.progress = savedState.getInt(ZOOM_LEVEL)
|
||||
frontFacing = savedState.getBoolean(FRONT_FACING)
|
||||
bulkMode = savedState.getBoolean(BULK_MODE)
|
||||
restrictFormat = savedState.getString(RESTRICT_FORMAT)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@@ -199,12 +246,13 @@ class CameraActivity : AppCompatActivity() {
|
||||
outState.putInt(ZOOM_LEVEL, zoomBar.progress)
|
||||
outState.putBoolean(FRONT_FACING, frontFacing)
|
||||
outState.putBoolean(BULK_MODE, bulkMode)
|
||||
outState.putString(RESTRICT_FORMAT, restrictFormat)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||
// always give crop handle precedence over other controls
|
||||
// because it can easily overlap and would then be inaccessible
|
||||
// Always give crop handle precedence over other controls
|
||||
// because it can easily overlap and would then be inaccessible.
|
||||
if (detectorView.onTouchEvent(ev)) {
|
||||
return true
|
||||
}
|
||||
@@ -228,11 +276,11 @@ class CameraActivity : AppCompatActivity() {
|
||||
true
|
||||
}
|
||||
R.id.pick_file -> {
|
||||
val chooseFile = Intent(Intent.ACTION_GET_CONTENT)
|
||||
chooseFile.type = "image/*"
|
||||
startActivityForResult(
|
||||
Intent.createChooser(
|
||||
chooseFile,
|
||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
type = "image/*"
|
||||
},
|
||||
getString(R.string.pick_file)
|
||||
),
|
||||
PICK_FILE_RESULT_CODE
|
||||
@@ -246,6 +294,11 @@ class CameraActivity : AppCompatActivity() {
|
||||
R.id.bulk_mode -> {
|
||||
bulkMode = bulkMode xor true
|
||||
item.isChecked = bulkMode
|
||||
ignoreNext = null
|
||||
true
|
||||
}
|
||||
R.id.restrict_format -> {
|
||||
showRestrictionDialog()
|
||||
true
|
||||
}
|
||||
R.id.preferences -> {
|
||||
@@ -270,16 +323,33 @@ class CameraActivity : AppCompatActivity() {
|
||||
openCamera()
|
||||
}
|
||||
|
||||
private fun showRestrictionDialog() {
|
||||
val names = resources.getStringArray(
|
||||
R.array.barcode_formats_names
|
||||
).toMutableList()
|
||||
val formats = resources.getStringArray(
|
||||
R.array.barcode_formats_values
|
||||
).toMutableList()
|
||||
if (restrictFormat != null) {
|
||||
names.add(0, getString(R.string.remove_restriction))
|
||||
formats.add(0, null)
|
||||
}
|
||||
AlertDialog.Builder(this).apply {
|
||||
setTitle(R.string.restrict_format)
|
||||
setItems(names.toTypedArray()) { _, which ->
|
||||
restrictFormat = formats[which]
|
||||
updateHints()
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openReadme() {
|
||||
val intent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(getString(R.string.project_url))
|
||||
)
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent)
|
||||
} else {
|
||||
toast(R.string.project_url)
|
||||
}
|
||||
execShareIntent(intent)
|
||||
}
|
||||
|
||||
private fun handleSendText(intent: Intent) {
|
||||
@@ -321,8 +391,8 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
// stop calling focusTo() as soon as it returns false
|
||||
// to avoid throwing and catching future exceptions
|
||||
// Stop calling focusTo() as soon as it returns false
|
||||
// to avoid throwing and catching future exceptions.
|
||||
if (focus) {
|
||||
focus = cameraView.focusTo(v, event.x, event.y)
|
||||
if (focus) {
|
||||
@@ -340,7 +410,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
override fun onConfigureParameters(
|
||||
parameters: Camera.Parameters
|
||||
) {
|
||||
if (parameters.isZoomSupported) {
|
||||
zoomBar.visibility = if (parameters.isZoomSupported) {
|
||||
val max = parameters.maxZoom
|
||||
if (zoomBar.max != max) {
|
||||
zoomBar.max = max
|
||||
@@ -348,8 +418,9 @@ class CameraActivity : AppCompatActivity() {
|
||||
saveZoom()
|
||||
}
|
||||
parameters.zoom = zoomBar.progress
|
||||
View.VISIBLE
|
||||
} else {
|
||||
zoomBar.visibility = View.GONE
|
||||
View.GONE
|
||||
}
|
||||
val sceneModes = parameters.supportedSceneModes
|
||||
sceneModes?.let {
|
||||
@@ -370,9 +441,9 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onCameraReady(camera: Camera) {
|
||||
// reset preprocessor to make sure it always fits the current
|
||||
// frame orientation; important for landscape to landscape
|
||||
// orientation changes
|
||||
// Reset preprocessor to make sure it always fits the current
|
||||
// frame orientation. Important for landscape to landscape
|
||||
// orientation changes.
|
||||
resetPreProcessor()
|
||||
val frameWidth = cameraView.frameWidth
|
||||
val frameHeight = cameraView.frameHeight
|
||||
@@ -413,7 +484,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
progress: Int,
|
||||
fromUser: Boolean
|
||||
) {
|
||||
setZoom(progress)
|
||||
cameraView.camera?.setZoom(progress)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
@@ -424,16 +495,13 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun setZoom(zoom: Int) {
|
||||
val camera: Camera? = cameraView.camera
|
||||
camera?.let {
|
||||
try {
|
||||
val params = camera.parameters
|
||||
params.zoom = zoom
|
||||
camera.parameters = params
|
||||
} catch (e: RuntimeException) {
|
||||
// ignore; there's nothing we can do
|
||||
}
|
||||
private fun Camera.setZoom(zoom: Int) {
|
||||
try {
|
||||
val params = parameters
|
||||
params.zoom = zoom
|
||||
parameters = params
|
||||
} catch (e: RuntimeException) {
|
||||
// Ignore. There's nothing we can do.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,22 +529,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
recreatePreprocessor = true
|
||||
}
|
||||
detectorView.setPaddingFromWindowInsets()
|
||||
restoreCropHandlePos()
|
||||
}
|
||||
|
||||
private fun saveCropHandlePos() {
|
||||
val pos = detectorView.getCropHandlePos()
|
||||
prefs.cropHandleX = pos.x
|
||||
prefs.cropHandleY = pos.y
|
||||
prefs.cropHandleOrientation = detectorView.currentOrientation
|
||||
}
|
||||
|
||||
private fun restoreCropHandlePos() {
|
||||
detectorView.setCropHandlePos(
|
||||
prefs.cropHandleX,
|
||||
prefs.cropHandleY,
|
||||
prefs.cropHandleOrientation
|
||||
)
|
||||
detectorView.restoreCropHandlePos()
|
||||
}
|
||||
|
||||
private fun updateFlashFab(unavailable: Boolean) {
|
||||
@@ -491,20 +544,19 @@ class CameraActivity : AppCompatActivity() {
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun toggleTorchMode() {
|
||||
val camera = cameraView.camera
|
||||
val parameters = camera?.parameters
|
||||
parameters?.flashMode = if (flash) {
|
||||
val camera = cameraView.camera ?: return
|
||||
val parameters = camera.parameters ?: return
|
||||
parameters.flashMode = if (
|
||||
parameters.flashMode != Camera.Parameters.FLASH_MODE_OFF
|
||||
) {
|
||||
Camera.Parameters.FLASH_MODE_OFF
|
||||
} else {
|
||||
Camera.Parameters.FLASH_MODE_TORCH
|
||||
}
|
||||
parameters?.let {
|
||||
try {
|
||||
camera.parameters = parameters
|
||||
flash = flash xor true
|
||||
} catch (e: RuntimeException) {
|
||||
// ignore; there's nothing we can do
|
||||
}
|
||||
try {
|
||||
camera.parameters = parameters
|
||||
} catch (e: RuntimeException) {
|
||||
toast(e.message ?: getString(R.string.error_flash))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,10 +599,10 @@ class CameraActivity : AppCompatActivity() {
|
||||
zxing.decode(frameData, w, h, invert)
|
||||
} catch (e: RSRuntimeException) {
|
||||
prefs.forceCompat = prefs.forceCompat xor true
|
||||
// now the only option is to restart the app because
|
||||
// Now the only option is to restart the app because
|
||||
// RenderScript.forceCompat() needs to be called before
|
||||
// RenderScript is initialized
|
||||
restartApp(this)
|
||||
// RenderScript is initialized.
|
||||
restartApp()
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -576,13 +628,13 @@ class CameraActivity : AppCompatActivity() {
|
||||
} else {
|
||||
cameraView.previewRect
|
||||
}
|
||||
nativeMapping = frameToView(
|
||||
nativeMappingMatrix = getFrameToViewMatrix(
|
||||
pp.outWidth,
|
||||
pp.outHeight,
|
||||
frameOrientation,
|
||||
viewRect
|
||||
)
|
||||
rotatedMapping = frameToView(
|
||||
rotatedMappingMatrix = getFrameToViewMatrix(
|
||||
pp.outHeight,
|
||||
pp.outWidth,
|
||||
(frameOrientation - 90 + 360) % 360,
|
||||
@@ -603,12 +655,12 @@ class CameraActivity : AppCompatActivity() {
|
||||
) {
|
||||
null
|
||||
} else {
|
||||
// map ROI in detectorView to cameraView
|
||||
// Map ROI in detectorView to cameraView.
|
||||
val previewRect = cameraView.previewRect
|
||||
// previewRect may be larger than the screen (and thus as the
|
||||
// detectorView) in which case its left and/or top coordinate
|
||||
// will be negative which must then be clamped to the
|
||||
// screen/DetectorView
|
||||
// screen/DetectorView.
|
||||
val previewLeft = max(0, previewRect.left)
|
||||
val previewTop = max(0, previewRect.top)
|
||||
val previewRoi = Rect(
|
||||
@@ -625,52 +677,95 @@ class CameraActivity : AppCompatActivity() {
|
||||
previewRoi.right.toFloat() / previewRectWidth,
|
||||
previewRoi.bottom.toFloat() / previewRectHeight
|
||||
)
|
||||
// since the ROI is always centered and symmetric, we don't
|
||||
// need to distinguish between 0 and 180 or 90 and 270 degree
|
||||
if (isPortrait(frameOrientation)) {
|
||||
Rect(
|
||||
(normalizedRoi.top * frameWidth.toFloat()).roundToInt(),
|
||||
(normalizedRoi.left * frameHeight.toFloat()).roundToInt(),
|
||||
(normalizedRoi.bottom * frameWidth.toFloat()).roundToInt(),
|
||||
(normalizedRoi.right * frameHeight.toFloat()).roundToInt()
|
||||
// Since the ROI is always centered and symmetric, we don't
|
||||
// need to distinguish between 0 and 180 or 90 and 270 degree.
|
||||
val errectedRoi = if (isPortrait(frameOrientation)) {
|
||||
RectF(
|
||||
normalizedRoi.top,
|
||||
normalizedRoi.left,
|
||||
normalizedRoi.bottom,
|
||||
normalizedRoi.right
|
||||
)
|
||||
} else {
|
||||
Rect(
|
||||
(normalizedRoi.left * frameWidth.toFloat()).roundToInt(),
|
||||
(normalizedRoi.top * frameHeight.toFloat()).roundToInt(),
|
||||
(normalizedRoi.right * frameWidth.toFloat()).roundToInt(),
|
||||
(normalizedRoi.bottom * frameHeight.toFloat()).roundToInt()
|
||||
)
|
||||
normalizedRoi
|
||||
}
|
||||
Rect(
|
||||
(errectedRoi.left * frameWidth.toFloat()).roundToInt(),
|
||||
(errectedRoi.top * frameHeight.toFloat()).roundToInt(),
|
||||
(errectedRoi.right * frameWidth.toFloat()).roundToInt(),
|
||||
(errectedRoi.bottom * frameHeight.toFloat()).roundToInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun postResult(result: Result) {
|
||||
// get mapping for the current rotate value
|
||||
val mapping = getMapping()
|
||||
// Get matrix for the current rotate value.
|
||||
val matrix = getMappingMatrix()
|
||||
val resultPoints = result.resultPoints
|
||||
if (matrix == null ||
|
||||
resultPoints == null ||
|
||||
resultPoints.isEmpty()
|
||||
) {
|
||||
return
|
||||
}
|
||||
cameraView.post {
|
||||
val rp = result.resultPoints
|
||||
if (mapping != null && rp != null && rp.isNotEmpty()) {
|
||||
detectorView.mark(mapping.map(rp))
|
||||
}
|
||||
vibrator.vibrate()
|
||||
showResult(
|
||||
this@CameraActivity,
|
||||
result,
|
||||
returnResult,
|
||||
bulkMode
|
||||
detectorView.update(
|
||||
matrix.map(
|
||||
resultPoints,
|
||||
detectorView.coordinates
|
||||
)
|
||||
)
|
||||
vibrator.vibrate()
|
||||
val returnUri = returnUrlTemplate?.let {
|
||||
try {
|
||||
completeUrl(it, result)
|
||||
} catch (e: Exception) {
|
||||
e.message?.let { message ->
|
||||
toast(message)
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
when {
|
||||
returnResult -> {
|
||||
setResult(Activity.RESULT_OK, getReturnIntent(result))
|
||||
finish()
|
||||
}
|
||||
returnUri != null -> execShareIntent(
|
||||
Intent(Intent.ACTION_VIEW, returnUri)
|
||||
)
|
||||
else -> {
|
||||
showResult(
|
||||
this@CameraActivity,
|
||||
result,
|
||||
vibrator,
|
||||
bulkMode
|
||||
)
|
||||
// If this app was invoked via a deep link but without
|
||||
// a return URI, we probably don't want to return to
|
||||
// the camera screen after scanning, but to the caller.
|
||||
if (finishAfterShowingResult) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bulkMode) {
|
||||
ignoreNext = result.text
|
||||
toast(result.text)
|
||||
if (prefs.showToastInBulkMode) {
|
||||
toast(result.text)
|
||||
}
|
||||
detectorView.postDelayed({
|
||||
decoding = true
|
||||
}, 500)
|
||||
}, prefs.bulkModeDelay.toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMapping() = if (rotate) rotatedMapping else nativeMapping
|
||||
private fun getMappingMatrix() = if (rotate) {
|
||||
rotatedMappingMatrix
|
||||
} else {
|
||||
nativeMappingMatrix
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PICK_FILE_RESULT_CODE = 1
|
||||
@@ -678,29 +773,40 @@ class CameraActivity : AppCompatActivity() {
|
||||
private const val ZOOM_LEVEL = "zoom_level"
|
||||
private const val FRONT_FACING = "front_facing"
|
||||
private const val BULK_MODE = "bulk_mode"
|
||||
private const val RESTRICT_FORMAT = "restrict_format"
|
||||
}
|
||||
}
|
||||
|
||||
fun showResult(
|
||||
activity: Activity,
|
||||
result: Result,
|
||||
isResult: Boolean = false,
|
||||
bulkMode: Boolean = false
|
||||
vibrator: Vibrator,
|
||||
bulkMode: Boolean = false,
|
||||
) {
|
||||
if (isResult) {
|
||||
activity.setResult(Activity.RESULT_OK, getReturnIntent(result))
|
||||
activity.finish()
|
||||
return
|
||||
}
|
||||
if (prefs.copyImmediately) {
|
||||
activity.copyToClipboard(result.text)
|
||||
}
|
||||
val scan = Scan(result)
|
||||
if (prefs.sendScanUrl.isNotEmpty()) {
|
||||
val scan = result.toScan()
|
||||
if (prefs.useHistory) {
|
||||
scan.id = db.insertScan(scan)
|
||||
}
|
||||
if (prefs.sendScanActive && prefs.sendScanUrl.isNotEmpty()) {
|
||||
if (prefs.sendScanType == "4") {
|
||||
activity.openUrl(
|
||||
prefs.sendScanUrl + scan.content.urlEncode()
|
||||
)
|
||||
return
|
||||
}
|
||||
scan.sendAsync(
|
||||
prefs.sendScanUrl,
|
||||
prefs.sendScanType
|
||||
) { code, body ->
|
||||
if (code == null || code < 200 || code > 299) {
|
||||
vibrator.error()
|
||||
if (!activity.isSilent()) {
|
||||
beepBeepBeep()
|
||||
}
|
||||
}
|
||||
if (body != null && body.isNotEmpty()) {
|
||||
activity.toast(body)
|
||||
} else if (code == null || code > 299) {
|
||||
@@ -708,9 +814,6 @@ fun showResult(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prefs.useHistory) {
|
||||
scan.id = db.insertScan(scan)
|
||||
}
|
||||
if (!bulkMode) {
|
||||
activity.startActivity(
|
||||
MainActivity.getDecodeIntent(activity, scan)
|
||||
@@ -729,7 +832,7 @@ fun getReturnIntent(result: Result): Intent {
|
||||
intent.putExtra("SCAN_RESULT_BYTES", result.rawBytes)
|
||||
}
|
||||
result.resultMetadata?.let { metadata ->
|
||||
metadata[ResultMetadataType.UPC_EAN_EXTENSION]?.let {
|
||||
metadata[ResultMetadataType.ORIENTATION]?.let {
|
||||
intent.putExtra(
|
||||
"SCAN_RESULT_ORIENTATION",
|
||||
it.toString()
|
||||
@@ -758,3 +861,43 @@ fun getReturnIntent(result: Result): Intent {
|
||||
}
|
||||
return intent
|
||||
}
|
||||
|
||||
private fun String.isReturnUrl() = listOf(
|
||||
"binaryeye://scan",
|
||||
"http://markusfisch.de/BinaryEye",
|
||||
"https://markusfisch.de/BinaryEye"
|
||||
).firstOrNull { startsWith(it) } != null
|
||||
|
||||
private fun completeUrl(urlTemplate: String, result: Result) = Uri.parse(
|
||||
urlTemplate
|
||||
.replace("{RESULT}", result.text.urlEncode())
|
||||
.replace("{RESULT_BYTES}", result.rawBytes?.toHexString() ?: "")
|
||||
.replace(
|
||||
"{FORMAT}", result.barcodeFormat.toString().urlEncode()
|
||||
)
|
||||
.replace(
|
||||
"{META}", result.resultMetadata?.toString()?.urlEncode() ?: ""
|
||||
)
|
||||
// And support {CODE} from the old ZXing app, too.
|
||||
.replace("{CODE}", result.text.urlEncode())
|
||||
)
|
||||
|
||||
private fun Context.isSilent(): Boolean {
|
||||
val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
return when (am.ringerMode) {
|
||||
AudioManager.RINGER_MODE_SILENT,
|
||||
AudioManager.RINGER_MODE_VIBRATE -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun beepBeepBeep() {
|
||||
ToneGenerator(AudioManager.STREAM_ALARM, 100).startTone(
|
||||
ToneGenerator.TONE_CDMA_CONFIRM,
|
||||
1000
|
||||
)
|
||||
}
|
||||
|
||||
private fun isPortrait(
|
||||
orientation: Int
|
||||
) = orientation == 90 || orientation == 270
|
||||
|
||||
@@ -15,6 +15,8 @@ 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
|
||||
import de.markusfisch.android.binaryeye.view.colorSystemAndToolBars
|
||||
import de.markusfisch.android.binaryeye.view.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.view.recordToolbarHeight
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
@@ -63,7 +65,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
if (state == null) {
|
||||
setFragment(supportFragmentManager, getFragmentForIntent(intent))
|
||||
supportFragmentManager?.setFragment(getFragmentForIntent(intent))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,24 +16,28 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.google.zxing.Result
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.*
|
||||
import de.markusfisch.android.binaryeye.app.applyLocale
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.graphics.crop
|
||||
import de.markusfisch.android.binaryeye.graphics.loadImageUri
|
||||
import de.markusfisch.android.binaryeye.graphics.mapResult
|
||||
import de.markusfisch.android.binaryeye.graphics.mapResultToView
|
||||
import de.markusfisch.android.binaryeye.os.getVibrator
|
||||
import de.markusfisch.android.binaryeye.os.vibrate
|
||||
import de.markusfisch.android.binaryeye.rs.fixTransparency
|
||||
import de.markusfisch.android.binaryeye.view.colorSystemAndToolBars
|
||||
import de.markusfisch.android.binaryeye.view.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.view.recordToolbarHeight
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.widget.CropImageView
|
||||
import de.markusfisch.android.binaryeye.widget.DetectorView
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
class PickActivity : AppCompatActivity() {
|
||||
private val zxing = Zxing()
|
||||
private val parentJob = Job()
|
||||
private val scope = CoroutineScope(Dispatchers.IO + parentJob)
|
||||
|
||||
private lateinit var rs: RenderScript
|
||||
private lateinit var vibrator: Vibrator
|
||||
@@ -51,12 +55,16 @@ class PickActivity : AppCompatActivity() {
|
||||
super.onCreate(state)
|
||||
setContentView(R.layout.activity_pick)
|
||||
|
||||
// necessary to get the right translation after setting a custom locale
|
||||
// Necessary to get the right translation after setting a custom
|
||||
// locale.
|
||||
setTitle(R.string.pick_code_to_scan)
|
||||
|
||||
zxing.updateHints(true)
|
||||
zxing.updateHints(
|
||||
true,
|
||||
prefs.barcodeFormats
|
||||
)
|
||||
rs = RenderScript.create(this)
|
||||
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
vibrator = getVibrator()
|
||||
|
||||
initSystemBars(this)
|
||||
val toolbar = findViewById(R.id.toolbar) as Toolbar
|
||||
@@ -67,11 +75,7 @@ class PickActivity : AppCompatActivity() {
|
||||
colorSystemAndToolBars(this@PickActivity)
|
||||
}
|
||||
|
||||
val bitmap = fixTransparency(
|
||||
rs,
|
||||
getBitmapFromIntent()
|
||||
)
|
||||
|
||||
val bitmap = fixTransparency(rs, getBitmapFromIntent())
|
||||
if (bitmap == null) {
|
||||
applicationContext.toast(R.string.error_no_content)
|
||||
finish()
|
||||
@@ -90,6 +94,7 @@ class PickActivity : AppCompatActivity() {
|
||||
scanWithinBounds(bitmap)
|
||||
}
|
||||
detectorView.setPaddingFromWindowInsets()
|
||||
detectorView.restoreCropHandlePos()
|
||||
|
||||
findViewById(R.id.scan).setOnClickListener {
|
||||
showResult()
|
||||
@@ -111,7 +116,7 @@ class PickActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun scanWithinBounds(bitmap: Bitmap) {
|
||||
val roi = detectorView.roi
|
||||
val roi = Rect(detectorView.roi)
|
||||
val rectInView = if (roi.width() < 1) {
|
||||
cropImageView.getBoundsRect()
|
||||
} else {
|
||||
@@ -119,28 +124,31 @@ class PickActivity : AppCompatActivity() {
|
||||
}
|
||||
val imageRect = cropImageView.mappedRect
|
||||
val rectInImage = normalizeRoi(imageRect, rectInView)
|
||||
val cropped = crop(
|
||||
bitmap,
|
||||
val cropped = bitmap.crop(
|
||||
rectInImage,
|
||||
cropImageView.imageRotation
|
||||
) ?: return
|
||||
CoroutineScope(Dispatchers.IO).launch(Dispatchers.IO) {
|
||||
scope.launch {
|
||||
result = zxing.decodePositiveNegative(cropped)
|
||||
result?.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (!isFinishing) {
|
||||
vibrator.vibrate()
|
||||
if (isFinishing) {
|
||||
return@withContext
|
||||
}
|
||||
vibrator.vibrate()
|
||||
val rc = Rect()
|
||||
imageRect.round(rc)
|
||||
rectInView.intersect(rc)
|
||||
detectorView.mark(
|
||||
mapResult(
|
||||
if (!rectInView.intersect(rc)) {
|
||||
return@withContext
|
||||
}
|
||||
detectorView.update(
|
||||
mapResultToView(
|
||||
cropped.width,
|
||||
cropped.height,
|
||||
0,
|
||||
rectInView,
|
||||
it
|
||||
it.resultPoints,
|
||||
detectorView.coordinates
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -150,7 +158,9 @@ class PickActivity : AppCompatActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
detectorView.saveCropHandlePos()
|
||||
rs.destroy()
|
||||
parentJob.cancel()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
@@ -176,21 +186,22 @@ class PickActivity : AppCompatActivity() {
|
||||
val uri = intent.getParcelableExtra<Parcelable>(
|
||||
Intent.EXTRA_STREAM
|
||||
) as? Uri ?: return null
|
||||
return loadImageUri(contentResolver, uri)
|
||||
return contentResolver.loadImageUri(uri)
|
||||
}
|
||||
|
||||
private fun loadImageToOpen(intent: Intent): Bitmap? {
|
||||
val uri = intent.data ?: return null
|
||||
return loadImageUri(contentResolver, uri)
|
||||
return contentResolver.loadImageUri(uri)
|
||||
}
|
||||
|
||||
private fun showResult() {
|
||||
result?.let {
|
||||
showResult(this, it)
|
||||
val r = result
|
||||
if (r != null) {
|
||||
showResult(this, r, vibrator)
|
||||
finish()
|
||||
return
|
||||
} else {
|
||||
applicationContext.toast(R.string.no_barcode_found)
|
||||
}
|
||||
applicationContext.toast(R.string.no_barcode_found)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ class SplashActivity : AppCompatActivity() {
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
|
||||
// it's important _not_ to inflate a layout file here
|
||||
// It's important _not_ to inflate a layout file here
|
||||
// because that would happen after the app is fully
|
||||
// initialized what is too late
|
||||
// initialized what is too late.
|
||||
|
||||
setRestartCount(intent)
|
||||
intent?.setRestartCount()
|
||||
startActivity(Intent(applicationContext, CameraActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import android.widget.CursorAdapter
|
||||
import android.widget.TextView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.database.Database
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
CursorAdapter(context, cursor, false) {
|
||||
@@ -55,38 +58,41 @@ class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
context: Context,
|
||||
cursor: Cursor
|
||||
) {
|
||||
val holder = getViewHolder(view)
|
||||
holder.timeView.text = cursor.getString(timeIndex)
|
||||
val name = cursor.getString(nameIndex)
|
||||
val content = cursor.getString(contentIndex)
|
||||
var icon = 0
|
||||
holder.contentView.text = when {
|
||||
name?.isNotEmpty() == true -> {
|
||||
icon = R.drawable.ic_label
|
||||
name
|
||||
getViewHolder(view).apply {
|
||||
timeView.text = formatDateTime(cursor.getString(timeIndex))
|
||||
val name = cursor.getString(nameIndex)
|
||||
val content = cursor.getString(contentIndex)
|
||||
var icon = 0
|
||||
contentView.text = when {
|
||||
name?.isNotEmpty() == true -> {
|
||||
icon = R.drawable.ic_label
|
||||
name
|
||||
}
|
||||
content?.isEmpty() == true -> context.getString(
|
||||
R.string.binary_data
|
||||
)
|
||||
else -> content
|
||||
}
|
||||
content?.isEmpty() == true -> context.getString(R.string.binary_data)
|
||||
else -> content
|
||||
contentView.setCompoundDrawablesWithIntrinsicBounds(
|
||||
icon, 0, 0, 0
|
||||
)
|
||||
formatView.text = prettifyFormatName(cursor.getString(formatIndex))
|
||||
}
|
||||
holder.contentView.setCompoundDrawablesWithIntrinsicBounds(
|
||||
icon, 0, 0, 0
|
||||
)
|
||||
holder.formatView.text = cursor.getString(formatIndex)
|
||||
// view.isSelected needs to be put on the queue to work
|
||||
// view.isSelected needs to be put on the queue to work.
|
||||
val selected = cursor.getLong(idIndex) == selectedScanId
|
||||
view.post {
|
||||
view.isSelected = selected
|
||||
}
|
||||
}
|
||||
|
||||
private fun getViewHolder(view: View): ViewHolder {
|
||||
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
|
||||
}
|
||||
private fun getViewHolder(
|
||||
view: View
|
||||
): ViewHolder = view.tag as ViewHolder? ?: ViewHolder(
|
||||
view.findViewById(R.id.time),
|
||||
view.findViewById(R.id.content),
|
||||
view.findViewById(R.id.format)
|
||||
).also {
|
||||
view.tag = it
|
||||
}
|
||||
|
||||
private data class ViewHolder(
|
||||
@@ -95,3 +101,15 @@ class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
val formatView: TextView
|
||||
)
|
||||
}
|
||||
|
||||
fun prettifyFormatName(format: String) = format.replace("_", " ")
|
||||
|
||||
private fun formatDateTime(rfc: String): String {
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).parse(rfc)?.let {
|
||||
return DateFormat.getDateTimeInstance(
|
||||
DateFormat.LONG,
|
||||
DateFormat.SHORT
|
||||
).format(it)
|
||||
}
|
||||
return rfc
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.app.AlertDialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
@@ -11,7 +11,7 @@ suspend inline fun <T : Any> alertDialog(
|
||||
context: Context,
|
||||
crossinline build: AlertDialog.Builder.(resume: (T?) -> Unit) -> Unit
|
||||
): T? = withContext(Dispatchers.Main) {
|
||||
suspendCoroutine<T?> { continuation ->
|
||||
suspendCoroutine { continuation ->
|
||||
AlertDialog.Builder(context).apply {
|
||||
setOnCancelListener {
|
||||
continuation.resume(null)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.app.Application
|
||||
import android.support.multidex.MultiDexApplication;
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
import de.markusfisch.android.binaryeye.database.Database
|
||||
import de.markusfisch.android.binaryeye.preference.Preferences
|
||||
@@ -8,7 +8,7 @@ import de.markusfisch.android.binaryeye.preference.Preferences
|
||||
val db = Database()
|
||||
val prefs = Preferences()
|
||||
|
||||
class BinaryEyeApp : Application() {
|
||||
class BinaryEyeApp : MultiDexApplication() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
db.open(this)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentManager
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
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)
|
||||
@@ -2,13 +2,29 @@ package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.net.URLEncoder
|
||||
|
||||
private val nonPrintable = "[\\x00-\\x08\\x0e-\\x1f]".toRegex()
|
||||
fun String.hasNonPrintableCharacters() = nonPrintable.containsMatchIn(this)
|
||||
|
||||
fun hasNonPrintableCharacters(s: String) = nonPrintable.containsMatchIn(s)
|
||||
private val nonAlNum = "[^a-zA-Z0-9]".toRegex()
|
||||
private val multipleDots = "[…]+".toRegex()
|
||||
fun String.foldNonAlNum() = replace(nonAlNum, "…")
|
||||
.replace(multipleDots, "…")
|
||||
|
||||
fun parseAndNormalizeUri(input: String): Uri = Uri.parse(input).let {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) it.normalizeScheme() else it
|
||||
fun String.urlEncode(): String = try {
|
||||
URLEncoder.encode(this, "UTF-8")
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
this
|
||||
}
|
||||
|
||||
fun String.parseAndNormalizeUri(): Uri = Uri.parse(this).let {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
it.normalizeScheme()
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentManager
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
fun FragmentManager.setFragment(fragment: Fragment) {
|
||||
getTransaction(fragment).commit()
|
||||
}
|
||||
|
||||
fun FragmentManager.addFragment(fragment: Fragment) {
|
||||
getTransaction(fragment).addToBackStack(null).commit()
|
||||
}
|
||||
|
||||
@SuppressLint("CommitTransaction")
|
||||
private fun FragmentManager.getTransaction(
|
||||
fragment: Fragment
|
||||
) = beginTransaction().replace(R.id.content_frame, fragment)
|
||||
@@ -0,0 +1,29 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
private val urlPattern: Pattern = Pattern.compile(
|
||||
"(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" +
|
||||
"(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*" +
|
||||
"[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)",
|
||||
Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL
|
||||
)
|
||||
|
||||
fun getLinks(text: String): List<String> {
|
||||
val matcher = urlPattern.matcher(text)
|
||||
val urls = mutableListOf<String>()
|
||||
|
||||
while (matcher.find()) {
|
||||
val matchStart = matcher.start(1)
|
||||
val matchEnd = matcher.end()
|
||||
|
||||
var url = text.substring(matchStart, matchEnd)
|
||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||
url = "https://$url"
|
||||
}
|
||||
|
||||
urls.add(url)
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
@@ -5,16 +5,22 @@ import android.os.Build
|
||||
import java.util.*
|
||||
|
||||
fun Context.applyLocale(localeName: String) {
|
||||
if (localeName.isNotEmpty()) {
|
||||
val locale = Locale(localeName)
|
||||
Locale.setDefault(locale)
|
||||
val conf = resources.configuration
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
@Suppress("DEPRECATION")
|
||||
conf.locale = locale
|
||||
} else {
|
||||
conf.setLocale(locale)
|
||||
}
|
||||
resources.updateConfiguration(conf, resources.displayMetrics)
|
||||
if (localeName.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val localeParts = localeName.split("-")
|
||||
val locale = if (localeParts.size == 2) {
|
||||
Locale(localeParts[0], localeParts[1])
|
||||
} else {
|
||||
Locale(localeName)
|
||||
}
|
||||
Locale.setDefault(locale)
|
||||
val conf = resources.configuration
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
@Suppress("DEPRECATION")
|
||||
conf.locale = locale
|
||||
} else {
|
||||
conf.setLocale(locale)
|
||||
}
|
||||
resources.updateConfiguration(conf, resources.displayMetrics)
|
||||
}
|
||||
|
||||
@@ -9,43 +9,39 @@ import android.support.v4.content.ContextCompat
|
||||
var permissionGrantedCallback: (() -> Any)? = null
|
||||
|
||||
const val PERMISSION_CAMERA = 1
|
||||
fun hasCameraPermission(activity: Activity) = hasPermission(
|
||||
activity,
|
||||
fun Activity.hasCameraPermission() = hasPermission(
|
||||
Manifest.permission.CAMERA,
|
||||
PERMISSION_CAMERA
|
||||
)
|
||||
|
||||
const val PERMISSION_WRITE = 2
|
||||
fun hasWritePermission(activity: Activity, callback: () -> Any): Boolean {
|
||||
fun Activity.hasWritePermission(callback: () -> Any): Boolean {
|
||||
permissionGrantedCallback = callback
|
||||
return hasPermission(
|
||||
activity,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
PERMISSION_WRITE
|
||||
)
|
||||
}
|
||||
|
||||
const val PERMISSION_LOCATION = 3
|
||||
fun hasLocationPermission(activity: Activity, callback: () -> Any): Boolean {
|
||||
fun Activity.hasLocationPermission(callback: () -> Any): Boolean {
|
||||
permissionGrantedCallback = callback
|
||||
return hasPermission(
|
||||
activity,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
PERMISSION_LOCATION
|
||||
)
|
||||
}
|
||||
|
||||
private fun hasPermission(
|
||||
activity: Activity,
|
||||
private fun Activity.hasPermission(
|
||||
permission: String,
|
||||
requestCode: Int
|
||||
) = if (ContextCompat.checkSelfPermission(
|
||||
activity,
|
||||
this,
|
||||
permission
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
this,
|
||||
arrayOf(permission),
|
||||
requestCode
|
||||
)
|
||||
|
||||
@@ -8,19 +8,17 @@ private const val RESTART_COUNT = "restart_count"
|
||||
|
||||
private var restartCount = 0
|
||||
|
||||
fun setRestartCount(intent: Intent?) {
|
||||
intent?.let {
|
||||
restartCount = intent.getIntExtra(RESTART_COUNT, restartCount)
|
||||
}
|
||||
fun Intent.setRestartCount() {
|
||||
restartCount = getIntExtra(RESTART_COUNT, restartCount)
|
||||
}
|
||||
|
||||
fun restartApp(activity: Activity? = null) {
|
||||
if (activity != null && restartCount < 3) {
|
||||
val intent = Intent(activity, SplashActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
intent.putExtra(RESTART_COUNT, ++restartCount)
|
||||
activity.startActivity(intent)
|
||||
activity.finish()
|
||||
fun Activity.restartApp() {
|
||||
if (restartCount < 3) {
|
||||
startActivity(Intent(this, SplashActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
putExtra(RESTART_COUNT, ++restartCount)
|
||||
})
|
||||
finish()
|
||||
}
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
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 de.markusfisch.android.binaryeye.BuildConfig
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
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
|
||||
execShareIntent(context, intent)
|
||||
}
|
||||
|
||||
fun shareUri(context: Context, uri: Uri, type: String) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.type = type
|
||||
execShareIntent(context, intent)
|
||||
}
|
||||
|
||||
fun execShareIntent(context: Context, intent: Intent) {
|
||||
if (intent.resolveActivity(context.packageManager) != null) {
|
||||
context.startActivity(intent)
|
||||
} else {
|
||||
context.toast(R.string.cannot_resolve_action)
|
||||
}
|
||||
}
|
||||
|
||||
fun shareFile(context: Context, file: File, type: String) {
|
||||
getUriForFile(context, file)?.let {
|
||||
shareUri(context, it, 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,24 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.os.Build
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
|
||||
private const val MILLISECONDS = 100L
|
||||
|
||||
fun Vibrator.vibrate() {
|
||||
if (!prefs.vibrate) {
|
||||
return
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
@Suppress("DEPRECATION")
|
||||
this.vibrate(MILLISECONDS)
|
||||
} else {
|
||||
this.vibrate(
|
||||
VibrationEffect.createOneShot(
|
||||
MILLISECONDS,
|
||||
VibrationEffect.DEFAULT_AMPLITUDE
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
package de.markusfisch.android.binaryeye.content
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.Context
|
||||
@@ -0,0 +1,76 @@
|
||||
package de.markusfisch.android.binaryeye.content
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.support.v4.content.FileProvider
|
||||
import de.markusfisch.android.binaryeye.BuildConfig
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.parseAndNormalizeUri
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import java.io.File
|
||||
|
||||
fun Context.execShareIntent(intent: Intent): Boolean = if (
|
||||
!startIntent(intent)
|
||||
) {
|
||||
toast(R.string.cannot_resolve_action)
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
fun Context.startIntent(intent: Intent): Boolean = try {
|
||||
// Avoid using `intent.resolveActivity()` at API level 30+ due
|
||||
// to the new package visibility restrictions. In order for
|
||||
// `resolveActivity()` to "see" another package, we would need
|
||||
// to list that package/intent in a `<queries>` block in the
|
||||
// Manifest. But since we used `resolveActivity()` only to avoid
|
||||
// an exception if the Intent cannot be resolved, it's much easier
|
||||
// and more robust to just try and catch that exception if
|
||||
// necessary.
|
||||
startActivity(intent)
|
||||
true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
false
|
||||
}
|
||||
|
||||
fun Context.openUrl(url: String): Boolean = openUri(url.parseAndNormalizeUri())
|
||||
|
||||
fun Context.openUri(uri: Uri): Boolean = execShareIntent(
|
||||
Intent(Intent.ACTION_VIEW, uri)
|
||||
)
|
||||
|
||||
fun Context.shareText(text: String, mimeType: String = "text/plain") {
|
||||
execShareIntent(Intent(Intent.ACTION_SEND).apply {
|
||||
putExtra(Intent.EXTRA_TEXT, text)
|
||||
type = mimeType
|
||||
})
|
||||
}
|
||||
|
||||
fun Context.shareFile(file: File, mimeType: String) {
|
||||
getUriForFile(file)?.let {
|
||||
shareUri(it, mimeType)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.getUriForFile(file: File): Uri? = if (
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.N
|
||||
) {
|
||||
Uri.fromFile(file)
|
||||
} else {
|
||||
FileProvider.getUriForFile(
|
||||
this,
|
||||
BuildConfig.APPLICATION_ID + ".provider",
|
||||
file
|
||||
)
|
||||
}
|
||||
|
||||
private fun Context.shareUri(uri: Uri, mimeType: String) {
|
||||
execShareIntent(Intent(Intent.ACTION_SEND).apply {
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
type = mimeType
|
||||
})
|
||||
}
|
||||
@@ -3,22 +3,21 @@ package de.markusfisch.android.binaryeye.database
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import de.markusfisch.android.binaryeye.app.toHexString
|
||||
import de.markusfisch.android.binaryeye.app.writeExternalFile
|
||||
import de.markusfisch.android.binaryeye.io.writeExternalFile
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
fun exportCsv(
|
||||
context: Context,
|
||||
fun Context.exportCsv(
|
||||
name: String,
|
||||
cursor: Cursor,
|
||||
delimiter: String
|
||||
) = writeExternalFile(context, name, "text/csv") { outputStream ->
|
||||
) = writeExternalFile(name, "text/csv") { outputStream ->
|
||||
exportCsv(outputStream, cursor, delimiter)
|
||||
}
|
||||
|
||||
fun exportCsv(cursor: Cursor, delimiter: String): String? {
|
||||
fun Cursor.exportCsv(delimiter: String): String {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
exportCsv(outputStream, cursor, delimiter)
|
||||
exportCsv(outputStream, this, delimiter)
|
||||
return outputStream.toString()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package de.markusfisch.android.binaryeye.database
|
||||
|
||||
import android.database.Cursor
|
||||
|
||||
fun Cursor.getString(name: String): String {
|
||||
val idx = getColumnIndex(name)
|
||||
return if (idx < 0) "" else getString(idx) ?: ""
|
||||
}
|
||||
|
||||
fun Cursor.getBlob(name: String): ByteArray? {
|
||||
val idx = getColumnIndex(name)
|
||||
return if (idx < 0) null else getBlob(idx)
|
||||
}
|
||||
|
||||
fun Cursor.getLong(name: String): Long {
|
||||
val idx = getColumnIndex(name)
|
||||
return if (idx < 0) 0L else getLong(idx)
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import de.markusfisch.android.binaryeye.app.hasNonPrintableCharacters
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
|
||||
class Database {
|
||||
@@ -25,7 +24,7 @@ class Database {
|
||||
FROM $SCANS
|
||||
${getWhereClause(query)}
|
||||
ORDER BY $SCANS_DATETIME DESC
|
||||
""", getWhereArguments(query)
|
||||
""".trimMargin(), getWhereArguments(query)
|
||||
)
|
||||
|
||||
fun getScansDetailed(query: String? = null): Cursor? = db.rawQuery(
|
||||
@@ -47,7 +46,7 @@ class Database {
|
||||
FROM $SCANS
|
||||
${getWhereClause(query)}
|
||||
ORDER BY $SCANS_DATETIME DESC
|
||||
""", getWhereArguments(query)
|
||||
""".trimMargin(), getWhereArguments(query)
|
||||
)
|
||||
|
||||
private fun getWhereClause(
|
||||
@@ -87,7 +86,7 @@ class Database {
|
||||
$SCANS_UPC_EAN_EXTENSION
|
||||
FROM $SCANS
|
||||
WHERE $SCANS_ID = ?
|
||||
""", arrayOf("$id")
|
||||
""".trimMargin(), arrayOf("$id")
|
||||
)?.use {
|
||||
if (it.moveToFirst()) {
|
||||
Scan(
|
||||
@@ -110,47 +109,48 @@ class Database {
|
||||
}
|
||||
}
|
||||
|
||||
fun insertScan(scan: Scan): Long {
|
||||
val cv = ContentValues()
|
||||
cv.put(SCANS_DATETIME, scan.timestamp)
|
||||
val isRaw = hasNonPrintableCharacters(scan.content)
|
||||
if (isRaw) {
|
||||
cv.put(SCANS_CONTENT, "")
|
||||
cv.put(SCANS_RAW, scan.raw ?: scan.content.toByteArray())
|
||||
} else {
|
||||
cv.put(SCANS_CONTENT, scan.content)
|
||||
}
|
||||
cv.put(SCANS_FORMAT, scan.format)
|
||||
scan.errorCorrectionLevel?.let {
|
||||
cv.put(SCANS_ERROR_CORRECTION_LEVEL, it)
|
||||
}
|
||||
scan.issueNumber?.let { cv.put(SCANS_ISSUE_NUMBER, it) }
|
||||
scan.orientation?.let { cv.put(SCANS_ORIENTATION, it) }
|
||||
scan.otherMetaData?.let { cv.put(SCANS_OTHER_META_DATA, it) }
|
||||
scan.pdf417ExtraMetaData?.let { cv.put(SCANS_PDF417_EXTRA_METADATA, it) }
|
||||
scan.possibleCountry?.let { cv.put(SCANS_POSSIBLE_COUNTRY, it) }
|
||||
scan.suggestedPrice?.let { cv.put(SCANS_SUGGESTED_PRICE, it) }
|
||||
scan.upcEanExtension?.let { cv.put(SCANS_UPC_EAN_EXTENSION, it) }
|
||||
if (prefs.ignoreConsecutiveDuplicates) {
|
||||
val id = getIdOfLastScan(
|
||||
cv.get(SCANS_CONTENT) as String,
|
||||
if (isRaw) cv.get(SCANS_RAW) as ByteArray else null,
|
||||
scan.format
|
||||
)
|
||||
if (id > 0L) {
|
||||
return id
|
||||
fun insertScan(scan: Scan): Long = db.insert(
|
||||
SCANS,
|
||||
null,
|
||||
ContentValues().apply {
|
||||
put(SCANS_DATETIME, scan.dateTime)
|
||||
val isRaw = scan.raw != null
|
||||
if (isRaw) {
|
||||
put(SCANS_CONTENT, "")
|
||||
put(SCANS_RAW, scan.raw)
|
||||
} else {
|
||||
put(SCANS_CONTENT, scan.content)
|
||||
}
|
||||
put(SCANS_FORMAT, scan.format)
|
||||
scan.errorCorrectionLevel?.let {
|
||||
put(SCANS_ERROR_CORRECTION_LEVEL, it)
|
||||
}
|
||||
scan.issueNumber?.let { put(SCANS_ISSUE_NUMBER, it) }
|
||||
scan.orientation?.let { put(SCANS_ORIENTATION, it) }
|
||||
scan.otherMetaData?.let { put(SCANS_OTHER_META_DATA, it) }
|
||||
scan.pdf417ExtraMetaData?.let { put(SCANS_PDF417_EXTRA_METADATA, it) }
|
||||
scan.possibleCountry?.let { put(SCANS_POSSIBLE_COUNTRY, it) }
|
||||
scan.suggestedPrice?.let { put(SCANS_SUGGESTED_PRICE, it) }
|
||||
scan.upcEanExtension?.let { put(SCANS_UPC_EAN_EXTENSION, it) }
|
||||
if (prefs.ignoreConsecutiveDuplicates) {
|
||||
val id = getIdOfLastScan(
|
||||
get(SCANS_CONTENT) as String,
|
||||
if (isRaw) get(SCANS_RAW) as ByteArray else null,
|
||||
scan.format
|
||||
)
|
||||
if (id > 0L) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
return db.insert(SCANS, null, cv)
|
||||
}
|
||||
)
|
||||
|
||||
private fun getIdOfLastScan(
|
||||
content: String,
|
||||
raw: ByteArray?,
|
||||
format: String
|
||||
): Long {
|
||||
return db.rawQuery(
|
||||
"""SELECT
|
||||
): Long = db.rawQuery(
|
||||
"""SELECT
|
||||
$SCANS_ID,
|
||||
$SCANS_CONTENT,
|
||||
$SCANS_RAW,
|
||||
@@ -158,21 +158,20 @@ class Database {
|
||||
FROM $SCANS
|
||||
ORDER BY $SCANS_ID DESC
|
||||
LIMIT 1
|
||||
""", null
|
||||
)?.use {
|
||||
if (it.count > 0 &&
|
||||
it.moveToFirst() &&
|
||||
it.getString(it.getColumnIndex(SCANS_CONTENT)) == content &&
|
||||
(raw == null || it.getBlob(it.getColumnIndex(SCANS_RAW))
|
||||
?.contentEquals(raw) == true) &&
|
||||
it.getString(it.getColumnIndex(SCANS_FORMAT)) == format
|
||||
) {
|
||||
it.getLong(it.getColumnIndex(SCANS_ID))
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
} ?: 0L
|
||||
}
|
||||
""".trimMargin(), null
|
||||
)?.use {
|
||||
if (it.count > 0 &&
|
||||
it.moveToFirst() &&
|
||||
it.getString(SCANS_CONTENT) == content &&
|
||||
(raw == null || it.getBlob(SCANS_RAW)
|
||||
?.contentEquals(raw) == true) &&
|
||||
it.getString(SCANS_FORMAT) == format
|
||||
) {
|
||||
it.getLong(SCANS_ID)
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
} ?: 0L
|
||||
|
||||
fun removeScan(id: Long) {
|
||||
db.delete(SCANS, "$SCANS_ID = ?", arrayOf("$id"))
|
||||
@@ -230,7 +229,7 @@ class Database {
|
||||
const val SCANS_UPC_EAN_EXTENSION = "upc_ean_extension"
|
||||
|
||||
private fun createScans(db: SQLiteDatabase) {
|
||||
db.execSQL("DROP TABLE IF EXISTS $SCANS")
|
||||
db.execSQL("DROP TABLE IF EXISTS $SCANS".trimMargin())
|
||||
db.execSQL(
|
||||
"""CREATE TABLE $SCANS (
|
||||
$SCANS_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -247,39 +246,29 @@ class Database {
|
||||
$SCANS_POSSIBLE_COUNTRY TEXT,
|
||||
$SCANS_SUGGESTED_PRICE TEXT,
|
||||
$SCANS_UPC_EAN_EXTENSION TEXT
|
||||
)"""
|
||||
)""".trimMargin()
|
||||
)
|
||||
}
|
||||
|
||||
private fun addRawColumn(db: SQLiteDatabase) {
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_RAW BLOB")
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_RAW BLOB".trimMargin())
|
||||
}
|
||||
|
||||
private fun addMetaDataColumns(db: SQLiteDatabase) {
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_ERROR_CORRECTION_LEVEL TEXT")
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_ISSUE_NUMBER INT")
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_ORIENTATION INT")
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_OTHER_META_DATA TEXT")
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_PDF417_EXTRA_METADATA TEXT")
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_POSSIBLE_COUNTRY TEXT")
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_SUGGESTED_PRICE TEXT")
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_UPC_EAN_EXTENSION TEXT")
|
||||
db.apply {
|
||||
execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_ERROR_CORRECTION_LEVEL TEXT".trimMargin())
|
||||
execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_ISSUE_NUMBER INT".trimMargin())
|
||||
execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_ORIENTATION INT".trimMargin())
|
||||
execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_OTHER_META_DATA TEXT".trimMargin())
|
||||
execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_PDF417_EXTRA_METADATA TEXT".trimMargin())
|
||||
execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_POSSIBLE_COUNTRY TEXT".trimMargin())
|
||||
execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_SUGGESTED_PRICE TEXT".trimMargin())
|
||||
execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_UPC_EAN_EXTENSION TEXT".trimMargin())
|
||||
}
|
||||
}
|
||||
|
||||
private fun addNameColumn(db: SQLiteDatabase) {
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_NAME TEXT")
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_NAME TEXT".trimMargin())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Cursor.getString(name: String) = this.getString(
|
||||
this.getColumnIndex(name)
|
||||
)
|
||||
|
||||
private fun Cursor.getBlob(name: String) = this.getBlob(
|
||||
this.getColumnIndex(name)
|
||||
)
|
||||
|
||||
private fun Cursor.getLong(name: String) = this.getLong(
|
||||
this.getColumnIndex(name)
|
||||
)
|
||||
|
||||
@@ -2,20 +2,19 @@ package de.markusfisch.android.binaryeye.database
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Environment
|
||||
import de.markusfisch.android.binaryeye.app.writeExternalFile
|
||||
import de.markusfisch.android.binaryeye.io.writeExternalFile
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
fun exportDatabase(activity: Activity, fileName: String): Boolean {
|
||||
fun Activity.exportDatabase(fileName: String): Boolean {
|
||||
val dbFile = File(
|
||||
Environment.getDataDirectory(),
|
||||
"//data//${activity.packageName}//databases//${Database.FILE_NAME}"
|
||||
"//data//${packageName}//databases//${Database.FILE_NAME}"
|
||||
)
|
||||
if (!dbFile.exists()) {
|
||||
return false
|
||||
}
|
||||
return writeExternalFile(
|
||||
activity,
|
||||
fileName,
|
||||
"application/vnd.sqlite3"
|
||||
) {
|
||||
|
||||
@@ -3,23 +3,22 @@ package de.markusfisch.android.binaryeye.database
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import de.markusfisch.android.binaryeye.app.toHexString
|
||||
import de.markusfisch.android.binaryeye.app.writeExternalFile
|
||||
import de.markusfisch.android.binaryeye.io.writeExternalFile
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
fun exportJson(
|
||||
context: Context,
|
||||
fun Context.exportJson(
|
||||
name: String,
|
||||
cursor: Cursor
|
||||
) = writeExternalFile(context, name, "application/json") { outputStream ->
|
||||
exportJson(cursor)?.let {
|
||||
) = writeExternalFile(name, "application/json") { outputStream ->
|
||||
cursor.exportJson()?.let {
|
||||
outputStream.write(it.toByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
fun exportJson(cursor: Cursor): String? {
|
||||
if (!cursor.moveToFirst()) {
|
||||
fun Cursor.exportJson(): String? {
|
||||
if (!moveToFirst()) {
|
||||
return null
|
||||
}
|
||||
val columns = arrayOf(
|
||||
@@ -36,21 +35,21 @@ fun exportJson(cursor: Cursor): String? {
|
||||
Database.SCANS_UPC_EAN_EXTENSION
|
||||
)
|
||||
val indices = columns.map {
|
||||
Pair(cursor.getColumnIndex(it), it)
|
||||
Pair(getColumnIndex(it), it)
|
||||
}
|
||||
val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
|
||||
val rawIndex = cursor.getColumnIndex(Database.SCANS_RAW)
|
||||
val contentIndex = getColumnIndex(Database.SCANS_CONTENT)
|
||||
val rawIndex = getColumnIndex(Database.SCANS_RAW)
|
||||
val root = JSONArray()
|
||||
do {
|
||||
var deviation: Pair<Int, String>? = null
|
||||
if (cursor.getString(contentIndex)?.isEmpty() == true) {
|
||||
if (getString(contentIndex)?.isEmpty() == true) {
|
||||
deviation = Pair(
|
||||
contentIndex,
|
||||
cursor.getBlob(rawIndex).toHexString()
|
||||
getBlob(rawIndex).toHexString()
|
||||
)
|
||||
}
|
||||
root.put(cursor.toJsonObject(indices, deviation))
|
||||
} while (cursor.moveToNext())
|
||||
root.put(toJsonObject(indices, deviation))
|
||||
} while (moveToNext())
|
||||
return root.toString()
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Parcelable
|
||||
import android.text.format.DateFormat
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.ResultMetadataType
|
||||
import de.markusfisch.android.binaryeye.app.hasNonPrintableCharacters
|
||||
|
||||
data class Scan(
|
||||
val content: String,
|
||||
@@ -18,23 +19,9 @@ data class Scan(
|
||||
val possibleCountry: String?,
|
||||
val suggestedPrice: String?,
|
||||
val upcEanExtension: String?,
|
||||
val timestamp: String = getDateTime(),
|
||||
val dateTime: String = getDateTime(),
|
||||
var id: Long = 0L
|
||||
) : Parcelable {
|
||||
constructor(result: Result) : this(
|
||||
result.text,
|
||||
getRawBytes(result),
|
||||
result.barcodeFormat.toString(),
|
||||
result.getMetaString(ResultMetadataType.ERROR_CORRECTION_LEVEL),
|
||||
result.getMetaString(ResultMetadataType.ISSUE_NUMBER),
|
||||
result.getMetaString(ResultMetadataType.ORIENTATION),
|
||||
result.getMetaString(ResultMetadataType.OTHER),
|
||||
result.getMetaString(ResultMetadataType.PDF417_EXTRA_METADATA),
|
||||
result.getMetaString(ResultMetadataType.POSSIBLE_COUNTRY),
|
||||
result.getMetaString(ResultMetadataType.SUGGESTED_PRICE),
|
||||
result.getMetaString(ResultMetadataType.UPC_EAN_EXTENSION)
|
||||
)
|
||||
|
||||
// Needs to be overwritten manually, as ByteArray is an array and
|
||||
// this isn't handled well by Kotlin
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -48,7 +35,7 @@ data class Scan(
|
||||
other as Scan
|
||||
|
||||
return id == other.id &&
|
||||
timestamp == other.timestamp &&
|
||||
dateTime == other.dateTime &&
|
||||
content == other.content &&
|
||||
((raw == null && other.raw == null) ||
|
||||
(raw != null && other.raw != null && raw.contentEquals(other.raw))) &&
|
||||
@@ -67,7 +54,7 @@ data class Scan(
|
||||
// this isn't handled well by Kotlin
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + timestamp.hashCode()
|
||||
result = 31 * result + dateTime.hashCode()
|
||||
result = 31 * result + content.hashCode()
|
||||
result = 31 * result + (raw?.contentHashCode() ?: 0)
|
||||
result = 31 * result + format.hashCode()
|
||||
@@ -84,7 +71,7 @@ data class Scan(
|
||||
|
||||
private constructor(parcel: Parcel) : this(
|
||||
content = parcel.readString() ?: "",
|
||||
raw = readByteArray(parcel),
|
||||
raw = parcel.readSizedByteArray(),
|
||||
format = parcel.readString() ?: "",
|
||||
errorCorrectionLevel = parcel.readString(),
|
||||
issueNumber = parcel.readString(),
|
||||
@@ -94,24 +81,26 @@ data class Scan(
|
||||
possibleCountry = parcel.readString(),
|
||||
suggestedPrice = parcel.readString(),
|
||||
upcEanExtension = parcel.readString(),
|
||||
timestamp = parcel.readString() ?: "",
|
||||
dateTime = parcel.readString() ?: "",
|
||||
id = parcel.readLong()
|
||||
)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(content)
|
||||
writeByteArray(parcel, raw)
|
||||
parcel.writeString(format)
|
||||
parcel.writeString(errorCorrectionLevel)
|
||||
parcel.writeString(issueNumber)
|
||||
parcel.writeString(orientation)
|
||||
parcel.writeString(otherMetaData)
|
||||
parcel.writeString(pdf417ExtraMetaData)
|
||||
parcel.writeString(possibleCountry)
|
||||
parcel.writeString(suggestedPrice)
|
||||
parcel.writeString(upcEanExtension)
|
||||
parcel.writeString(timestamp)
|
||||
parcel.writeLong(id)
|
||||
parcel.apply {
|
||||
writeString(content)
|
||||
writeSizedByteArray(raw)
|
||||
writeString(format)
|
||||
writeString(errorCorrectionLevel)
|
||||
writeString(issueNumber)
|
||||
writeString(orientation)
|
||||
writeString(otherMetaData)
|
||||
writeString(pdf417ExtraMetaData)
|
||||
writeString(possibleCountry)
|
||||
writeString(suggestedPrice)
|
||||
writeString(upcEanExtension)
|
||||
writeString(dateTime)
|
||||
writeLong(id)
|
||||
}
|
||||
}
|
||||
|
||||
override fun describeContents() = 0
|
||||
@@ -130,17 +119,65 @@ private fun getDateTime(time: Long = System.currentTimeMillis()) = DateFormat.fo
|
||||
time
|
||||
).toString()
|
||||
|
||||
private fun getRawBytes(result: Result): ByteArray? {
|
||||
val metadata = result.resultMetadata ?: return null
|
||||
private fun Parcel.writeSizedByteArray(array: ByteArray?) {
|
||||
val size = array?.size ?: 0
|
||||
writeInt(size)
|
||||
if (size > 0) {
|
||||
writeByteArray(array)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Parcel.readSizedByteArray(): ByteArray? {
|
||||
val size = readInt()
|
||||
return if (size > 0) {
|
||||
val array = ByteArray(size)
|
||||
readByteArray(array)
|
||||
array
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun Result.toScan(): Scan {
|
||||
val content: String
|
||||
val raw: ByteArray?
|
||||
if (text.hasNonPrintableCharacters()) {
|
||||
content = ""
|
||||
raw = getRawData() ?: text.toByteArray()
|
||||
} else {
|
||||
content = text
|
||||
raw = null
|
||||
}
|
||||
return Scan(
|
||||
content,
|
||||
raw,
|
||||
barcodeFormat.toString(),
|
||||
getMetaString(ResultMetadataType.ERROR_CORRECTION_LEVEL),
|
||||
getMetaString(ResultMetadataType.ISSUE_NUMBER),
|
||||
getMetaString(ResultMetadataType.ORIENTATION),
|
||||
getMetaString(ResultMetadataType.OTHER),
|
||||
getMetaString(ResultMetadataType.PDF417_EXTRA_METADATA),
|
||||
getMetaString(ResultMetadataType.POSSIBLE_COUNTRY),
|
||||
getMetaString(ResultMetadataType.SUGGESTED_PRICE),
|
||||
getMetaString(ResultMetadataType.UPC_EAN_EXTENSION)
|
||||
)
|
||||
}
|
||||
|
||||
private fun Result.getRawData(): ByteArray? {
|
||||
val metadata = 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
|
||||
// If the byte segments are shorter than the converted string, the
|
||||
// content of the QR Code has been encoded with different encoding
|
||||
// modes (e.g. some parts in alphanumeric, some in byte encoding).
|
||||
// This is because Zxing only records byte segments for byte encoded
|
||||
// parts. Please note the byte segments can actually be longer than
|
||||
// the string because Zxing cuts off prefixes like "WIFI:".
|
||||
return if (bytes.size >= text.length) bytes else null
|
||||
}
|
||||
|
||||
private fun Result.getMetaString(
|
||||
@@ -148,22 +185,3 @@ private fun Result.getMetaString(
|
||||
): String? = this.resultMetadata?.let {
|
||||
it[key]?.toString()
|
||||
}
|
||||
|
||||
private fun writeByteArray(parcel: Parcel, array: ByteArray?) {
|
||||
val size = array?.size ?: 0
|
||||
parcel.writeInt(size)
|
||||
if (size > 0) {
|
||||
parcel.writeByteArray(array)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readByteArray(parcel: Parcel): ByteArray? {
|
||||
val size = parcel.readInt()
|
||||
return if (size > 0) {
|
||||
val array = ByteArray(size)
|
||||
parcel.readByteArray(array)
|
||||
array
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.view.*
|
||||
@@ -11,7 +12,13 @@ import android.widget.EditText
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.EncodeHintType
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.*
|
||||
import de.markusfisch.android.binaryeye.app.hasWritePermission
|
||||
import de.markusfisch.android.binaryeye.content.copyToClipboard
|
||||
import de.markusfisch.android.binaryeye.content.shareFile
|
||||
import de.markusfisch.android.binaryeye.content.shareText
|
||||
import de.markusfisch.android.binaryeye.io.addSuffixIfNotGiven
|
||||
import de.markusfisch.android.binaryeye.io.toSaveResult
|
||||
import de.markusfisch.android.binaryeye.io.writeExternalFile
|
||||
import de.markusfisch.android.binaryeye.view.doOnApplyWindowInsets
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.widget.ConfinedScalingImageView
|
||||
@@ -19,10 +26,7 @@ import de.markusfisch.android.binaryeye.widget.toast
|
||||
import de.markusfisch.android.binaryeye.zxing.encodeAsBitmap
|
||||
import de.markusfisch.android.binaryeye.zxing.encodeAsSvg
|
||||
import de.markusfisch.android.binaryeye.zxing.encodeAsText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
@@ -34,6 +38,9 @@ class BarcodeFragment : Fragment() {
|
||||
PNG, SVG, TXT
|
||||
}
|
||||
|
||||
private val parentJob = Job()
|
||||
private val scope = CoroutineScope(Dispatchers.IO + parentJob)
|
||||
|
||||
private var barcodeBitmap: Bitmap? = null
|
||||
private var barcodeSvg: String? = null
|
||||
private var barcodeTxt: String? = null
|
||||
@@ -87,13 +94,15 @@ class BarcodeFragment : Fragment() {
|
||||
)
|
||||
imageView.setImageBitmap(barcodeBitmap)
|
||||
imageView.post {
|
||||
// make sure to invoke this after ScalingImageView.onLayout()
|
||||
// Make sure to invoke this after ScalingImageView.onLayout().
|
||||
imageView.minWidth /= 2f
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.share).setOnClickListener {
|
||||
pickFileType(context, R.string.share_as) {
|
||||
shareAs(it)
|
||||
it.context?.apply {
|
||||
pickFileType(R.string.share_as) { fileType ->
|
||||
shareAs(fileType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +114,11 @@ class BarcodeFragment : Fragment() {
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
parentJob.cancel()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.fragment_barcode, menu)
|
||||
}
|
||||
@@ -119,7 +133,7 @@ class BarcodeFragment : Fragment() {
|
||||
true
|
||||
}
|
||||
R.id.export_to_file -> {
|
||||
pickFileType(context, R.string.export_as) {
|
||||
context.pickFileType(R.string.export_as) {
|
||||
askForFileNameAndSave(it)
|
||||
}
|
||||
true
|
||||
@@ -128,13 +142,12 @@ class BarcodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun pickFileType(
|
||||
context: Context,
|
||||
private fun Context.pickFileType(
|
||||
title: Int,
|
||||
action: (FileType) -> Unit
|
||||
) {
|
||||
val fileTypes = FileType.values()
|
||||
AlertDialog.Builder(context)
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(title)
|
||||
.setItems(fileTypes.map { it.name }.toTypedArray()) { _, which ->
|
||||
action(fileTypes[which])
|
||||
@@ -142,11 +155,14 @@ class BarcodeFragment : Fragment() {
|
||||
.show()
|
||||
}
|
||||
|
||||
// dialogs do not have a parent view
|
||||
// Dialogs do not have a parent view.
|
||||
@SuppressLint("InflateParams")
|
||||
private fun askForFileNameAndSave(fileType: FileType) {
|
||||
val ac = activity ?: return
|
||||
if (!hasWritePermission(ac) { askForFileNameAndSave(fileType) }) {
|
||||
// Write permission is only required before Android Q.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
|
||||
!ac.hasWritePermission { askForFileNameAndSave(fileType) }
|
||||
) {
|
||||
return
|
||||
}
|
||||
val view = ac.layoutInflater.inflate(R.layout.dialog_save_file, null)
|
||||
@@ -192,26 +208,26 @@ class BarcodeFragment : Fragment() {
|
||||
write: (outputStream: OutputStream) -> Unit
|
||||
) {
|
||||
val ac = activity ?: return
|
||||
GlobalScope.launch {
|
||||
val message = writeExternalFile(ac, fileName, mimeType, write).toSaveResult()
|
||||
GlobalScope.launch(Main) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val message = ac.writeExternalFile(fileName, mimeType, write).toSaveResult()
|
||||
withContext(Dispatchers.Main) {
|
||||
ac.toast(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareAs(fileType: FileType) {
|
||||
private fun Context.shareAs(fileType: FileType) {
|
||||
when (fileType) {
|
||||
FileType.PNG -> barcodeBitmap?.let { share(it) }
|
||||
FileType.SVG -> barcodeSvg?.let { shareText(context, it, MIME_SVG) }
|
||||
FileType.TXT -> barcodeTxt?.let { shareText(context, it) }
|
||||
FileType.SVG -> barcodeSvg?.let { shareText(it, MIME_SVG) }
|
||||
FileType.TXT -> barcodeTxt?.let { shareText(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun share(bitmap: Bitmap) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
private fun Context.share(bitmap: Bitmap) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val file = File(
|
||||
context.externalCacheDir,
|
||||
externalCacheDir,
|
||||
"shared_barcode.png"
|
||||
)
|
||||
val success = try {
|
||||
@@ -222,11 +238,11 @@ class BarcodeFragment : Fragment() {
|
||||
} catch (e: IOException) {
|
||||
false
|
||||
}
|
||||
GlobalScope.launch(Main) {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (success) {
|
||||
shareFile(context, file, "image/png")
|
||||
shareFile(file, "image/png")
|
||||
} else {
|
||||
activity?.toast(R.string.error_saving_file)
|
||||
toast(R.string.error_saving_file)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,10 +277,9 @@ class BarcodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Bitmap.saveAsPng(outputStream: OutputStream, quality: Int = 90) {
|
||||
this.compress(Bitmap.CompressFormat.PNG, quality, outputStream)
|
||||
}
|
||||
private fun Bitmap.saveAsPng(outputStream: OutputStream, quality: Int = 90) =
|
||||
compress(Bitmap.CompressFormat.PNG, quality, outputStream)
|
||||
|
||||
private val fileNameCharacters = "[^A-Za-z0-9]".toRegex()
|
||||
private fun encodeFileName(name: String): String =
|
||||
fileNameCharacters.replace(name, "_").take(16).trim('_').toLowerCase(Locale.getDefault())
|
||||
fileNameCharacters.replace(name, "_").take(16).trim('_').lowercase(Locale.getDefault())
|
||||
|
||||
@@ -14,9 +14,16 @@ import android.widget.TextView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.ActionRegistry
|
||||
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
|
||||
import de.markusfisch.android.binaryeye.actions.wifi.WifiConnector
|
||||
import de.markusfisch.android.binaryeye.activity.MainActivity
|
||||
import de.markusfisch.android.binaryeye.adapter.prettifyFormatName
|
||||
import de.markusfisch.android.binaryeye.app.*
|
||||
import de.markusfisch.android.binaryeye.content.copyToClipboard
|
||||
import de.markusfisch.android.binaryeye.content.shareText
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
import de.markusfisch.android.binaryeye.io.askForFileName
|
||||
import de.markusfisch.android.binaryeye.io.toSaveResult
|
||||
import de.markusfisch.android.binaryeye.io.writeExternalFile
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -27,6 +34,7 @@ import kotlinx.coroutines.launch
|
||||
class DecodeFragment : Fragment() {
|
||||
private lateinit var contentView: EditText
|
||||
private lateinit var formatView: TextView
|
||||
private lateinit var dataView: TableLayout
|
||||
private lateinit var metaView: TableLayout
|
||||
private lateinit var hexView: TextView
|
||||
private lateinit var format: String
|
||||
@@ -40,6 +48,7 @@ class DecodeFragment : Fragment() {
|
||||
private var closeAutomatically = false
|
||||
private var action = ActionRegistry.DEFAULT_ACTION
|
||||
private var isBinary = false
|
||||
private var raw: ByteArray = ByteArray(0)
|
||||
private var id = 0L
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
@@ -68,10 +77,8 @@ class DecodeFragment : Fragment() {
|
||||
id = scan.id
|
||||
|
||||
val inputContent = scan.content
|
||||
isBinary = hasNonPrintableCharacters(
|
||||
inputContent
|
||||
) or inputContent.isEmpty()
|
||||
val raw = scan.raw ?: inputContent.toByteArray()
|
||||
isBinary = scan.raw != null
|
||||
raw = scan.raw ?: inputContent.toByteArray()
|
||||
format = scan.format
|
||||
|
||||
contentView = view.findViewById(R.id.content)
|
||||
@@ -107,7 +114,7 @@ class DecodeFragment : Fragment() {
|
||||
executeAction(content.toByteArray())
|
||||
}
|
||||
} else {
|
||||
contentView.setText(R.string.binary_data)
|
||||
contentView.setText(String(raw).foldNonAlNum())
|
||||
contentView.isEnabled = false
|
||||
fab.setImageResource(R.drawable.ic_action_save)
|
||||
fab.setOnClickListener {
|
||||
@@ -116,11 +123,16 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
|
||||
formatView = view.findViewById(R.id.format)
|
||||
dataView = view.findViewById(R.id.data)
|
||||
metaView = view.findViewById(R.id.meta)
|
||||
hexView = view.findViewById(R.id.hex)
|
||||
|
||||
updateViewsAndAction(raw)
|
||||
|
||||
if (!isBinary) {
|
||||
fillDataView(dataView, inputContent)
|
||||
}
|
||||
|
||||
if (prefs.showMetaData) {
|
||||
fillMetaView(metaView, scan)
|
||||
}
|
||||
@@ -131,9 +143,9 @@ class DecodeFragment : Fragment() {
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
parentJob.cancel()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun updateViewsAndAction(bytes: ByteArray) {
|
||||
@@ -144,7 +156,7 @@ class DecodeFragment : Fragment() {
|
||||
formatView.text = resources.getQuantityString(
|
||||
R.plurals.barcode_info,
|
||||
bytes.size,
|
||||
format,
|
||||
prettifyFormatName(format),
|
||||
bytes.size
|
||||
)
|
||||
hexView.text = if (prefs.showHexDump) hexDump(bytes) else ""
|
||||
@@ -161,34 +173,64 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun fillDataView(tableLayout: TableLayout, content: String) {
|
||||
val items = LinkedHashMap<Int, String?>()
|
||||
if (action is WifiAction) {
|
||||
WifiConnector.parseMap(content)?.let { wifiData ->
|
||||
items.putAll(
|
||||
linkedMapOf(
|
||||
R.string.entry_type to getString(R.string.wifi_network),
|
||||
R.string.wifi_ssid to wifiData["S"],
|
||||
R.string.wifi_password to wifiData["P"],
|
||||
R.string.wifi_type to wifiData["T"],
|
||||
R.string.wifi_hidden to wifiData["H"],
|
||||
R.string.wifi_eap to wifiData["E"],
|
||||
R.string.wifi_identity to wifiData["I"],
|
||||
R.string.wifi_anonymous_identity to wifiData["A"],
|
||||
R.string.wifi_phase2 to wifiData["PH2"]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
fillDataTable(tableLayout, items)
|
||||
}
|
||||
|
||||
private fun fillMetaView(tableLayout: TableLayout, scan: Scan) {
|
||||
fillDataTable(
|
||||
tableLayout, linkedMapOf(
|
||||
R.string.error_correction_level to scan.errorCorrectionLevel,
|
||||
R.string.issue_number to scan.issueNumber,
|
||||
R.string.orientation to scan.orientation,
|
||||
R.string.other_meta_data to scan.otherMetaData,
|
||||
R.string.pdf417_extra_metadata to scan.pdf417ExtraMetaData,
|
||||
R.string.possible_country to scan.possibleCountry,
|
||||
R.string.suggested_price to scan.suggestedPrice,
|
||||
R.string.upc_ean_extension to scan.upcEanExtension
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun fillDataTable(
|
||||
tableLayout: TableLayout,
|
||||
items: LinkedHashMap<Int, String?>
|
||||
) {
|
||||
val ctx = tableLayout.context
|
||||
val spaceBetween = (16f * ctx.resources.displayMetrics.density).toInt()
|
||||
var hasMeta = false
|
||||
sortedMapOf(
|
||||
R.string.error_correction_level to scan.errorCorrectionLevel,
|
||||
R.string.issue_number to scan.issueNumber,
|
||||
R.string.orientation to scan.orientation,
|
||||
R.string.other_meta_data to scan.otherMetaData,
|
||||
R.string.pdf417_extra_metadata to scan.pdf417ExtraMetaData,
|
||||
R.string.possible_country to scan.possibleCountry,
|
||||
R.string.suggested_price to scan.suggestedPrice,
|
||||
R.string.upc_ean_extension to scan.upcEanExtension
|
||||
).forEach { item ->
|
||||
item.value?.let {
|
||||
items.forEach { item ->
|
||||
val text = item.value
|
||||
if (!text.isNullOrBlank()) {
|
||||
val tr = TableRow(ctx)
|
||||
val keyView = TextView(ctx)
|
||||
keyView.setText(item.key)
|
||||
val valueView = TextView(ctx)
|
||||
valueView.setPadding(spaceBetween, 0, 0, 0)
|
||||
valueView.text = it
|
||||
valueView.text = text
|
||||
tr.addView(keyView)
|
||||
tr.addView(valueView)
|
||||
tableLayout.addView(tr)
|
||||
hasMeta = true
|
||||
}
|
||||
}
|
||||
if (hasMeta) {
|
||||
if (tableLayout.childCount > 0) {
|
||||
tableLayout.setPadding(0, 0, 0, spaceBetween)
|
||||
}
|
||||
}
|
||||
@@ -215,20 +257,19 @@ class DecodeFragment : Fragment() {
|
||||
true
|
||||
}
|
||||
R.id.copy_to_clipboard -> {
|
||||
copyToClipboard(content)
|
||||
copyToClipboard(textOrHex())
|
||||
maybeBackOrFinish()
|
||||
true
|
||||
}
|
||||
R.id.share -> {
|
||||
context?.also {
|
||||
shareText(it, content)
|
||||
context?.apply {
|
||||
shareText(textOrHex())
|
||||
maybeBackOrFinish()
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.create -> {
|
||||
addFragment(
|
||||
fragmentManager,
|
||||
fragmentManager?.addFragment(
|
||||
EncodeFragment.newInstance(content, format)
|
||||
)
|
||||
true
|
||||
@@ -242,6 +283,12 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun textOrHex() = if (isBinary) {
|
||||
raw.toHexString()
|
||||
} else {
|
||||
content
|
||||
}
|
||||
|
||||
private fun copyPasswordToClipboard() {
|
||||
val ac = action
|
||||
if (ac is WifiAction) {
|
||||
@@ -281,7 +328,7 @@ class DecodeFragment : Fragment() {
|
||||
if (content.isNotEmpty()) {
|
||||
if (action is WifiAction &&
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
|
||||
!hasLocationPermission(ac) { executeAction(content) }
|
||||
!ac.hasLocationPermission { executeAction(content) }
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -294,13 +341,15 @@ class DecodeFragment : Fragment() {
|
||||
|
||||
private fun askForFileNameAndSave(raw: ByteArray) {
|
||||
val ac = activity ?: return
|
||||
if (!hasWritePermission(ac) { askForFileNameAndSave(raw) }) {
|
||||
// Write permission is only required before Android Q.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
|
||||
!ac.hasWritePermission { askForFileNameAndSave(raw) }
|
||||
) {
|
||||
return
|
||||
}
|
||||
scope.launch(Dispatchers.Main) {
|
||||
val name = ac.askForFileName() ?: return@launch
|
||||
val message = writeExternalFile(
|
||||
ac,
|
||||
val message = ac.writeExternalFile(
|
||||
name,
|
||||
"application/octet-stream"
|
||||
) {
|
||||
@@ -336,7 +385,7 @@ private fun hexDump(bytes: ByteArray, charsPerLine: Int = 33): String {
|
||||
while (true) {
|
||||
val ord = bytes[i]
|
||||
hex.append(String.format("%02X ", ord))
|
||||
ascii.append(if (ord > 31) ord.toChar() else " ")
|
||||
ascii.append(if (ord > 31) ord.toInt().toChar() else " ")
|
||||
++i
|
||||
val posInLine = i % itemsPerLine
|
||||
val atEnd = i >= len
|
||||
|
||||
@@ -10,9 +10,10 @@ import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.EncodeHintType
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.adapter.prettifyFormatName
|
||||
import de.markusfisch.android.binaryeye.app.addFragment
|
||||
import de.markusfisch.android.binaryeye.app.hideSoftKeyboard
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.view.hideSoftKeyboard
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import java.util.*
|
||||
|
||||
@@ -56,7 +57,7 @@ class EncodeFragment : Fragment() {
|
||||
val formatAdapter = ArrayAdapter(
|
||||
ac,
|
||||
android.R.layout.simple_spinner_item,
|
||||
writers.map { it.name }
|
||||
writers.map { prettifyFormatName(it.name) }
|
||||
)
|
||||
formatAdapter.setDropDownViewResource(
|
||||
android.R.layout.simple_spinner_dropdown_item
|
||||
@@ -144,8 +145,7 @@ class EncodeFragment : Fragment() {
|
||||
).show()
|
||||
} else {
|
||||
activity?.hideSoftKeyboard(contentView)
|
||||
addFragment(
|
||||
fragmentManager,
|
||||
fragmentManager?.addFragment(
|
||||
BarcodeFragment.newInstance(content, format, size, hints)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,12 +21,16 @@ import android.widget.ListView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.adapter.ScansAdapter
|
||||
import de.markusfisch.android.binaryeye.app.*
|
||||
import de.markusfisch.android.binaryeye.content.copyToClipboard
|
||||
import de.markusfisch.android.binaryeye.content.shareText
|
||||
import de.markusfisch.android.binaryeye.database.Database
|
||||
import de.markusfisch.android.binaryeye.database.getString
|
||||
import de.markusfisch.android.binaryeye.database.exportCsv
|
||||
import de.markusfisch.android.binaryeye.database.exportDatabase
|
||||
import de.markusfisch.android.binaryeye.database.exportJson
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.view.useVisibility
|
||||
import de.markusfisch.android.binaryeye.io.askForFileName
|
||||
import de.markusfisch.android.binaryeye.io.toSaveResult
|
||||
import de.markusfisch.android.binaryeye.view.*
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
@@ -221,7 +225,7 @@ class HistoryFragment : Fragment() {
|
||||
true
|
||||
}
|
||||
R.id.export_history -> {
|
||||
askToExportToFile(context)
|
||||
askToExportToFile()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
@@ -262,7 +266,7 @@ class HistoryFragment : Fragment() {
|
||||
View.GONE
|
||||
}
|
||||
cursor?.let { cursor ->
|
||||
// close previous cursor
|
||||
// Close previous cursor.
|
||||
scansAdapter?.also { it.changeCursor(null) }
|
||||
scansAdapter = ScansAdapter(ac, cursor)
|
||||
listView.adapter = scansAdapter
|
||||
@@ -290,21 +294,18 @@ class HistoryFragment : Fragment() {
|
||||
private fun showScan(id: Long) = db.getScan(id)?.also { scan ->
|
||||
closeActionMode()
|
||||
try {
|
||||
addFragment(
|
||||
fragmentManager,
|
||||
DecodeFragment.newInstance(scan)
|
||||
)
|
||||
fragmentManager?.addFragment(DecodeFragment.newInstance(scan))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// ignore, can never happen
|
||||
// Ignore, can never happen.
|
||||
}
|
||||
}
|
||||
|
||||
private fun getScanName(position: Int): String? {
|
||||
val cursor = scansAdapter?.getItem(position) as Cursor?
|
||||
return cursor?.getString(cursor.getColumnIndex(Database.SCANS_NAME))
|
||||
return cursor?.getString(Database.SCANS_NAME)
|
||||
}
|
||||
|
||||
// dialogs don't have a parent layout
|
||||
// Dialogs don't have a parent layout.
|
||||
@SuppressLint("InflateParams")
|
||||
private fun askForName(context: Context, id: Long, text: String?) {
|
||||
val view = LayoutInflater.from(context).inflate(
|
||||
@@ -357,17 +358,20 @@ class HistoryFragment : Fragment() {
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun askToExportToFile(context: Context) {
|
||||
private fun askToExportToFile() {
|
||||
scope.launch {
|
||||
val ac = activity ?: return@launch
|
||||
progressView.useVisibility {
|
||||
if (!hasWritePermission(ac) { askToExportToFile(context) }) {
|
||||
// Write permission is only required before Android Q.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
|
||||
!ac.hasWritePermission { askToExportToFile() }
|
||||
) {
|
||||
return@useVisibility
|
||||
}
|
||||
val options = context.resources.getStringArray(
|
||||
val options = ac.resources.getStringArray(
|
||||
R.array.export_options_values
|
||||
)
|
||||
val delimiter = alertDialog<String>(context) { resume ->
|
||||
val delimiter = alertDialog<String>(ac) { resume ->
|
||||
setTitle(R.string.export_as)
|
||||
setItems(R.array.export_options_names) { _, which ->
|
||||
resume(options[which])
|
||||
@@ -383,11 +387,11 @@ class HistoryFragment : Fragment() {
|
||||
)
|
||||
} ?: return@useVisibility
|
||||
val message = when (delimiter) {
|
||||
"db" -> exportDatabase(ac, name)
|
||||
"db" -> ac.exportDatabase(name)
|
||||
else -> db.getScansDetailed(filter)?.use {
|
||||
when (delimiter) {
|
||||
"json" -> exportJson(context, name, it)
|
||||
else -> exportCsv(context, name, it, delimiter)
|
||||
"json" -> ac.exportJson(name, it)
|
||||
else -> ac.exportCsv(name, it, delimiter)
|
||||
}
|
||||
} ?: false
|
||||
}.toSaveResult()
|
||||
@@ -416,31 +420,31 @@ class HistoryFragment : Fragment() {
|
||||
db.getScansDetailed(filter)?.use { cursor ->
|
||||
val details = format.split(":")
|
||||
text = when (details[0]) {
|
||||
"text" -> exportText(cursor, details[1])
|
||||
"csv" -> exportCsv(cursor, details[1])
|
||||
else -> exportJson(cursor)
|
||||
"text" -> cursor.exportText(details[1])
|
||||
"csv" -> cursor.exportCsv(details[1])
|
||||
else -> cursor.exportJson()
|
||||
}
|
||||
}
|
||||
text?.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
shareText(context, it)
|
||||
context.shareText(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportText(cursor: Cursor, separator: String): String {
|
||||
private fun Cursor.exportText(separator: String): String {
|
||||
val sb = StringBuilder()
|
||||
val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
|
||||
if (cursor.moveToFirst()) {
|
||||
val contentIndex = getColumnIndex(Database.SCANS_CONTENT)
|
||||
if (contentIndex > -1 && moveToFirst()) {
|
||||
do {
|
||||
val content = cursor.getString(contentIndex)
|
||||
val content = getString(contentIndex)
|
||||
if (content?.isNotEmpty() == true) {
|
||||
sb.append(content)
|
||||
sb.append(separator)
|
||||
}
|
||||
} while (cursor.moveToNext())
|
||||
} while (moveToNext())
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v14.preference.MultiSelectListPreference
|
||||
import android.support.v7.preference.ListPreference
|
||||
import android.support.v7.preference.Preference
|
||||
import android.support.v7.preference.PreferenceFragmentCompat
|
||||
import android.support.v7.preference.PreferenceGroup
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.wifi.removeNetworkSuggestions
|
||||
import de.markusfisch.android.binaryeye.activity.SplashActivity
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.app.systemBarRecyclerViewScrollListener
|
||||
import de.markusfisch.android.binaryeye.preference.UrlPreference
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.view.systemBarRecyclerViewScrollListener
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
|
||||
class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
private val changeListener = object : OnSharedPreferenceChangeListener {
|
||||
@@ -36,6 +43,43 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(state: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences)
|
||||
activity?.setTitle(R.string.preferences)
|
||||
wireClearNetworkPreferences()
|
||||
}
|
||||
|
||||
private fun wireClearNetworkPreferences() {
|
||||
val pref = findPreference("clear_network_suggestions") ?: return
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
pref.isVisible = false
|
||||
} else {
|
||||
pref.setOnPreferenceClickListener {
|
||||
askToClearNetworkSuggestions(context)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private fun askToClearNetworkSuggestions(context: Context) {
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(R.string.really_remove_all_networks)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
clearNetworkSuggestions(context)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private fun clearNetworkSuggestions(context: Context) {
|
||||
context.toast(
|
||||
if (
|
||||
removeNetworkSuggestions(context) == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS
|
||||
) {
|
||||
R.string.clear_network_suggestions_success
|
||||
} else {
|
||||
R.string.clear_network_suggestions_nothing_to_remove
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -81,6 +125,13 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
is ListPreference -> {
|
||||
preference.setSummary(preference.entry)
|
||||
}
|
||||
is MultiSelectListPreference -> {
|
||||
preference.setSummary(
|
||||
preference.values.joinToString(", ") {
|
||||
it.replace(Regex("_"), " ")
|
||||
}
|
||||
)
|
||||
}
|
||||
is PreferenceGroup -> {
|
||||
setSummaries(preference)
|
||||
}
|
||||
@@ -98,6 +149,6 @@ private fun Activity.restartApp() {
|
||||
}
|
||||
startActivity(intent)
|
||||
finish()
|
||||
// restart to begin with an unmodified Locale to follow system settings
|
||||
// Restart to begin with an unmodified Locale to follow system settings.
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ private fun completeUrl(template: String): String {
|
||||
s = "http://${s}"
|
||||
}
|
||||
if (prefs.sendScanType == "0" &&
|
||||
!s.matches(".*/[a-zA-Z._-]*\\?[a-zA-Z0-9_-]+=$".toRegex())
|
||||
!s.matches(".*/[a-zA-Z._-]*\\?[a-zA-Z0-9&=_-]+=$".toRegex())
|
||||
) {
|
||||
s = "${s}?content="
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun loadImageUri(cr: ContentResolver, uri: Uri): Bitmap? = try {
|
||||
fun ContentResolver.loadImageUri(uri: Uri): Bitmap? = try {
|
||||
val options = BitmapFactory.Options()
|
||||
cr.openInputStream(uri)?.use {
|
||||
openInputStream(uri)?.use {
|
||||
options.inJustDecodeBounds = true
|
||||
BitmapFactory.decodeStream(it, null, options)
|
||||
options.inSampleSize = calculateInSampleSize(
|
||||
@@ -22,7 +22,7 @@ fun loadImageUri(cr: ContentResolver, uri: Uri): Bitmap? = try {
|
||||
)
|
||||
options.inJustDecodeBounds = false
|
||||
}
|
||||
cr.openInputStream(uri)?.use {
|
||||
openInputStream(uri)?.use {
|
||||
BitmapFactory.decodeStream(it, null, options)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
@@ -49,8 +49,8 @@ private fun calculateInSampleSize(
|
||||
return inSampleSize
|
||||
}
|
||||
|
||||
fun crop(bitmap: Bitmap, rect: RectF, rotation: Float) = try {
|
||||
val erected = erect(bitmap, rotation)
|
||||
fun Bitmap.crop(rect: RectF, rotation: Float) = try {
|
||||
val erected = erect(rotation)
|
||||
val w = erected.width
|
||||
val h = erected.height
|
||||
val x = max(0, (rect.left * w).roundToInt())
|
||||
@@ -59,8 +59,8 @@ fun crop(bitmap: Bitmap, rect: RectF, rotation: Float) = try {
|
||||
erected,
|
||||
x,
|
||||
y,
|
||||
min(w - x, (rect.width() * w).roundToInt()),
|
||||
min(h - y, (rect.height() * h).roundToInt())
|
||||
min(w - x, (rect.right * w).roundToInt() - x),
|
||||
min(h - y, (rect.bottom * h).roundToInt() - y)
|
||||
)
|
||||
} catch (e: OutOfMemoryError) {
|
||||
null
|
||||
@@ -68,20 +68,20 @@ fun crop(bitmap: Bitmap, rect: RectF, rotation: Float) = try {
|
||||
null
|
||||
}
|
||||
|
||||
private fun erect(bitmap: Bitmap, rotation: Float): Bitmap = if (
|
||||
private fun Bitmap.erect(rotation: Float): Bitmap = if (
|
||||
rotation % 360f != 0f
|
||||
) {
|
||||
val matrix = Matrix()
|
||||
matrix.setRotate(rotation)
|
||||
Bitmap.createBitmap(
|
||||
bitmap,
|
||||
this,
|
||||
0,
|
||||
0,
|
||||
bitmap.width,
|
||||
bitmap.height,
|
||||
matrix,
|
||||
width,
|
||||
height,
|
||||
Matrix().apply {
|
||||
setRotate(rotation)
|
||||
},
|
||||
true
|
||||
)
|
||||
} else {
|
||||
bitmap
|
||||
this
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.graphics
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Point
|
||||
import android.support.v4.content.ContextCompat
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
class Dots(context: Context) {
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val radius = 8f * context.resources.displayMetrics.density
|
||||
|
||||
init {
|
||||
paint.color = ContextCompat.getColor(context, R.color.dot)
|
||||
paint.style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
fun draw(canvas: Canvas, points: List<Point>) {
|
||||
for (point in points) {
|
||||
canvas.drawCircle(
|
||||
point.x.toFloat(),
|
||||
point.y.toFloat(),
|
||||
radius,
|
||||
paint
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +1,62 @@
|
||||
package de.markusfisch.android.binaryeye.graphics
|
||||
|
||||
import android.graphics.Point
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Rect
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.ResultPoint
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun mapResult(
|
||||
fun mapResultToView(
|
||||
frameWidth: Int,
|
||||
frameHeight: Int,
|
||||
frameOrientation: Int,
|
||||
viewRect: Rect,
|
||||
result: Result
|
||||
): List<Point> = frameToView(
|
||||
resultPoints: Array<ResultPoint?>,
|
||||
targetArray: FloatArray
|
||||
): Int = getFrameToViewMatrix(
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation,
|
||||
viewRect
|
||||
).map(
|
||||
result.resultPoints
|
||||
resultPoints,
|
||||
targetArray
|
||||
)
|
||||
|
||||
fun frameToView(
|
||||
fun getFrameToViewMatrix(
|
||||
frameWidth: Int,
|
||||
frameHeight: Int,
|
||||
frameOrientation: Int,
|
||||
viewRect: Rect
|
||||
) = Mapping(
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation,
|
||||
viewRect.width().toFloat(),
|
||||
viewRect.height().toFloat(),
|
||||
viewRect.left,
|
||||
viewRect.top
|
||||
)
|
||||
|
||||
fun isPortrait(orientation: Int) = orientation == 90 || orientation == 270
|
||||
|
||||
data class Mapping(
|
||||
val frameWidth: Int,
|
||||
val frameHeight: Int,
|
||||
val frameOrientation: Int,
|
||||
val viewWidth: Float,
|
||||
val viewHeight: Float,
|
||||
val offsetX: Int,
|
||||
val offsetY: Int
|
||||
) {
|
||||
fun map(resultPoint: ResultPoint): Point {
|
||||
val point = Point(
|
||||
resultPoint.x.roundToInt(),
|
||||
resultPoint.y.roundToInt()
|
||||
)
|
||||
rotate(point)
|
||||
val w: Int
|
||||
val h: Int
|
||||
if (isPortrait(frameOrientation)) {
|
||||
w = frameHeight
|
||||
h = frameWidth
|
||||
} else {
|
||||
w = frameWidth
|
||||
h = frameHeight
|
||||
}
|
||||
val ratioX = viewWidth / w.toFloat()
|
||||
val ratioY = viewHeight / h.toFloat()
|
||||
point.set(
|
||||
(point.x * ratioX).roundToInt() + offsetX,
|
||||
(point.y * ratioY).roundToInt() + offsetY
|
||||
)
|
||||
return point
|
||||
}
|
||||
|
||||
fun map(points: Array<ResultPoint?>): List<Point> =
|
||||
// because ZXing apparently returns null in this array sometimes
|
||||
points.filterNotNull().map { map(it) }
|
||||
|
||||
private fun rotate(point: Point) = when (frameOrientation) {
|
||||
90 -> point.set(frameHeight - point.y, point.x)
|
||||
180 -> point.set(frameWidth - point.x, frameHeight - point.y)
|
||||
270 -> point.set(point.y, frameWidth - point.x)
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
) = Matrix().apply {
|
||||
// Normalize to frame dimensions for rotation.
|
||||
postScale(
|
||||
1f / frameWidth,
|
||||
1f / frameHeight
|
||||
)
|
||||
// Rotate around center.
|
||||
postRotate(frameOrientation.toFloat(), 0.5f, 0.5f)
|
||||
// Scale up to view size.
|
||||
postScale(viewRect.width().toFloat(), viewRect.height().toFloat())
|
||||
// Apply view displacement.
|
||||
postTranslate(viewRect.left.toFloat(), viewRect.top.toFloat())
|
||||
}
|
||||
|
||||
fun Matrix.map(
|
||||
resultPoints: Array<ResultPoint?>,
|
||||
targetArray: FloatArray
|
||||
): Int {
|
||||
val max = targetArray.size
|
||||
var i = 0
|
||||
for (resultPoint in resultPoints) {
|
||||
// Because ZXing apparently returns null in this array sometimes.
|
||||
if (resultPoint == null) {
|
||||
continue
|
||||
}
|
||||
targetArray[i++] = resultPoint.x
|
||||
targetArray[i++] = resultPoint.y
|
||||
if (i >= max) {
|
||||
break
|
||||
}
|
||||
}
|
||||
mapPoints(targetArray, 0, targetArray, 0, i)
|
||||
return i
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
package de.markusfisch.android.binaryeye.io
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ContentValues
|
||||
@@ -9,6 +9,7 @@ import android.provider.MediaStore
|
||||
import android.support.annotation.MainThread
|
||||
import android.widget.EditText
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.alertDialog
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
@@ -45,20 +46,18 @@ fun Boolean.toSaveResult() = if (this) {
|
||||
R.string.error_saving_file
|
||||
}
|
||||
|
||||
fun writeExternalFile(
|
||||
context: Context,
|
||||
fun Context.writeExternalFile(
|
||||
fileName: String,
|
||||
mimeType: String,
|
||||
write: (outputStream: OutputStream) -> Unit
|
||||
): Boolean = try {
|
||||
openExternalOutputStream(context, fileName, mimeType).use { write(it) }
|
||||
openExternalOutputStream(fileName, mimeType).use { write(it) }
|
||||
true
|
||||
} catch (e: IOException) {
|
||||
false
|
||||
}
|
||||
|
||||
private fun openExternalOutputStream(
|
||||
context: Context,
|
||||
private fun Context.openExternalOutputStream(
|
||||
fileName: String,
|
||||
mimeType: String
|
||||
): OutputStream = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
@@ -74,7 +73,7 @@ private fun openExternalOutputStream(
|
||||
}
|
||||
FileOutputStream(file)
|
||||
} else {
|
||||
val resolver = context.contentResolver
|
||||
val resolver = contentResolver
|
||||
val uri = resolver.insert(
|
||||
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
|
||||
ContentValues().apply {
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.markusfisch.android.binaryeye.net
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.toHexString
|
||||
import de.markusfisch.android.binaryeye.app.urlEncode
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -14,9 +15,12 @@ import java.io.InputStreamReader
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.ProtocolException
|
||||
import java.net.URL
|
||||
import java.net.URLEncoder
|
||||
|
||||
fun Scan.sendAsync(url: String, type: String, callback: (Int?, String?) -> Unit) {
|
||||
fun Scan.sendAsync(
|
||||
url: String,
|
||||
type: String,
|
||||
callback: (Int?, String?) -> Unit
|
||||
) {
|
||||
CoroutineScope(Dispatchers.IO).launch(Dispatchers.IO) {
|
||||
val response = send(url, type)
|
||||
withContext(Dispatchers.Main) {
|
||||
@@ -47,21 +51,19 @@ private fun Scan.send(url: String, type: String): Response {
|
||||
}
|
||||
}
|
||||
else -> request(
|
||||
url + urlEncode(content)
|
||||
url + content.urlEncode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Scan.asUrlArguments(): String = getMap().map { (k, v) ->
|
||||
"${k}=${urlEncode(v)}"
|
||||
"${k}=${v.urlEncode()}"
|
||||
}.joinToString("&")
|
||||
|
||||
private fun Scan.asJson() = JSONObject().apply {
|
||||
getMap().forEach { (k, v) -> put(k, v) }
|
||||
}
|
||||
|
||||
private fun urlEncode(s: String) = URLEncoder.encode(s, "utf-8")
|
||||
|
||||
private fun Scan.getMap(): Map<String, String> = mapOf(
|
||||
"content" to content,
|
||||
"raw" to raw?.toHexString(),
|
||||
@@ -74,7 +76,7 @@ private fun Scan.getMap(): Map<String, String> = mapOf(
|
||||
"possibleCountry" to possibleCountry,
|
||||
"suggestedPrice" to suggestedPrice,
|
||||
"upcEanExtension" to upcEanExtension,
|
||||
"timestamp" to timestamp
|
||||
"timestamp" to dateTime
|
||||
).filterNullValues()
|
||||
|
||||
private fun Map<String, String?>.filterNullValues() =
|
||||
@@ -87,6 +89,7 @@ private fun request(
|
||||
var con: HttpURLConnection? = null
|
||||
return try {
|
||||
con = URL(url).openConnection() as HttpURLConnection
|
||||
con.connectTimeout = 5000
|
||||
writer?.invoke(con)
|
||||
val body = con.inputStream.readHead()
|
||||
Response(con.responseCode, body)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package de.markusfisch.android.binaryeye.os
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
|
||||
private const val DURATION_IN_MS = 100L
|
||||
|
||||
private val beforeO = Build.VERSION.SDK_INT < Build.VERSION_CODES.O
|
||||
private val errorPatternTimings = longArrayOf(
|
||||
50L, 100L, 50L, 100L, 50L
|
||||
)
|
||||
private val errorPatternAmplitudes = intArrayOf(
|
||||
255, 0, 255, 0, 255
|
||||
)
|
||||
|
||||
fun Context.getVibrator(): Vibrator = if (
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.S
|
||||
) {
|
||||
@Suppress("DEPRECATION")
|
||||
getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
} else {
|
||||
(getSystemService(
|
||||
Context.VIBRATOR_MANAGER_SERVICE
|
||||
) as VibratorManager).defaultVibrator
|
||||
}
|
||||
|
||||
fun Vibrator.vibrate() {
|
||||
if (!prefs.vibrate) {
|
||||
return
|
||||
} else if (beforeO) {
|
||||
@Suppress("DEPRECATION")
|
||||
vibrate(DURATION_IN_MS)
|
||||
} else {
|
||||
vibrate(
|
||||
VibrationEffect.createOneShot(
|
||||
DURATION_IN_MS,
|
||||
VibrationEffect.DEFAULT_AMPLITUDE
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Vibrator.error() {
|
||||
if (!prefs.vibrate) {
|
||||
return
|
||||
} else if (beforeO) {
|
||||
@Suppress("DEPRECATION")
|
||||
vibrate(errorPatternTimings, -1)
|
||||
} else {
|
||||
vibrate(
|
||||
VibrationEffect.createWaveform(
|
||||
errorPatternTimings,
|
||||
errorPatternAmplitudes,
|
||||
-1
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,43 @@ package de.markusfisch.android.binaryeye.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.annotation.RequiresApi
|
||||
|
||||
class Preferences {
|
||||
lateinit var preferences: SharedPreferences
|
||||
|
||||
var cropHandleX = -1
|
||||
var barcodeFormats = setOf(
|
||||
"AZTEC",
|
||||
"CODABAR",
|
||||
"CODE_39",
|
||||
"CODE_93",
|
||||
"CODE_128",
|
||||
"DATA_MATRIX",
|
||||
"EAN_8",
|
||||
"EAN_13",
|
||||
"ITF",
|
||||
"MAXICODE",
|
||||
"PDF_417",
|
||||
"QR_CODE",
|
||||
"RSS_14",
|
||||
"RSS_EXPANDED",
|
||||
"UPC_A",
|
||||
"UPC_E",
|
||||
"UPC_EAN_EXTENSION"
|
||||
)
|
||||
@RequiresApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
set(value) {
|
||||
apply(BARCODE_FORMATS, value)
|
||||
field = value
|
||||
}
|
||||
var cropHandleX = -2 // -2 means set default roi.
|
||||
set(value) {
|
||||
apply(CROP_HANDLE_X, value)
|
||||
field = value
|
||||
}
|
||||
var cropHandleY = -1
|
||||
var cropHandleY = -2
|
||||
set(value) {
|
||||
apply(CROP_HANDLE_Y, value)
|
||||
field = value
|
||||
@@ -34,18 +60,39 @@ class Preferences {
|
||||
}
|
||||
var autoRotate = false
|
||||
set(value) {
|
||||
// immediately save this setting before it shouldn't change
|
||||
// on the fly while scanning
|
||||
// Immediately save this setting before it shouldn't change
|
||||
// on the fly while scanning.
|
||||
commit(AUTO_ROTATE, value)
|
||||
field = value
|
||||
}
|
||||
var tryHarder = false
|
||||
set(value) {
|
||||
// immediately save this setting because it's only ever read
|
||||
// before the camera is opened
|
||||
// Immediately save this setting because it's only ever read
|
||||
// before the camera is opened.
|
||||
commit(TRY_HARDER, value)
|
||||
field = value
|
||||
}
|
||||
var bulkMode = false
|
||||
set(value) {
|
||||
// Immediately save this setting because it's only ever read
|
||||
// before the camera is opened.
|
||||
commit(BULK_MODE, value)
|
||||
field = value
|
||||
}
|
||||
var bulkModeDelay = "500"
|
||||
set(value) {
|
||||
// Immediately save this setting because it's only ever read
|
||||
// before the camera is opened.
|
||||
commit(BULK_MODE_DELAY, value)
|
||||
field = value
|
||||
}
|
||||
var showToastInBulkMode = true
|
||||
set(value) {
|
||||
// Immediately save this setting because it's only ever read
|
||||
// before the camera is opened.
|
||||
commit(SHOW_TOAST_IN_BULK_MODE, value)
|
||||
field = value
|
||||
}
|
||||
var vibrate = true
|
||||
set(value) {
|
||||
apply(VIBRATE, value)
|
||||
@@ -86,11 +133,21 @@ class Preferences {
|
||||
apply(CLOSE_AUTOMATICALLY, value)
|
||||
field = value
|
||||
}
|
||||
var defaultSearchUrl = ""
|
||||
set(value) {
|
||||
apply(DEFAULT_SEARCH_URL, value)
|
||||
field = value
|
||||
}
|
||||
var openWithUrl: String = ""
|
||||
set(value) {
|
||||
apply(OPEN_WITH_URL, value)
|
||||
field = value
|
||||
}
|
||||
var sendScanActive = true
|
||||
set(value) {
|
||||
apply(SEND_SCAN_ACTIVE, value)
|
||||
field = value
|
||||
}
|
||||
var sendScanUrl: String = ""
|
||||
set(value) {
|
||||
apply(SEND_SCAN_URL, value)
|
||||
@@ -111,10 +168,10 @@ class Preferences {
|
||||
apply(INDEX_OF_LAST_SELECTED_FORMAT, value)
|
||||
field = value
|
||||
}
|
||||
var forceCompat: Boolean = false
|
||||
var forceCompat = false
|
||||
set(value) {
|
||||
// since the app may be about to crash when forceCompat is set,
|
||||
// it's necessary to `commit()` this synchronously
|
||||
// Since the app may be about to crash when forceCompat is set,
|
||||
// it's necessary to `commit()` this synchronously.
|
||||
commit(FORCE_COMPAT, value)
|
||||
field = value
|
||||
}
|
||||
@@ -125,16 +182,33 @@ class Preferences {
|
||||
}
|
||||
|
||||
fun update() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
preferences.getStringSet(BARCODE_FORMATS, barcodeFormats)?.let {
|
||||
barcodeFormats = it
|
||||
}
|
||||
}
|
||||
cropHandleX = preferences.getInt(CROP_HANDLE_X, cropHandleX)
|
||||
cropHandleY = preferences.getInt(CROP_HANDLE_Y, cropHandleY)
|
||||
cropHandleOrientation = preferences.getInt(
|
||||
CROP_HANDLE_ORIENTATION,
|
||||
cropHandleOrientation
|
||||
)
|
||||
showCropHandle = preferences.getBoolean(SHOW_CROP_HANDLE, showCropHandle)
|
||||
showCropHandle = preferences.getBoolean(
|
||||
SHOW_CROP_HANDLE,
|
||||
showCropHandle
|
||||
)
|
||||
zoomBySwiping = preferences.getBoolean(ZOOM_BY_SWIPING, zoomBySwiping)
|
||||
autoRotate = preferences.getBoolean(AUTO_ROTATE, autoRotate)
|
||||
tryHarder = preferences.getBoolean(TRY_HARDER, tryHarder)
|
||||
bulkMode = preferences.getBoolean(BULK_MODE, bulkMode)
|
||||
bulkModeDelay = preferences.getString(
|
||||
BULK_MODE_DELAY,
|
||||
bulkModeDelay
|
||||
) ?: bulkModeDelay
|
||||
showToastInBulkMode = preferences.getBoolean(
|
||||
SHOW_TOAST_IN_BULK_MODE,
|
||||
showToastInBulkMode
|
||||
)
|
||||
vibrate = preferences.getBoolean(VIBRATE, vibrate)
|
||||
useHistory = preferences.getBoolean(USE_HISTORY, useHistory)
|
||||
ignoreConsecutiveDuplicates = preferences.getBoolean(
|
||||
@@ -155,9 +229,16 @@ class Preferences {
|
||||
CLOSE_AUTOMATICALLY,
|
||||
closeAutomatically
|
||||
)
|
||||
preferences.getString(DEFAULT_SEARCH_URL, defaultSearchUrl)?.also {
|
||||
defaultSearchUrl = it
|
||||
}
|
||||
preferences.getString(OPEN_WITH_URL, openWithUrl)?.also {
|
||||
openWithUrl = it
|
||||
}
|
||||
sendScanActive = preferences.getBoolean(
|
||||
SEND_SCAN_ACTIVE,
|
||||
sendScanActive
|
||||
)
|
||||
preferences.getString(SEND_SCAN_URL, sendScanUrl)?.also {
|
||||
sendScanUrl = it
|
||||
}
|
||||
@@ -177,10 +258,17 @@ class Preferences {
|
||||
private fun put(label: String, value: Boolean) =
|
||||
preferences.edit().putBoolean(label, value)
|
||||
|
||||
private fun put(label: String, value: String) =
|
||||
preferences.edit().putString(label, value)
|
||||
|
||||
private fun commit(label: String, value: Boolean) {
|
||||
put(label, value).commit()
|
||||
}
|
||||
|
||||
private fun commit(label: String, value: String) {
|
||||
put(label, value).commit()
|
||||
}
|
||||
|
||||
private fun apply(label: String, value: Boolean) {
|
||||
put(label, value).apply()
|
||||
}
|
||||
@@ -193,27 +281,38 @@ class Preferences {
|
||||
preferences.edit().putInt(label, value).apply()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
private fun apply(label: String, value: Set<String>) {
|
||||
preferences.edit().putStringSet(label, value).apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CROP_HANDLE_X = "crop_handle_x"
|
||||
const val CROP_HANDLE_Y = "crop_handle_y"
|
||||
const val CROP_HANDLE_ORIENTATION = "crop_handle_orientation"
|
||||
const val SHOW_CROP_HANDLE = "show_crop_handle"
|
||||
const val ZOOM_BY_SWIPING = "zoom_by_swiping"
|
||||
const val AUTO_ROTATE = "auto_rotate"
|
||||
const val TRY_HARDER = "try_harder"
|
||||
const val VIBRATE = "vibrate"
|
||||
const val USE_HISTORY = "use_history"
|
||||
const val IGNORE_CONSECUTIVE_DUPLICATES = "ignore_consecutive_duplicates"
|
||||
const val OPEN_IMMEDIATELY = "open_immediately"
|
||||
const val COPY_IMMEDIATELY = "copy_immediately"
|
||||
const val SHOW_META_DATA = "show_meta_data"
|
||||
const val SHOW_HEX_DUMP = "show_hex_dump"
|
||||
const val CLOSE_AUTOMATICALLY = "close_automatically"
|
||||
const val OPEN_WITH_URL = "open_with_url"
|
||||
const val SEND_SCAN_URL = "send_scan_url"
|
||||
const val SEND_SCAN_TYPE = "send_scan_type"
|
||||
const val CUSTOM_LOCALE = "custom_locale"
|
||||
const val INDEX_OF_LAST_SELECTED_FORMAT = "index_of_last_selected_format"
|
||||
const val FORCE_COMPAT = "force_compat"
|
||||
private const val BARCODE_FORMATS = "barcode_formats"
|
||||
private const val CROP_HANDLE_X = "crop_handle_x"
|
||||
private const val CROP_HANDLE_Y = "crop_handle_y"
|
||||
private const val CROP_HANDLE_ORIENTATION = "crop_handle_orientation"
|
||||
private const val SHOW_CROP_HANDLE = "show_crop_handle"
|
||||
private const val ZOOM_BY_SWIPING = "zoom_by_swiping"
|
||||
private const val AUTO_ROTATE = "auto_rotate"
|
||||
private const val TRY_HARDER = "try_harder"
|
||||
private const val BULK_MODE = "bulk_mode"
|
||||
private const val BULK_MODE_DELAY = "bulk_mode_delay"
|
||||
private const val SHOW_TOAST_IN_BULK_MODE = "show_toast_in_bulk_mode"
|
||||
private const val VIBRATE = "vibrate"
|
||||
private const val USE_HISTORY = "use_history"
|
||||
private const val IGNORE_CONSECUTIVE_DUPLICATES = "ignore_consecutive_duplicates"
|
||||
private const val OPEN_IMMEDIATELY = "open_immediately"
|
||||
private const val COPY_IMMEDIATELY = "copy_immediately"
|
||||
private const val SHOW_META_DATA = "show_meta_data"
|
||||
private const val SHOW_HEX_DUMP = "show_hex_dump"
|
||||
private const val CLOSE_AUTOMATICALLY = "close_automatically"
|
||||
private const val DEFAULT_SEARCH_URL = "default_search_url"
|
||||
private const val OPEN_WITH_URL = "open_with_url"
|
||||
private const val SEND_SCAN_ACTIVE = "send_scan_active"
|
||||
private const val SEND_SCAN_URL = "send_scan_url"
|
||||
private const val SEND_SCAN_TYPE = "send_scan_type"
|
||||
private const val CUSTOM_LOCALE = "custom_locale"
|
||||
private const val INDEX_OF_LAST_SELECTED_FORMAT = "index_of_last_selected_format"
|
||||
private const val FORCE_COMPAT = "force_compat"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,11 +60,11 @@ class Preprocessor(
|
||||
outWidth = (roiWidth * SCALE_FACTOR).roundToInt()
|
||||
outHeight = (roiHeight * SCALE_FACTOR).roundToInt()
|
||||
|
||||
// make sure the dimensions are always a multiple of 4
|
||||
// Make sure the dimensions are always a multiple of 4.
|
||||
outWidth -= outWidth % 4
|
||||
outHeight -= outHeight % 4
|
||||
|
||||
// make sure the dimensions are always greater than 4
|
||||
// Make sure the dimensions are always greater than 4.
|
||||
outWidth = max(4, outWidth)
|
||||
outHeight = max(4, outHeight)
|
||||
} else {
|
||||
@@ -130,7 +130,7 @@ class Preprocessor(
|
||||
rotateScript._inWidth = t.x
|
||||
rotateScript._inHeight = t.y
|
||||
rotateScript.forEach_rotate90(
|
||||
rotatedAlloc, // ignored in kernel, just to satisfy forEach
|
||||
rotatedAlloc, // Ignored in kernel, just to satisfy forEach.
|
||||
rotatedAlloc
|
||||
)
|
||||
rotatedAlloc?.copyTo(frame)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package de.markusfisch.android.binaryeye.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.service.quicksettings.TileService
|
||||
import android.support.annotation.RequiresApi
|
||||
import de.markusfisch.android.binaryeye.activity.CameraActivity
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
class ScanTileService : TileService() {
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
val intent = Intent(applicationContext, CameraActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivityAndCollapse(intent)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
package de.markusfisch.android.binaryeye.view
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
package de.markusfisch.android.binaryeye.view
|
||||
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
@@ -20,8 +20,8 @@ val systemBarListViewScrollListener = object : AbsListView.OnScrollListener {
|
||||
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
|
||||
// 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 && firstChildScrolled(view))
|
||||
@@ -59,7 +59,7 @@ private fun lastChildOutOfView(listView: AbsListView): Boolean {
|
||||
|
||||
fun initSystemBars(activity: AppCompatActivity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// keeps the soft keyboard from repositioning the layout
|
||||
// Keeps the soft keyboard from repositioning the layout.
|
||||
val window = activity.window
|
||||
window.setSoftInputMode(
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
@@ -109,11 +109,11 @@ fun colorSystemAndToolBars(
|
||||
}
|
||||
activity.supportActionBar?.setBackgroundDrawable(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
// avoid allocation on Honeycomb and better
|
||||
// Avoid allocation on Honeycomb and better.
|
||||
actionBarBackground.color = topColor
|
||||
actionBarBackground
|
||||
} else {
|
||||
// ColorDrawable.setColor() doesn't exist pre Honeycomb
|
||||
// ColorDrawable.setColor() doesn't exist pre Honeycomb.
|
||||
ColorDrawable(topColor)
|
||||
}
|
||||
)
|
||||
@@ -29,10 +29,10 @@ fun View.doOnApplyWindowInsets(f: (View, Rect) -> Unit) {
|
||||
f(v, insetsWithToolbar(insets))
|
||||
insets
|
||||
}
|
||||
// it's important to explicitly request the insets (again) in
|
||||
// It's important to explicitly request the insets (again) in
|
||||
// case the view was created in Fragment.onCreateView() because
|
||||
// setOnApplyWindowInsetsListener() won't fire when the view
|
||||
// isn't attached
|
||||
// isn't attached.
|
||||
requestApplyInsetsWhenAttached()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.util.AttributeSet
|
||||
import de.markusfisch.android.binaryeye.app.colorSystemAndToolBars
|
||||
import de.markusfisch.android.binaryeye.view.colorSystemAndToolBars
|
||||
import de.markusfisch.android.scalingimageview.widget.ScalingImageView
|
||||
|
||||
open class ConfinedScalingImageView : ScalingImageView {
|
||||
|
||||
@@ -3,7 +3,7 @@ package de.markusfisch.android.binaryeye.widget
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ScrollView
|
||||
import de.markusfisch.android.binaryeye.app.colorSystemAndToolBars
|
||||
import de.markusfisch.android.binaryeye.view.colorSystemAndToolBars
|
||||
|
||||
class ConfinedScrollView : ScrollView {
|
||||
private var scrollable = false
|
||||
@@ -23,8 +23,8 @@ class ConfinedScrollView : ScrollView {
|
||||
) {
|
||||
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
|
||||
// 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
|
||||
|
||||
@@ -6,12 +6,12 @@ import android.graphics.*
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.graphics.Dots
|
||||
import de.markusfisch.android.binaryeye.graphics.getBitmapFromDrawable
|
||||
import de.markusfisch.android.binaryeye.graphics.getDashedBorderPaint
|
||||
import kotlin.math.abs
|
||||
@@ -20,18 +20,23 @@ import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class DetectorView : View {
|
||||
val currentOrientation = resources.configuration.orientation
|
||||
val coordinates = FloatArray(32)
|
||||
val roi = Rect()
|
||||
|
||||
var onRoiChange: (() -> Unit)? = null
|
||||
var onRoiChanged: (() -> Unit)? = null
|
||||
|
||||
private val dots = Dots(context)
|
||||
private val currentOrientation = resources.configuration.orientation
|
||||
private val invalidateRunnable: Runnable = Runnable {
|
||||
marks = null
|
||||
coordinatesLast = 0
|
||||
invalidate()
|
||||
}
|
||||
private val roiPaint = context.getDashedBorderPaint()
|
||||
private val dotPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = ContextCompat.getColor(context, R.color.dot)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
private val dotRadius: Float
|
||||
private val handleBitmap = resources.getBitmapFromDrawable(
|
||||
R.drawable.button_crop
|
||||
)
|
||||
@@ -47,7 +52,7 @@ class DetectorView : View {
|
||||
private val fabHeight: Int
|
||||
private val padding: Int
|
||||
|
||||
private var marks: List<Point>? = null
|
||||
private var coordinatesLast = 0
|
||||
private var handleGrabbed = false
|
||||
private var handleActive = false
|
||||
private var minY = 0
|
||||
@@ -57,6 +62,7 @@ class DetectorView : View {
|
||||
|
||||
init {
|
||||
val dp = context.resources.displayMetrics.density
|
||||
dotRadius = 8f * dp
|
||||
distToFull = (24f * dp).roundToInt()
|
||||
val minMoveThreshold = (8f * dp).roundToInt()
|
||||
minMoveThresholdSq = minMoveThreshold * minMoveThreshold
|
||||
@@ -72,7 +78,28 @@ class DetectorView : View {
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
|
||||
super(context, attrs, defStyleAttr)
|
||||
|
||||
fun setCropHandlePos(x: Int, y: Int, orientation: Int) {
|
||||
fun saveCropHandlePos() {
|
||||
val pos = getCropHandlePos()
|
||||
prefs.cropHandleX = pos.x
|
||||
prefs.cropHandleY = pos.y
|
||||
prefs.cropHandleOrientation = currentOrientation
|
||||
}
|
||||
|
||||
private fun getCropHandlePos() = if (handleActive) {
|
||||
handlePos
|
||||
} else {
|
||||
Point(-1, -1)
|
||||
}
|
||||
|
||||
fun restoreCropHandlePos() {
|
||||
setCropHandlePos(
|
||||
prefs.cropHandleX,
|
||||
prefs.cropHandleY,
|
||||
prefs.cropHandleOrientation
|
||||
)
|
||||
}
|
||||
|
||||
private fun setCropHandlePos(x: Int, y: Int, orientation: Int) {
|
||||
if (orientation == currentOrientation) {
|
||||
handlePos.set(x, y)
|
||||
} else {
|
||||
@@ -83,14 +110,8 @@ class DetectorView : View {
|
||||
}
|
||||
}
|
||||
|
||||
fun getCropHandlePos() = if (handleActive) {
|
||||
handlePos
|
||||
} else {
|
||||
Point(-1, -1)
|
||||
}
|
||||
|
||||
fun mark(points: List<Point>) {
|
||||
marks = points
|
||||
fun update(numberOfCoordinates: Int) {
|
||||
coordinatesLast = numberOfCoordinates
|
||||
invalidate()
|
||||
removeCallbacks(invalidateRunnable)
|
||||
postDelayed(invalidateRunnable, 500)
|
||||
@@ -165,7 +186,6 @@ class DetectorView : View {
|
||||
if (handleGrabbed) {
|
||||
if (!handleActive) {
|
||||
setHandleToDefaultRoi()
|
||||
handleActive = true
|
||||
} else {
|
||||
snap(x, y)
|
||||
}
|
||||
@@ -188,6 +208,7 @@ class DetectorView : View {
|
||||
(center.x + mn).roundToInt(),
|
||||
(center.y + mn).roundToInt()
|
||||
)
|
||||
handleActive = true
|
||||
}
|
||||
|
||||
private fun snap(x: Int, y: Int) {
|
||||
@@ -227,6 +248,9 @@ class DetectorView : View {
|
||||
width - handleXRadius - paddingRight - padding,
|
||||
height - handleYRadius - paddingBottom - fabHeight
|
||||
)
|
||||
if (handlePos.x == -2) {
|
||||
setHandleToDefaultRoi()
|
||||
}
|
||||
if (handleActive) {
|
||||
updateClipRect()
|
||||
} else {
|
||||
@@ -238,9 +262,7 @@ class DetectorView : View {
|
||||
if (handleActive) {
|
||||
drawClip(canvas)
|
||||
}
|
||||
marks?.let {
|
||||
dots.draw(canvas, it)
|
||||
}
|
||||
drawDots(canvas)
|
||||
if (prefs.showCropHandle) {
|
||||
canvas.drawBitmap(
|
||||
handleBitmap,
|
||||
@@ -251,11 +273,23 @@ class DetectorView : View {
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawDots(canvas: Canvas) {
|
||||
var i = 0
|
||||
while (i < coordinatesLast) {
|
||||
canvas.drawCircle(
|
||||
coordinates[i++],
|
||||
coordinates[i++],
|
||||
dotRadius,
|
||||
dotPaint
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawClip(canvas: Canvas) {
|
||||
if (minDist < 1) {
|
||||
return
|
||||
}
|
||||
// canvas.clipRect() doesn't work reliably below KITKAT
|
||||
// canvas.clipRect() doesn't work reliably below KITKAT.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
val radius = min(minDist / 2, cornerRadius).toFloat()
|
||||
canvas.save()
|
||||
|
||||
@@ -11,6 +11,12 @@ fun Context.toast(message: Int) = Toast.makeText(
|
||||
|
||||
fun Context.toast(message: String) = Toast.makeText(
|
||||
applicationContext,
|
||||
message,
|
||||
message.ellipsize(128),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
private fun String.ellipsize(max: Int) = if (length < max) {
|
||||
this
|
||||
} else {
|
||||
"${take(max)}…"
|
||||
}
|
||||
|
||||
@@ -11,44 +11,30 @@ class Zxing(possibleResultPoint: ResultPointCallback? = null) {
|
||||
private val multiFormatReader = MultiFormatReader()
|
||||
|
||||
init {
|
||||
val decodeFormats = EnumSet.noneOf(
|
||||
BarcodeFormat::class.java
|
||||
)
|
||||
decodeFormats.addAll(
|
||||
EnumSet.copyOf(
|
||||
listOf(
|
||||
BarcodeFormat.AZTEC,
|
||||
BarcodeFormat.CODABAR,
|
||||
BarcodeFormat.CODE_39,
|
||||
BarcodeFormat.CODE_93,
|
||||
BarcodeFormat.CODE_128,
|
||||
BarcodeFormat.DATA_MATRIX,
|
||||
BarcodeFormat.EAN_8,
|
||||
BarcodeFormat.EAN_13,
|
||||
BarcodeFormat.ITF,
|
||||
BarcodeFormat.MAXICODE,
|
||||
BarcodeFormat.PDF_417,
|
||||
BarcodeFormat.QR_CODE,
|
||||
BarcodeFormat.RSS_14,
|
||||
BarcodeFormat.RSS_EXPANDED,
|
||||
BarcodeFormat.UPC_A,
|
||||
BarcodeFormat.UPC_E,
|
||||
BarcodeFormat.UPC_EAN_EXTENSION
|
||||
)
|
||||
)
|
||||
)
|
||||
hints[DecodeHintType.POSSIBLE_FORMATS] = decodeFormats
|
||||
possibleResultPoint?.let {
|
||||
hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK] = it
|
||||
}
|
||||
}
|
||||
|
||||
fun updateHints(tryHarder: Boolean) {
|
||||
fun updateHints(tryHarder: Boolean, formats: Set<String>) {
|
||||
if (tryHarder) {
|
||||
hints[DecodeHintType.TRY_HARDER] = java.lang.Boolean.TRUE
|
||||
} else {
|
||||
hints.remove(DecodeHintType.TRY_HARDER)
|
||||
}
|
||||
setDecodeFormats(formats)
|
||||
}
|
||||
|
||||
private fun setDecodeFormats(formats: Set<String>) {
|
||||
hints[DecodeHintType.POSSIBLE_FORMATS] = EnumSet.noneOf(
|
||||
BarcodeFormat::class.java
|
||||
).apply {
|
||||
addAll(
|
||||
EnumSet.copyOf(
|
||||
formats.map { BarcodeFormat.valueOf(it) }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun decode(
|
||||
@@ -56,8 +42,8 @@ class Zxing(possibleResultPoint: ResultPointCallback? = null) {
|
||||
width: Int,
|
||||
height: Int,
|
||||
invert: Boolean = false
|
||||
): Result? {
|
||||
val source = PlanarYUVLuminanceSource(
|
||||
): Result? = decodeLuminanceSource(
|
||||
PlanarYUVLuminanceSource(
|
||||
yuvData,
|
||||
width,
|
||||
height,
|
||||
@@ -66,25 +52,20 @@ class Zxing(possibleResultPoint: ResultPointCallback? = null) {
|
||||
width,
|
||||
height,
|
||||
false
|
||||
)
|
||||
return decodeLuminanceSource(source, invert)
|
||||
}
|
||||
),
|
||||
invert
|
||||
)
|
||||
|
||||
fun decodePositiveNegative(bitmap: Bitmap): Result? =
|
||||
decode(bitmap, false) ?: decode(bitmap, true)
|
||||
|
||||
private fun decode(bitmap: Bitmap, invert: Boolean = false): Result? {
|
||||
val pixels = IntArray(bitmap.width * bitmap.height)
|
||||
return decode(pixels, bitmap, invert)
|
||||
}
|
||||
|
||||
private fun decode(
|
||||
pixels: IntArray,
|
||||
bitmap: Bitmap,
|
||||
invert: Boolean = false
|
||||
): 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 {
|
||||
@@ -99,25 +80,29 @@ class Zxing(possibleResultPoint: ResultPointCallback? = null) {
|
||||
private fun decodeLuminanceSource(
|
||||
source: LuminanceSource,
|
||||
invert: Boolean
|
||||
): Result? {
|
||||
return decodeLuminanceSource(
|
||||
if (invert) {
|
||||
source.invert()
|
||||
} else {
|
||||
source
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun decodeLuminanceSource(source: LuminanceSource): Result? {
|
||||
val bitmap = BinaryBitmap(HybridBinarizer(source))
|
||||
return try {
|
||||
multiFormatReader.decode(bitmap, hints)
|
||||
} catch (e: ReaderException) {
|
||||
null
|
||||
} finally {
|
||||
multiFormatReader.reset()
|
||||
): Result? = decodeLuminanceSource(
|
||||
if (invert) {
|
||||
source.invert()
|
||||
} else {
|
||||
source
|
||||
}
|
||||
)
|
||||
|
||||
private fun decodeLuminanceSource(source: LuminanceSource): Result? = try {
|
||||
multiFormatReader.decode(
|
||||
BinaryBitmap(HybridBinarizer(source)),
|
||||
hints
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// Usually it's bad practice to blindly catch *all* exceptions
|
||||
// because this can hide problems we want to know about. But
|
||||
// since ZXing has some errors (like all software) and I don't
|
||||
// want this app to break in the hands of my users, which won't
|
||||
// see the stack trace anyway, it's better to just catch all
|
||||
// exceptions here and live on.
|
||||
null
|
||||
} finally {
|
||||
multiFormatReader.reset()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:pathData="M54,55.787m-5.719,0a5.719,5.719 0,1 1,11.438 0a5.719,5.719 0,1 1,-11.438 0"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M48.639,37.916L45.368,41.491L39.703,41.491C37.738,41.491 36.129,43.099 36.129,45.065L36.129,66.509C36.129,68.475 37.738,70.084 39.703,70.084L68.297,70.084C70.262,70.084 71.871,68.475 71.871,66.509L71.871,45.065C71.871,43.099 70.262,41.491 68.297,41.491L62.632,41.491L59.361,37.916ZM54,64.722C49.068,64.722 45.065,60.719 45.065,55.787C45.065,50.855 49.068,46.852 54,46.852C58.932,46.852 62.935,50.855 62.935,55.787C62.935,60.719 58.932,64.722 54,64.722Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108">
|
||||
<path android:pathData="M48.281 55.787a5.719 5.719 0 1 1 11.438 0 5.719 5.719 0 1 1-11.438 0" android:fillColor="#ffffff"/>
|
||||
<path android:pathData="M48.639 37.916l-3.271 3.575h-5.665c-1.965 0-3.574 1.608-3.574 3.574v21.444c0 1.966 1.609 3.575 3.574 3.575h28.594c1.965 0 3.574-1.609 3.574-3.575V45.065c0-1.966-1.609-3.574-3.574-3.574h-5.665l-3.271-3.575zM54 64.722c-4.932 0-8.935-4.003-8.935-8.935s4.003-8.935 8.935-8.935 8.935 4.003 8.935 8.935-4.003 8.935-8.935 8.935z" android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:pathData="M69.75,56.25L56.25,56.25L56.25,69.75L51.75,69.75L51.75,56.25L38.25,56.25L38.25,51.75L51.75,51.75L51.75,38.25L56.25,38.25L56.25,51.75L69.75,51.75Z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108">
|
||||
<path android:pathData="M69.75 56.25h-13.5v13.5h-4.5v-13.5h-13.5v-4.5h13.5v-13.5h4.5v13.5h13.5z" android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:pathData="M70.057,56.114C70.147,55.439 70.192,54.742 70.192,54C70.192,53.281 70.147,52.561 70.034,51.886L74.6,48.333C75.005,48.018 75.117,47.411 74.87,46.961L70.552,39.495C70.282,39 69.72,38.843 69.225,39L63.85,41.159C62.726,40.305 61.534,39.585 60.207,39.045L59.397,33.333C59.307,32.793 58.858,32.411 58.318,32.411L49.682,32.411C49.142,32.411 48.715,32.793 48.625,33.333L47.816,39.045C46.489,39.585 45.274,40.327 44.172,41.159L38.798,39C38.303,38.82 37.741,39 37.471,39.495L33.175,46.961C32.906,47.433 32.996,48.018 33.445,48.333L38.011,51.886C37.898,52.561 37.808,53.303 37.808,54C37.808,54.697 37.853,55.439 37.966,56.114L33.4,59.667C32.996,59.982 32.883,60.589 33.13,61.039L37.448,68.505C37.718,69 38.28,69.158 38.775,69L44.15,66.841C45.274,67.696 46.466,68.415 47.793,68.955L48.603,74.667C48.715,75.207 49.142,75.589 49.682,75.589L58.318,75.589C58.858,75.589 59.307,75.207 59.375,74.667L60.184,68.955C61.511,68.415 62.726,67.696 63.828,66.841L69.202,69C69.697,69.18 70.259,69 70.529,68.505L74.847,61.039C75.117,60.544 75.005,59.982 74.577,59.667ZM54,62.096C49.547,62.096 45.904,58.453 45.904,54C45.904,49.547 49.547,45.904 54,45.904C58.453,45.904 62.096,49.547 62.096,54C62.096,58.453 58.453,62.096 54,62.096Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108">
|
||||
<path android:pathData="M70.057 56.114c0.09-0.675 0.135-1.372 0.135-2.114 0-0.719-0.045-1.439-0.158-2.114l4.566-3.553c0.405-0.315 0.517-0.922 0.27-1.372l-4.318-7.466C70.282 39 69.72 38.843 69.225 39l-5.375 2.159c-1.124-0.854-2.316-1.574-3.643-2.114l-0.81-5.712c-0.09-0.54-0.539-0.922-1.079-0.922h-8.636c-0.54 0-0.967 0.382-1.057 0.922l-0.809 5.712c-1.327 0.54-2.542 1.282-3.644 2.114L38.798 39c-0.495-0.18-1.057 0-1.327 0.495l-4.296 7.466c-0.269 0.472-0.179 1.057 0.27 1.372l4.566 3.553c-0.113 0.675-0.203 1.417-0.203 2.114 0 0.697 0.045 1.439 0.158 2.114L33.4 59.667c-0.404 0.315-0.517 0.922-0.27 1.372l4.318 7.466C37.718 69 38.28 69.158 38.775 69l5.375-2.159c1.124 0.855 2.316 1.574 3.643 2.114l0.81 5.712c0.112 0.54 0.539 0.922 1.079 0.922h8.636c0.54 0 0.989-0.382 1.057-0.922l0.809-5.712c1.327-0.54 2.542-1.259 3.644-2.114L69.202 69c0.495 0.18 1.057 0 1.327-0.495l4.318-7.466c0.27-0.495 0.158-1.057-0.27-1.372zM54 62.096c-4.453 0-8.096-3.643-8.096-8.096s3.643-8.096 8.096-8.096 8.096 3.643 8.096 8.096-3.643 8.096-8.096 8.096z" android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,3 @@
|
||||
<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="@android:color/white" android:pathData="M3 11h8V3H3v8zm2-6h4v4H5V5zM3 21h8v-8H3v8zm2-6h4v4H5v-4zm8-12v8h8V3h-8zm6 6h-4V5h4v4zm0 10h2v2h-2zm-6-6h2v2h-2zm2 2h2v2h-2zm-2 2h2v2h-2zm2 2h2v2h-2zm2-2h2v2h-2zm0-4h2v2h-2zm2 2h2v2h-2z"/>
|
||||
</vector>
|
||||
@@ -1,3 +1,3 @@
|
||||
<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.57L14 14.71v0.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.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 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="M3 11h8V3H3v8zm2-6h4v4H5V5zM3 21h8v-8H3v8zm2-6h4v4H5v-4zm8-12v8h8V3h-8zm6 6h-4V5h4v4zm0 10h2v2h-2zm-6-6h2v2h-2zm2 2h2v2h-2zm-2 2h2v2h-2zm2 2h2v2h-2zm2-2h2v2h-2zm0-4h2v2h-2zm2 2h2v2h-2z"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108">
|
||||
<path android:fillColor="#FF000000" android:pathData="M52-884.362h29v71H52z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M52-884.362h29v71H52z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M52-884.362h29v71H52zM32.002-24.974h3.127v7.656h-3.127z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M52-884.362h29v71H52z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M52-884.362h29v71H52z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M52-884.362h29v71H52z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M52-884.362h29v71H52z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M52-884.362h29v71H52z"/>
|
||||
<path android:pathData="M54.826 27.893c-6.851 0-13.701 2.581-18.924 7.745-10.445 10.328-10.413 27.062 0 37.423l36.218 36.036 37.879 0.902-0.07-38.363-36.18-35.998c-5.207-5.18-12.073-7.745-18.924-7.745z" android:strokeWidth="8.60725021" android:fillColor="#000000" android:fillAlpha="0.1254902"/>
|
||||
<path android:pathData="M28.001 54.118a26.525 26.227 0 1 0 53.049 0 26.525 26.227 0 1 0-53.049 0z" android:strokeWidth="4" android:fillColor="#ffffff"/>
|
||||
<path android:pathData="M39.26 54.988a14.509 14.346 0 1 0 29.019 0 14.509 14.346 0 1 0-29.019 0z" android:strokeWidth="4" android:fillColor="#000000"/>
|
||||
<path android:pathData="M54.034 44.513a9.535 9.428 0 1 0 19.07 0 9.535 9.428 0 1 0-19.07 0z" android:strokeWidth="10" android:fillColor="#ffffff"/>
|
||||
<path android:pathData="M54.723 28.295c-6.85 0-13.701 2.581-18.923 7.745-10.446 10.328-10.414 27.062 0 37.423l36.218 36.036L108 108l1.827-35.962-36.18-35.998c-5.207-5.18-12.073-7.745-18.924-7.745z" android:strokeWidth="8.60725021" android:fillColor="#000000" android:fillAlpha="0.1254902"/>
|
||||
<path android:pathData="M80.5 54a26.5 26.5 0 1 1-53 0 26.5 26.5 0 1 1 53 0" android:strokeWidth="4" android:fillColor="#FFFFFF"/>
|
||||
<path android:pathData="M35.179 54c0-4.605 1.656-8.824 4.402-12.095v24.19c-2.746-3.271-4.402-7.49-4.402-12.095zM54 35.181c0.539 0 1.074 0.023 1.601 0.066v37.506c-0.527 0.044-1.062 0.066-1.601 0.066-0.54 0-1.074-0.022-1.602-0.066V35.247c0.528-0.043 1.062-0.066 1.602-0.066zM72.819 54c0 4.605-1.654 8.824-4.401 12.095v-24.19c2.747 3.271 4.401 7.49 4.401 12.095zM45.99 36.965c1.02-0.481 2.093-0.873 3.204-1.166v36.402c-1.111-0.293-2.184-0.685-3.204-1.166v-34.07zm12.816-1.166c2.347 0.619 4.514 1.679 6.408 3.086v30.23c-1.894 1.408-4.061 2.467-6.408 3.086V35.799z" android:strokeWidth="4" android:fillColor="#000000"/>
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="90dp" android:height="90dp" android:viewportWidth="90" android:viewportHeight="90">
|
||||
<path android:fillColor="#FF000000" android:pathData="M27 45a18 18 0 1 1 36 0 18 18 0 1 1-36 0" android:strokeWidth="4"/>
|
||||
<path android:pathData="M31.738 45a13.262 13.262 0 1 1 26.524 0 13.262 13.262 0 1 1-26.524 0" android:strokeWidth="4" android:fillColor="#ffffff"/>
|
||||
<path android:pathData="M54.41 45c0 2.303-0.827 4.412-2.201 6.048V38.952c1.374 1.636 2.201 3.745 2.201 6.048zM45 54.41c-0.27 0-0.537-0.012-0.801-0.034V35.624C44.463 35.602 44.73 35.59 45 35.59s0.537 0.012 0.801 0.034v18.752C45.537 54.398 45.27 54.41 45 54.41zM35.59 45c0-2.303 0.827-4.412 2.201-6.048v12.096C36.417 49.412 35.59 47.303 35.59 45zm13.415 8.517c-0.511 0.241-1.046 0.437-1.602 0.583V35.9c0.556 0.146 1.091 0.342 1.602 0.583v17.034zM42.597 54.1c-1.173-0.309-2.257-0.839-3.204-1.542V37.442c0.947-0.703 2.031-1.233 3.204-1.542v18.2z" android:strokeWidth="4" android:fillColor="#000000"/>
|
||||
</vector>
|
||||
@@ -17,10 +17,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|start"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginRight="80dp"
|
||||
android:layout_marginEnd="80dp"
|
||||
android:padding="16dp"/>
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginRight="88dp"
|
||||
android:layout_marginEnd="88dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:padding="24dp"/>
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
style="@style/FAB"
|
||||
android:id="@+id/flash"
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
style="@style/FAB"
|
||||
android:id="@+id/scan"
|
||||
android:contentDescription="@string/scan_code"
|
||||
android:src="@drawable/ic_action_scan"/>
|
||||
android:src="@drawable/ic_action_search"/>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</merge>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="end|top"/>
|
||||
<TableLayout
|
||||
android:id="@+id/meta"
|
||||
android:id="@+id/data"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/format"
|
||||
@@ -46,6 +46,15 @@
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"/>
|
||||
<TableLayout
|
||||
android:id="@+id/meta"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/data"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"/>
|
||||
<TextView
|
||||
android:id="@+id/hex"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
android:icon="@drawable/ic_action_bulk_mode"
|
||||
android:checkable="true"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/restrict_format"
|
||||
android:title="@string/restrict_format"
|
||||
android:icon="@drawable/ic_action_restrict_format"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/preferences"
|
||||
android:title="@string/preferences"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/accent"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,167 @@
|
||||
<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>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%2$dটি অক্ষর, %1$s</item>
|
||||
<item quantity="other">%2$dটি অক্ষর, %1$s</item>
|
||||
</plurals>
|
||||
<string name="error_correction_level">ত্রুটি ঠিককরণ পরিমাণ</string>
|
||||
<string name="error_correction_level_l" formatted="false">কম (~৭% ঠিককরণ)</string>
|
||||
<string name="error_correction_level_m" formatted="false">মধ্যম (~১৫% ঠিককরণ)</string>
|
||||
<string name="error_correction_level_q" formatted="false">বেশি (~২৫% ঠিককরণ)</string>
|
||||
<string name="error_correction_level_h" formatted="false">অনেক বেশি (~৩০% ঠিককরণ)</string>
|
||||
<string name="issue_number">সমস্যা ক্রম</string>
|
||||
<string name="orientation">দিক</string>
|
||||
<string name="other_meta_data">মেটাতথ্য</string>
|
||||
<string name="pdf417_extra_metadata">পিডিএফ৪১৭ মেটাতথ্য</string>
|
||||
<string name="possible_country">তৈরির সম্ভাব্য দেশ</string>
|
||||
<string name="suggested_price">প্রস্তসবিত দাম</string>
|
||||
<string name="upc_ean_extension">ইউপিসি ইএএন বর্ধিত</string>
|
||||
<string name="toggle_flash">আলো</string>
|
||||
<string name="error_flash">Error toggling flash</string>
|
||||
<string name="share">ভাগ করো</string>
|
||||
<string name="share_as">ভাগ করার মাধ্যম</string>
|
||||
<string name="copy_to_clipboard">ক্লিপবোর্ডে অনুলিপি</string>
|
||||
<string name="copied_to_clipboard">ক্লিপবোর্ডে অনুলিপিত</string>
|
||||
<string name="copy_password">ক্লিপবোর্ডে পাসওয়ার্ড অনুলিপি করো</string>
|
||||
<string name="copied_password_to_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="bulk_mode">ধারাবাহিকভাবে খুঁজো</string>
|
||||
<string name="bulk_mode_summary">শুরুতে সবসময় ধারাবাহিকভাবে খুঁজো</string>
|
||||
<string name="bulk_mode_delay">Delay between continuous scans</string>
|
||||
<string name="restrict_format">Restrict format</string>
|
||||
<string name="remove_restriction">Remove restriction</string>
|
||||
<string name="scan_format">Scan %s</string>
|
||||
<string name="quarter_second">A quarter second</string>
|
||||
<string name="half_second">Half a second</string>
|
||||
<string name="a_second">One second</string>
|
||||
<string name="two_seconds">Two seconds</string>
|
||||
<string name="five_seconds">Five seconds</string>
|
||||
<string name="history">ইতিহাস</string>
|
||||
<string name="preferences">পছন্দসমূহ</string>
|
||||
<string name="scan_category">খুঁজো</string>
|
||||
<string name="content_category">তথ্য</string>
|
||||
<string name="locale_category">ভাষা</string>
|
||||
<string name="custom_locale">আলাদা ভাষা</string>
|
||||
<string name="follow_system_settings">ব্যবস্থার পছন্দসমূহ অনুসরণ করো</string>
|
||||
<string name="show_crop_handle">ছাটার সীমা দেখাও</string>
|
||||
<string name="show_crop_handle_summary">পরিবর্তনযোগ্য জায়গায়ই শুধু খুঁজো।</string>
|
||||
<string name="barcode_formats">Barcode formats</string>
|
||||
<string name="zoom_by_swiping">ওপরে/নিচে টানো বড়ো করার জন্য</string>
|
||||
<string name="zoom_by_swiping_summary">ক্যামেরার ছোটো-বড় নিয়ন্ত্রণের জন্য উপরে-নিচে টানো।</string>
|
||||
<string name="auto_rotate">খাড়া ১ডি বারকোড সনাক্ত করো</string>
|
||||
<string name="auto_rotate_summary">১ডি বারকোড সনাক্ত করতে স্বয়ংক্রিয়ভাবে ঘুরাও। যন্ত্রনির্ভরে এটি কার্যসম্পাদনের হার কমাতে পারে।</string>
|
||||
<string name="try_harder">পাঠকের নির্ভুলতা বাড়াও</string>
|
||||
<string name="try_harder_summary">অনেক কঠিন সংকেতের জন্য এটা চালু করো। এতে কার্যসম্পাদনার হার কমতে পারে।</string>
|
||||
<string name="show_toast_in_bulk_mode">ধারাবাহিকভাবে তথ্য দেখাও</string>
|
||||
<string name="show_toast_in_bulk_mode_summary">ধারাবাহিকভাবে তথ্য দেখানোর সময় মুহূর্তের জন্য তথ্য দেখাও।</string>
|
||||
<string name="vibrate">চিনলে কাঁপো</string>
|
||||
<string name="vibrate_summary">এটা সক্ষম রাখলে সংকেত চিনার সাথে সাথে কাঁপবে।</string>
|
||||
<string name="use_history">ইতিহাস সংরক্ষণ করো।</string>
|
||||
<string name="use_history_summary">সনাক্তকৃত সংকেত যন্ত্রে সংরক্ষণ করো।</string>
|
||||
<string name="ignore_consecutive_duplicates">দ্বিত্ব উপেক্ষা করো</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">দুইটা ইতিহাস সংরক্ষণ করবে না।</string>
|
||||
<string name="open_immediately">সাথে সাথে খুলো</string>
|
||||
<string name="open_immediately_summary">না দেখে সাথে সাথে খুলো। সক্রিয় থাকলে ক্ষতিকারক তথ্য খুলতে পারে।</string>
|
||||
<string name="copy_immediately">ক্লিপবোর্ডে রাখো</string>
|
||||
<string name="copy_immediately_summary">স্বয়ংক্রিয়ভাবে ক্লিপবোর্ডে অনুলিপি করো। বিশেষ দ্রষ্টব্য অন্য অ্যাপ পটভূমিতে এই তথ্য দেখে ফেলতে পারে।</string>
|
||||
<string name="show_meta_data">মেটাতথ্য দেখাও</string>
|
||||
<string name="show_meta_data_summary">সনাক্তকৃত বারকোড নিয়ে অতিরিক্ত তথ্য দেখাও।</string>
|
||||
<string name="show_hex_dump">ষড়(hex) সংকেত দেখাও</string>
|
||||
<string name="show_hex_dump_summary">পড়া তথ্যের ষড় সংকেত দেখাও</string>
|
||||
<string name="close_automatically">অনুলিপি/ভাগ করার পরে পিছনে যাও</string>
|
||||
<string name="close_automatically_summary">তথ্য অনুলিপি/ভাগ করার পরে স্বয়ংক্রিয়ভাবে পিছনে যাও।</string>
|
||||
<string name="default_search_engine">Open unknown data in</string>
|
||||
<string name="always_ask">Always ask</string>
|
||||
<string name="open_with_url">অজানা ইউআরএল খুলো</string>
|
||||
<string name="clear_network_suggestions">Clear network suggestions</string>
|
||||
<string name="clear_network_suggestions_summary">Remove all of the network suggestions that were previously provided by this app.</string>
|
||||
<string name="really_remove_all_networks">Really remove all networks?</string>
|
||||
<string name="clear_network_suggestions_success">Network suggestions cleared</string>
|
||||
<string name="clear_network_suggestions_nothing_to_remove">Nothing to remove</string>
|
||||
<string name="send_category">অন্যকে পাঠাও</string>
|
||||
<string name="send_scan_active">Forward scans</string>
|
||||
<string name="send_scan_active_summary">Enable to forward scans to a the given URL</string>
|
||||
<string name="send_scan_url">প্রতিটি পাঠোদ্ধার ইউআরএল-এ পাঠাও</string>
|
||||
<string name="send_scan_type">প্রতি পাঠোদ্ধারের জন্য অনুরোধ</string>
|
||||
<string name="send_type_get_add_content">গেট(GET) দিয়ে তথ্য যোগ করো</string>
|
||||
<string name="send_type_get_query_string">সম্পূর্ণ কুয়েরি স্ট্রিং দিয়ে গেট(GET) করো</string>
|
||||
<string name="send_type_post_form">POST application/x-www-form-urlencoded</string>
|
||||
<string name="send_type_post_json">POST application/json</string>
|
||||
<string name="send_type_external_browser">Open in external browser</string>
|
||||
<string name="test_url">পরীক্ষার জন্য ইউআরএল</string>
|
||||
<string name="really_remove_scan">আসলেই এটা মুছবে?</string>
|
||||
<string name="really_remove_all_scans">আসলেই সব মুছবে?</string>
|
||||
<string name="really_remove_selected_scans">আসলেই নির্বাচিত সব মুছবে?</string>
|
||||
<string name="clear_history">ইতিহাস মুছো</string>
|
||||
<string name="copy_scan">পাঠোদ্ধার অনুলিপি করো</string>
|
||||
<string name="edit_scan">চিহ্ন সম্পাদনা করো</string>
|
||||
<string name="remove_scan">পাঠোদ্ধার মুছো</string>
|
||||
<string name="enter_name">পাঠোদ্ধার ব্যাখ্যা করতে একটা নাম দাও</string>
|
||||
<string name="enter_name_hint">পাঠোদ্ধারের নাম</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_file">নথি সংরক্ষণ করা যায়নি</string>
|
||||
<string name="error_file_exists">নথি আগে থেকে আছে</string>
|
||||
<string name="connect_to_wifi">ওয়াইফাইর সাথে যুক্ত করো</string>
|
||||
<string name="wifi_config_failed">ওয়াইফাইয়ে ঠিক করে যুক্ত হওয়া যায়নি</string>
|
||||
<string name="wifi_added">ওয়াইফাই যোগকৃত</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">২এফএ যোগ করো</string>
|
||||
<string name="search_web">ওয়েবে খুঁজো</string>
|
||||
<string name="search_scan">আগেরগুলোতে খুঁজো</string>
|
||||
<string name="export_to_file">নথিতে রপ্তানি করো</string>
|
||||
<string name="export_as">রপ্তানির ধরণ?</string>
|
||||
<string name="export_csv_comma">কমাসহ সিএসভি(.csv) নথি</string>
|
||||
<string name="export_csv_semicolon">সেমিকোলনসহ সিএসভি(.csv) নথি</string>
|
||||
<string name="export_json">জেসন নথি</string>
|
||||
<string name="export_database">এসকিউলাইট তথ্যভিত্তি</string>
|
||||
<string name="saved_in_downloads">ডাউনলোডে সংরক্ষিত</string>
|
||||
<string name="rotate_image_cw">ছবি ঘড়ির দিকে ঘুরাও</string>
|
||||
<string name="pick_file">ছবি নাও</string>
|
||||
<string name="pick_code_to_scan">খুঁজার জন্য কোড নাও</string>
|
||||
<string name="shortcut_decode">বারকোড খুজো</string>
|
||||
<string name="shortcut_encode">বারকোড বানাও</string>
|
||||
<string name="shortcut_preferences">পছন্দসমূহ</string>
|
||||
<string name="background_request_failed">পটভূমির অনুরোধ ব্যর্থ</string>
|
||||
<string name="entry_type">Type</string>
|
||||
<string name="wifi_network">Wi-Fi network</string>
|
||||
<string name="wifi_ssid">Name</string>
|
||||
<string name="wifi_type">Authentication type</string>
|
||||
<string name="wifi_password">Password</string>
|
||||
<string name="wifi_hidden">Hidden network</string>
|
||||
<string name="wifi_eap">EAP method</string>
|
||||
<string name="wifi_anonymous_identity">Anonymous identity</string>
|
||||
<string name="wifi_identity">Identity</string>
|
||||
<string name="wifi_phase2">Phase 2 method</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,169 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Bez oprávnění k fotoaparátu tato aplikace nemá smysl. Na viděnou.</string>
|
||||
<string name="camera_error">Nepodařilo se otevřít fotoaparát, prosím zkuste to znovu</string>
|
||||
<string name="scan_code">Skenovat kód</string>
|
||||
<string name="compose_barcode">Vytvořit čárový kód</string>
|
||||
<string name="decode_barcode">Dekódovat čárový kód</string>
|
||||
<string name="content">Obsah</string>
|
||||
<string name="binary_data">(binární data)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%2$d znak, %1$s</item>
|
||||
<item quantity="few">%2$d znaky, %1$s</item>
|
||||
<item quantity="many">%2$d znaků, %1$s</item>
|
||||
<item quantity="other">%2$d znaků, %1$s</item>
|
||||
</plurals>
|
||||
<string name="error_correction_level">Stupeň korekce chyb</string>
|
||||
<string name="error_correction_level_l" formatted="false">Nízký (~7% korekce)</string>
|
||||
<string name="error_correction_level_m" formatted="false">Střední (~15% korekce)</string>
|
||||
<string name="error_correction_level_q" formatted="false">Čtvrtinový (~25% korekce)</string>
|
||||
<string name="error_correction_level_h" formatted="false">Vysoký (~30% korekce)</string>
|
||||
<string name="issue_number">Issue number</string>
|
||||
<string name="orientation">Orientace</string>
|
||||
<string name="other_meta_data">Metadata</string>
|
||||
<string name="pdf417_extra_metadata">PDF417 metadata</string>
|
||||
<string name="possible_country">Možná země původu</string>
|
||||
<string name="suggested_price">Doporučená cena</string>
|
||||
<string name="upc_ean_extension">rozšíření UPC EAN</string>
|
||||
<string name="toggle_flash">Zap/vyp blesk</string>
|
||||
<string name="error_flash">Error toggling flash</string>
|
||||
<string name="share">Sdílet</string>
|
||||
<string name="share_as">Sdílet jako</string>
|
||||
<string name="copy_to_clipboard">Zkopírovat do schránky</string>
|
||||
<string name="copied_to_clipboard">Zkopírováno do schránky</string>
|
||||
<string name="copy_password">Zkopírovat heslo do schránky</string>
|
||||
<string name="copied_password_to_clipboard">Heslo zkopírováno do schránky</string>
|
||||
<string name="open_url">Otevřít URL</string>
|
||||
<string name="cannot_resolve_action">Nemáte aplikaci, která by to uměla otevřít</string>
|
||||
<string name="pick_search_engine">Zvolte vyhledávač</string>
|
||||
<string name="format">Formát</string>
|
||||
<string name="size">Velikost v pixelech</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Sem vložte obsah</string>
|
||||
<string name="encode">ZAKÓDOVAT</string>
|
||||
<string name="error_no_content">Chybí obsah</string>
|
||||
<string name="error_encoding_barcode">Nepodařilo se vygenerovat čárový kód</string>
|
||||
<string name="view_barcode">Zobrazit čárový kód</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Přepnout kameru</string>
|
||||
<string name="bulk_mode">Skenovat nepřetržitě</string>
|
||||
<string name="bulk_mode_summary">Always start scanning continuously.</string>
|
||||
<string name="bulk_mode_delay">Delay between continuous scans</string>
|
||||
<string name="restrict_format">Restrict format</string>
|
||||
<string name="remove_restriction">Remove restriction</string>
|
||||
<string name="scan_format">Scan %s</string>
|
||||
<string name="quarter_second">A quarter second</string>
|
||||
<string name="half_second">Half a second</string>
|
||||
<string name="a_second">One second</string>
|
||||
<string name="two_seconds">Two seconds</string>
|
||||
<string name="five_seconds">Five seconds</string>
|
||||
<string name="history">Historie</string>
|
||||
<string name="preferences">Nastavení</string>
|
||||
<string name="scan_category">Skenování</string>
|
||||
<string name="content_category">Obsah</string>
|
||||
<string name="locale_category">Jazyk</string>
|
||||
<string name="custom_locale">Zvolit jazyk</string>
|
||||
<string name="follow_system_settings">Řídit se nastavením systému</string>
|
||||
<string name="show_crop_handle">Zobrazovat hrany výřezu</string>
|
||||
<string name="show_crop_handle_summary">Omezí skenování na zvolenou oblast.</string>
|
||||
<string name="barcode_formats">Barcode formats</string>
|
||||
<string name="zoom_by_swiping">Potáhnutím nahoru/dolů přiblížit</string>
|
||||
<string name="zoom_by_swiping_summary">Přetáhnutím nahoru nebo dolů se zvětší / zmenší úroveň přiblížení.</string>
|
||||
<string name="auto_rotate">Rozpoznávat otočené 1D čárové kódy</string>
|
||||
<string name="auto_rotate_summary">Automaticky otočí obraz a tak detekuje 1D čárové kódy na výšku. Na některých zařízeních může snížit výkonnost.</string>
|
||||
<string name="try_harder">Optimalizovat pro přesnost</string>
|
||||
<string name="try_harder_summary">Použijte pro velmi nečitelné kódy. Sníží výkonnost.</string>
|
||||
<string name="show_toast_in_bulk_mode">Show data in continuous mode</string>
|
||||
<string name="show_toast_in_bulk_mode_summary">Briefly show the scanned data when scanning continuously.</string>
|
||||
<string name="vibrate">Zavibrovat při detekování</string>
|
||||
<string name="vibrate_summary">Zařízení zavibruje, když rozpozná čárový kód.</string>
|
||||
<string name="use_history">Ukládat historii skenování</string>
|
||||
<string name="use_history_summary">Rozpoznané kódy se uloží do zařízení.</string>
|
||||
<string name="ignore_consecutive_duplicates">Ignorovat duplikáty</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">Další skeny stejného kódu se nebudou ukládat.</string>
|
||||
<string name="open_immediately">Otevírat okamžitě</string>
|
||||
<string name="open_immediately_summary">Přeskočí kontrolu a otevře obsah okamžitě. Může otevřít škodlivý obsah.</string>
|
||||
<string name="copy_immediately">Vložit do schránky</string>
|
||||
<string name="copy_immediately_summary">Automaticky zkopírovat naskenovaný obsah do schránky. Pozor, jiné aplikace jej mohou ze schránky přečíst, i když neběží.</string>
|
||||
<string name="show_meta_data">Zobrazit metadata</string>
|
||||
<string name="show_meta_data_summary">Zobrazí další data o naskenovaném čárovém kódu.</string>
|
||||
<string name="show_hex_dump">Zobrazit hex dump</string>
|
||||
<string name="show_hex_dump_summary">Zobrazí hex dump naskenovaného obsahu.</string>
|
||||
<string name="close_automatically">Vrátit se po zkopírování/sdílení</string>
|
||||
<string name="close_automatically_summary">Po zkopírovaní či sdílení se automaticky vrátí na skenovací obrazovku.</string>
|
||||
<string name="default_search_engine">Open unknown data in</string>
|
||||
<string name="always_ask">Always ask</string>
|
||||
<string name="open_with_url">Otevřít neznámá data pomocí URL</string>
|
||||
<string name="clear_network_suggestions">Clear network suggestions</string>
|
||||
<string name="clear_network_suggestions_summary">Remove all of the network suggestions that were previously provided by this app.</string>
|
||||
<string name="really_remove_all_networks">Really remove all networks?</string>
|
||||
<string name="clear_network_suggestions_success">Network suggestions cleared</string>
|
||||
<string name="clear_network_suggestions_nothing_to_remove">Nothing to remove</string>
|
||||
<string name="send_category">Přesměrování</string>
|
||||
<string name="send_scan_active">Forward scans</string>
|
||||
<string name="send_scan_active_summary">Enable to forward scans to a the given URL</string>
|
||||
<string name="send_scan_url">Posílat každý sken na URL</string>
|
||||
<string name="send_scan_type">Typ requestu pro každý sken</string>
|
||||
<string name="send_type_get_add_content">GET a simply add content</string>
|
||||
<string name="send_type_get_query_string">GET with complete query string</string>
|
||||
<string name="send_type_post_form">POST application/x-www-form-urlencoded</string>
|
||||
<string name="send_type_post_json">POST application/json</string>
|
||||
<string name="send_type_external_browser">Open in external browser</string>
|
||||
<string name="test_url">Otestovat URL</string>
|
||||
<string name="really_remove_scan">Opravdu odstranit sken?</string>
|
||||
<string name="really_remove_all_scans">Opravdu odstranit všechny skeny?</string>
|
||||
<string name="really_remove_selected_scans">Opravdu odstranit zvolené skeny?</string>
|
||||
<string name="clear_history">Vymazat historii</string>
|
||||
<string name="copy_scan">Zkopírovat sken</string>
|
||||
<string name="edit_scan">Upravit popisek</string>
|
||||
<string name="remove_scan">Odstranit sken</string>
|
||||
<string name="enter_name">Napište název popisující sken</string>
|
||||
<string name="enter_name_hint">Název skenu</string>
|
||||
<string name="no_barcode_found">Čárový kód nenalezen</string>
|
||||
<string name="pick_list_separator">Jak mají být odděleny položky?</string>
|
||||
<string name="separator_line_break">Nový řádek</string>
|
||||
<string name="separator_ruler">Svislice</string>
|
||||
<string name="save_as_file_name">Uložit jako do složky Stahování</string>
|
||||
<string name="file_name">Název souboru</string>
|
||||
<string name="error_saving_file">Soubor se nepodařilo uložit</string>
|
||||
<string name="error_file_exists">Soubor již existuje</string>
|
||||
<string name="connect_to_wifi">Připojit k WiFi</string>
|
||||
<string name="wifi_config_failed">Nepodařilo se nastavit WiFi</string>
|
||||
<string name="wifi_added">WiFi přidána</string>
|
||||
<string name="sms_send">Poslat SMS</string>
|
||||
<string name="sms_error">Nepodařilo se poslat SMS</string>
|
||||
<string name="tel_dial">Vytočit číslo</string>
|
||||
<string name="tel_error">Nepodařilo se vytočit číslo</string>
|
||||
<string name="mail_send">Poslat email</string>
|
||||
<string name="mail_error">Nepodařilo se poslat email</string>
|
||||
<string name="vcard_add">Přidat do kontaktů</string>
|
||||
<string name="vcard_failed">Nepodařilo se přidat do kontaktů</string>
|
||||
<string name="vevent_add">Vložit do kalendáře</string>
|
||||
<string name="vevent_failed">Nepodařilo se vložit do kalendáře</string>
|
||||
<string name="otpauth_add">Přidat 2FA</string>
|
||||
<string name="search_web">Vyhledat na webu</string>
|
||||
<string name="search_scan">Vyhledat sken</string>
|
||||
<string name="export_to_file">Exportovat do souboru</string>
|
||||
<string name="export_as">Exportovat jako</string>
|
||||
<string name="export_csv_comma">CSV oddělené čárkami</string>
|
||||
<string name="export_csv_semicolon">CSV oddělené středníky</string>
|
||||
<string name="export_json">soubor JSON</string>
|
||||
<string name="export_database">SQLite databáze</string>
|
||||
<string name="saved_in_downloads">Uloženo do složky Stahování</string>
|
||||
<string name="rotate_image_cw">Otočit obrázek po směru hodinových ručiček</string>
|
||||
<string name="pick_file">Zvolte obrázek</string>
|
||||
<string name="pick_code_to_scan">Zvolte kód k naskenování</string>
|
||||
<string name="shortcut_decode">Skenovat čárový kód</string>
|
||||
<string name="shortcut_encode">Vytvořit čárový kód</string>
|
||||
<string name="shortcut_preferences">Nastavení</string>
|
||||
<string name="background_request_failed">Požadavek na pozadí selhal</string>
|
||||
<string name="entry_type">Type</string>
|
||||
<string name="wifi_network">Wi-Fi network</string>
|
||||
<string name="wifi_ssid">Name</string>
|
||||
<string name="wifi_type">Authentication type</string>
|
||||
<string name="wifi_password">Password</string>
|
||||
<string name="wifi_hidden">Hidden network</string>
|
||||
<string name="wifi_eap">EAP method</string>
|
||||
<string name="wifi_anonymous_identity">Anonymous identity</string>
|
||||
<string name="wifi_identity">Identity</string>
|
||||
<string name="wifi_phase2">Phase 2 method</string>
|
||||
</resources>
|
||||
@@ -23,6 +23,7 @@
|
||||
<string name="suggested_price">Preisvorschlag</string>
|
||||
<string name="upc_ean_extension">UPC EAN Erweiterung</string>
|
||||
<string name="toggle_flash">Licht an/ausschalten</string>
|
||||
<string name="error_flash">Fehler beim ein/ausschalten des Lichts</string>
|
||||
<string name="share">Teilen</string>
|
||||
<string name="share_as">Teilen als?</string>
|
||||
<string name="copy_to_clipboard">In die Zwischenablage kopieren</string>
|
||||
@@ -43,21 +44,34 @@
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Kamera wechseln</string>
|
||||
<string name="bulk_mode">Fortlaufend Scannen</string>
|
||||
<string name="bulk_mode_summary">Immer mit fortlaufendem Scannen starten.</string>
|
||||
<string name="bulk_mode_delay">Pause zwischen fortlaufenden Scans</string>
|
||||
<string name="restrict_format">Format eingrenzen</string>
|
||||
<string name="remove_restriction">Einschränkung entfernen</string>
|
||||
<string name="scan_format">%s scannen</string>
|
||||
<string name="quarter_second">Eine Viertelsekunde</string>
|
||||
<string name="half_second">Eine halbe Sekunde</string>
|
||||
<string name="a_second">Eine Sekunde</string>
|
||||
<string name="two_seconds">Zwei Sekunden</string>
|
||||
<string name="five_seconds">Fünf Sekunden</string>
|
||||
<string name="history">Gespeicherte Codes</string>
|
||||
<string name="preferences">Einstellungen</string>
|
||||
<string name="scan_category">Scannen</string>
|
||||
<string name="content_category">Inhalt</string>
|
||||
<string name="locale_category">Sprache</string>
|
||||
<string name="custom_locale">Abweichendes Gebietsschema</string>
|
||||
<string name="custom_locale">Spracheinstellung</string>
|
||||
<string name="follow_system_settings">Systemeinstellung übernehmen</string>
|
||||
<string name="show_crop_handle">Ausschnitt verkleinern anzeigen</string>
|
||||
<string name="show_crop_handle_summary">Scannen auf einen anpassbaren Bereich einschränken.</string>
|
||||
<string name="barcode_formats">Barcode Formate</string>
|
||||
<string name="zoom_by_swiping">Geste für Kamerazoom</string>
|
||||
<string name="zoom_by_swiping_summary">Im Kamerabildschirm nach oben oder unten streichen, um den Kamerazoom zu steuern.</string>
|
||||
<string name="auto_rotate">Vertikale Erkennung</string>
|
||||
<string name="auto_rotate_summary">Das Kamerabild automatisch drehen um vertikale 1D Barcodes zu erkennen. Je nach Gerät kann dies die Leistung beeinträchtigen.</string>
|
||||
<string name="try_harder">Gründliche Suche</string>
|
||||
<string name="try_harder_summary">Aktivieren Sie diese Option für sehr schwer lesbare Codes. Beeinträchtigt die Leistung.</string>
|
||||
<string name="show_toast_in_bulk_mode">Daten einblenden</string>
|
||||
<string name="show_toast_in_bulk_mode_summary">Die gescannten Daten werden kurz eingeblendet, wenn fortlaufend gescannt wird.</string>
|
||||
<string name="vibrate">Bei Erkennung vibrieren</string>
|
||||
<string name="vibrate_summary">Das Gerät soll vibrieren wenn ein Code erkannt wird.</string>
|
||||
<string name="use_history">Codes speichern</string>
|
||||
@@ -74,14 +88,24 @@
|
||||
<string name="show_hex_dump_summary">Zeigt einen Hexdump des eingelesenen Inhalts an.</string>
|
||||
<string name="close_automatically">Automatisch zurückkehren</string>
|
||||
<string name="close_automatically_summary">Nach dem Kopieren/Teilen automatisch zum Scanbildschirm zurückkehren.</string>
|
||||
<string name="default_search_engine">Unbekannte Daten öffnen mit</string>
|
||||
<string name="always_ask">Immer fragen</string>
|
||||
<string name="open_with_url">URL zum Öffnen unbekannter Daten</string>
|
||||
<string name="clear_network_suggestions">WiFi Netzwerkvorschläge entfernen</string>
|
||||
<string name="clear_network_suggestions_summary">Betrifft alle WiFi Netzwerke, die zuvor mit dieser App bereitgestellt wurden.</string>
|
||||
<string name="really_remove_all_networks">Alle Netzwerkvorschläge entfernen?</string>
|
||||
<string name="clear_network_suggestions_success">Netzwerkvorschläge entfernt</string>
|
||||
<string name="clear_network_suggestions_nothing_to_remove">Nichts zu entfernen</string>
|
||||
<string name="send_category">Weiterleitung</string>
|
||||
<string name="send_scan_url">URL die für jeden Scan aufgerufen werden soll</string>
|
||||
<string name="send_scan_type">Typ des Requests für jeden Scan</string>
|
||||
<string name="send_scan_active">Scans weiterleiten</string>
|
||||
<string name="send_scan_active_summary">Aktivieren Sie diese Option, um Scans an die angegebene URL weiterzuleiten.</string>
|
||||
<string name="send_scan_url">URL, an die weitergeleitet werden soll</string>
|
||||
<string name="send_scan_type">Requesttyp</string>
|
||||
<string name="send_type_get_add_content">GET mit an die URL angehängtem Inhalt</string>
|
||||
<string name="send_type_get_query_string">GET mit vollständigem Query-String</string>
|
||||
<string name="send_type_post_form">POST application/x-www-form-urlencoded</string>
|
||||
<string name="send_type_post_json">POST application/json</string>
|
||||
<string name="send_type_external_browser">In externem Browser öffnen</string>
|
||||
<string name="test_url">Test URL</string>
|
||||
<string name="really_remove_scan">Code wirklich entfernen?</string>
|
||||
<string name="really_remove_all_scans">Alle Codes entfernen?</string>
|
||||
@@ -130,4 +154,14 @@
|
||||
<string name="shortcut_encode">Barcode erstellen</string>
|
||||
<string name="shortcut_preferences">Einstellungen</string>
|
||||
<string name="background_request_failed">Hintergrundanfrage fehlgeschlagen</string>
|
||||
<string name="entry_type">Typ</string>
|
||||
<string name="wifi_network">Wi-Fi Netzwerk</string>
|
||||
<string name="wifi_ssid">Name</string>
|
||||
<string name="wifi_type">Authentifizierungstyp</string>
|
||||
<string name="wifi_password">Passwort</string>
|
||||
<string name="wifi_hidden">Verstecktes Netzwerk</string>
|
||||
<string name="wifi_eap">EAP Methode</string>
|
||||
<string name="wifi_anonymous_identity">Anonyme Identität</string>
|
||||
<string name="wifi_identity">Identität</string>
|
||||
<string name="wifi_phase2">Phase 2 Methode</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Esta aplicación no tendría sentido usar sin acceso a la cámara. Adiós.</string>
|
||||
<string name="camera_error">Sin acceso a la cámara, por favor, inténtelo de nuevo</string>
|
||||
<string name="no_camera_no_fun">Esta aplicación no tiene sentido usar sin acceso a la cámara. Adiós.</string>
|
||||
<string name="camera_error">No se puede acceder a la cámara, por favor, inténtelo de nuevo</string>
|
||||
<string name="scan_code">Escanear código de barras</string>
|
||||
<string name="compose_barcode">Componer código de barras</string>
|
||||
<string name="decode_barcode">Decodificar código de barras</string>
|
||||
<string name="content">Contenido</string>
|
||||
<string name="binary_data">(binario)</string>
|
||||
<string name="binary_data">(datos binarios)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%2$d caracter, %1$s</item>
|
||||
<item quantity="one">%2$d carácter, %1$s</item>
|
||||
<item quantity="other">%2$d caracteres, %1$s</item>
|
||||
</plurals>
|
||||
<string name="error_correction_level">Nivel de corrección de errores</string>
|
||||
<string name="error_correction_level_l" formatted="false">Low (~7% correction)</string>
|
||||
<string name="error_correction_level_m" formatted="false">Medium (~15% correction)</string>
|
||||
<string name="error_correction_level_q" formatted="false">Quartile (~25% correction)</string>
|
||||
<string name="error_correction_level_h" formatted="false">High (~30% correction)</string>
|
||||
<string name="error_correction_level_l" formatted="false">Bajo (~7% de correción)</string>
|
||||
<string name="error_correction_level_m" formatted="false">Medio (~15% de correción)</string>
|
||||
<string name="error_correction_level_q" formatted="false">Cuartil (~25% de correción)</string>
|
||||
<string name="error_correction_level_h" formatted="false">Alto (~30% de correción)</string>
|
||||
<string name="issue_number">Número de emisión</string>
|
||||
<string name="orientation">Orientación</string>
|
||||
<string name="other_meta_data">Metadatos</string>
|
||||
<string name="pdf417_extra_metadata">Metadatos PDF417</string>
|
||||
<string name="possible_country">Posible país de fabricación</string>
|
||||
<string name="suggested_price">Precio sugerido</string>
|
||||
<string name="upc_ean_extension">UPC EAN extension</string>
|
||||
<string name="upc_ean_extension">Extensión UPC EAN</string>
|
||||
<string name="toggle_flash">Activar la linterna</string>
|
||||
<string name="error_flash">Error toggling flash</string>
|
||||
<string name="share">Compartir</string>
|
||||
<string name="share_as">Share as?</string>
|
||||
<string name="share_as">¿Compartir como?</string>
|
||||
<string name="copy_to_clipboard">Copiar al portapapeles</string>
|
||||
<string name="copied_to_clipboard">Copiado al portapapeles</string>
|
||||
<string name="copy_password">Copy password into clipboard</string>
|
||||
<string name="copy_password">Copiar contraseña al portapapeles</string>
|
||||
<string name="copied_password_to_clipboard">Contraseña copiada al portapapeles</string>
|
||||
<string name="open_url">Abrir URL</string>
|
||||
<string name="cannot_resolve_action">Ninguna aplicación puede abrir esto</string>
|
||||
<string name="cannot_resolve_action">Ninguna aplicación puede abrir eso</string>
|
||||
<string name="pick_search_engine">Elija un motor de búsqueda</string>
|
||||
<string name="format">Formato</string>
|
||||
<string name="size">Tamaño en píxeles</string>
|
||||
@@ -38,55 +39,78 @@
|
||||
<string name="input_content_here">Introduzca el contenido aquí</string>
|
||||
<string name="encode">CODIFICAR</string>
|
||||
<string name="error_no_content">Contenido no disponible</string>
|
||||
<string name="error_encoding_barcode">No pudo generar el código de barras</string>
|
||||
<string name="error_encoding_barcode">No se pudo generar el código de barras</string>
|
||||
<string name="view_barcode">Ver código de barras</string>
|
||||
<string name="info">Información</string>
|
||||
<string name="switch_camera">Cambiar cámara</string>
|
||||
<string name="bulk_mode">Scan continuously</string>
|
||||
<string name="bulk_mode">Escanear constantemente</string>
|
||||
<string name="bulk_mode_summary">Siempre iniciar escaneando constantemente</string>
|
||||
<string name="bulk_mode_delay">Delay between continuous scans</string>
|
||||
<string name="restrict_format">Restrict format</string>
|
||||
<string name="remove_restriction">Remove restriction</string>
|
||||
<string name="scan_format">Scan %s</string>
|
||||
<string name="quarter_second">A quarter second</string>
|
||||
<string name="half_second">Half a second</string>
|
||||
<string name="a_second">One second</string>
|
||||
<string name="two_seconds">Two seconds</string>
|
||||
<string name="five_seconds">Five seconds</string>
|
||||
<string name="history">Historial</string>
|
||||
<string name="preferences">Configuración</string>
|
||||
<string name="scan_category">Scan</string>
|
||||
<string name="content_category">Content</string>
|
||||
<string name="locale_category">Language</string>
|
||||
<string name="custom_locale">Custom locale</string>
|
||||
<string name="follow_system_settings">Follow system settings</string>
|
||||
<string name="show_crop_handle">Show cropping limiter</string>
|
||||
<string name="show_crop_handle_summary">Restrict scanning to a customizable region.</string>
|
||||
<string name="zoom_by_swiping">Aumento de cámara al deslizar hacia arriba/abajo</string>
|
||||
<string name="zoom_by_swiping_summary">Swipe up or down in camera screen to control the camera zoom.</string>
|
||||
<string name="scan_category">Escanear</string>
|
||||
<string name="content_category">Contenido</string>
|
||||
<string name="locale_category">Idioma</string>
|
||||
<string name="custom_locale">Idioma personalizado</string>
|
||||
<string name="follow_system_settings">Seguir los ajustes del sistema</string>
|
||||
<string name="show_crop_handle">Mostrar el delimitador de recorte</string>
|
||||
<string name="show_crop_handle_summary">Restringir el escaneo a una región personalizable.</string>
|
||||
<string name="barcode_formats">Formatos de códigos de barras</string>
|
||||
<string name="zoom_by_swiping">Deslice hacia arriba/abajo para ampliar</string>
|
||||
<string name="zoom_by_swiping_summary">Deslice su dedo hacia arriba o abajo en la pantalla para controlar el aumento de la cámara.</string>
|
||||
<string name="auto_rotate">Reconocer los códigos de barras 1D verticalmente</string>
|
||||
<string name="auto_rotate_summary">Automatically rotate the camera frame to recognize vertical 1D barcodes. Depending on the device, this may affect performance.</string>
|
||||
<string name="try_harder">Optimizar el lector para la precisión, no la velocidad</string>
|
||||
<string name="try_harder_summary">Enable this option for very hard to read codes. Will affect performance.</string>
|
||||
<string name="vibrate">Vibración en la detección</string>
|
||||
<string name="vibrate_summary">Enable this if you want your device to vibrate when a code is recognized.</string>
|
||||
<string name="use_history">Guardar historial de escaneados</string>
|
||||
<string name="use_history_summary">Save recognized codes on your device.</string>
|
||||
<string name="ignore_consecutive_duplicates">No guardar duplicados consecutivos</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">Do not save consecutive duplicates in the scan history.</string>
|
||||
<string name="open_immediately">Saltar la inspección y abrir el contenido inmediatamente</string>
|
||||
<string name="open_immediately_summary">Skip inspection and open contents immediately. May open harmful content if enabled.</string>
|
||||
<string name="copy_immediately">Automatically copy contents into clipboard</string>
|
||||
<string name="copy_immediately_summary">Automatically copy scanned contents into clipboard. Please note that other apps may snoop on your clipboard in the background.</string>
|
||||
<string name="auto_rotate_summary">Rotar automáticamente el marco de la cámara para reconocer los códigos de barras 1D verticales. Dependiendo del dispositivo, esto puede afectar al rendimiento.</string>
|
||||
<string name="try_harder">Optimizar la precisión del lector</string>
|
||||
<string name="try_harder_summary">Active esta opción para códigos muy difíciles de leer. Esto afectará al rendimiento.</string>
|
||||
<string name="show_toast_in_bulk_mode">Mostrar datos en el modo constante</string>
|
||||
<string name="show_toast_in_bulk_mode_summary">Mostrar brevemente los datos escaneados al escanear constantemente. </string>
|
||||
<string name="vibrate">Vibrar al detectar</string>
|
||||
<string name="vibrate_summary">Active esta opción si desea que su dispositivo vibre cuando se reconozca un código.</string>
|
||||
<string name="use_history">Guardar historial de escaneo</string>
|
||||
<string name="use_history_summary">Guardar códigos reconocidos en su dispositivo.</string>
|
||||
<string name="ignore_consecutive_duplicates">Ignorar duplicados</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">No guarde duplicados consecutivos en el historial de escaneo.</string>
|
||||
<string name="open_immediately">Abrir inmediatamente</string>
|
||||
<string name="open_immediately_summary">Omite la inspección y abre el contenido inmediatamente. Puede abrir contenido dañino si está activado.</string>
|
||||
<string name="copy_immediately">Poner en el portapapeles</string>
|
||||
<string name="copy_immediately_summary">Copiar contenido al portapapeles automáticamente. Ten en cuenta que otras aplicaciones pueden husmear en tu portapapeles en segundo plano.</string>
|
||||
<string name="show_meta_data">Mostrar metadatos</string>
|
||||
<string name="show_meta_data_summary">Show additional data about the scanned barcoded.</string>
|
||||
<string name="show_hex_dump">Mostrar volcado hexagonal</string>
|
||||
<string name="show_hex_dump_summary">Show a hex dump of the scanned contents.</string>
|
||||
<string name="close_automatically">Go back after Copy/Share</string>
|
||||
<string name="close_automatically_summary">Automatically return to the scan screen after copying or sharing the read contents.</string>
|
||||
<string name="open_with_url">Abrir datos desconocidos con URL</string>
|
||||
<string name="send_category">Forwarding</string>
|
||||
<string name="send_scan_url">Send every scan to URL</string>
|
||||
<string name="send_scan_type">Type of request for every scan</string>
|
||||
<string name="send_type_get_add_content">GET and simply add content</string>
|
||||
<string name="send_type_get_query_string">GET with compelete query string</string>
|
||||
<string name="show_meta_data_summary">Mostrar datos adicionales del código escaneado.</string>
|
||||
<string name="show_hex_dump">Mostrar dump hexadecimal</string>
|
||||
<string name="show_hex_dump_summary">Mostrar un dump hexadecimal del contenido escaneado.</string>
|
||||
<string name="close_automatically">Retroceder luego de Copiar/Compartir</string>
|
||||
<string name="close_automatically_summary">Regresar automáticamente a la pantalla de escaneo luego de copiar o compartir el contenido leído.</string>
|
||||
<string name="default_search_engine">Abrir datos desconocidos en</string>
|
||||
<string name="always_ask">Preguntar siempre</string>
|
||||
<string name="open_with_url">Abrir datos desconocidos con la URL</string>
|
||||
<string name="clear_network_suggestions">Borrar las sugerencias de red</string>
|
||||
<string name="clear_network_suggestions_summary">Elimina todas las sugerencias de red previamente proporcionadas por esta aplicación.</string>
|
||||
<string name="really_remove_all_networks">¿Realmente eliminar todas las redes?</string>
|
||||
<string name="clear_network_suggestions_success">Sugerencias de red eliminadas</string>
|
||||
<string name="clear_network_suggestions_nothing_to_remove">Nada que eliminar</string>
|
||||
<string name="send_category">Reenvío</string>
|
||||
<string name="send_scan_active">Forward scans</string>
|
||||
<string name="send_scan_active_summary">Enable to forward scans to a the given URL</string>
|
||||
<string name="send_scan_url">Enviar todo escaneo a la URL</string>
|
||||
<string name="send_scan_type">Tipo de solicitud para cada escaneo</string>
|
||||
<string name="send_type_get_add_content">GET y solo añadir contenido</string>
|
||||
<string name="send_type_get_query_string">GET con consulta completa</string>
|
||||
<string name="send_type_post_form">POST application/x-www-form-urlencoded</string>
|
||||
<string name="send_type_post_json">POST application/json</string>
|
||||
<string name="test_url">Test URL</string>
|
||||
<string name="send_type_external_browser">Open in external browser</string>
|
||||
<string name="test_url">URL de prueba</string>
|
||||
<string name="really_remove_scan">¿Realmente desea borrar el escaneo seleccionado?</string>
|
||||
<string name="really_remove_all_scans">¿Realmente desea borrar todo lo escaneado?</string>
|
||||
<string name="really_remove_selected_scans">¿Realmente desea borrar los escaneos seleccionados?</string>
|
||||
<string name="clear_history">Borrar historial</string>
|
||||
<string name="clear_history">Borrar el historial</string>
|
||||
<string name="copy_scan">Copiar escaneo</string>
|
||||
<string name="edit_scan">Editar etiqueta</string>
|
||||
<string name="remove_scan">Borrar escaneo</string>
|
||||
@@ -100,34 +124,44 @@
|
||||
<string name="file_name">Nombre del archivo</string>
|
||||
<string name="error_saving_file">No se puede guardar el archivo</string>
|
||||
<string name="error_file_exists">El archivo ya existe</string>
|
||||
<string name="connect_to_wifi">Conectar a WiFi</string>
|
||||
<string name="wifi_config_failed">No se pudo configurar el WiFi</string>
|
||||
<string name="wifi_added">WiFi añadido</string>
|
||||
<string name="connect_to_wifi">Conectar al Wifi</string>
|
||||
<string name="wifi_config_failed">No se pudo configurar el Wifi</string>
|
||||
<string name="wifi_added">Wifi añadido</string>
|
||||
<string name="sms_send">Enviar SMS</string>
|
||||
<string name="sms_error">No se puedo enviar SMS</string>
|
||||
<string name="tel_dial">Marcar número</string>
|
||||
<string name="tel_error">No se pudo marcar el número</string>
|
||||
<string name="mail_send">Enviar correo</string>
|
||||
<string name="mail_error">No se pudo enviar correo</string>
|
||||
<string name="mail_error">No se pudo enviar el correo</string>
|
||||
<string name="vcard_add">Añadir a contactos</string>
|
||||
<string name="vcard_failed">No se pudo añadir a los contactos</string>
|
||||
<string name="vevent_add">Añadir se pudo calendario</string>
|
||||
<string name="vevent_add">Añadir al calendario</string>
|
||||
<string name="vevent_failed">No se pudo añadir al calendario</string>
|
||||
<string name="otpauth_add">Añadir 2FA</string>
|
||||
<string name="search_web">Buscar en la web</string>
|
||||
<string name="search_scan">Buscar escaneos</string>
|
||||
<string name="search_scan">Buscar escaneo</string>
|
||||
<string name="export_to_file">Exportar a archivo</string>
|
||||
<string name="export_as">¿Dónde desea exportar?</string>
|
||||
<string name="export_csv_comma">CSV con comas</string>
|
||||
<string name="export_csv_semicolon">CSV con punto y coma</string>
|
||||
<string name="export_json">Exportar como JSON</string>
|
||||
<string name="export_database">Exportar base de datos SQLite</string>
|
||||
<string name="export_as">¿Exportar como?</string>
|
||||
<string name="export_csv_comma">Archivo CSV con comas</string>
|
||||
<string name="export_csv_semicolon">Archivo CSV con punto y coma</string>
|
||||
<string name="export_json">Archivo JSON</string>
|
||||
<string name="export_database">Base de datos SQLite</string>
|
||||
<string name="saved_in_downloads">Guardado en Descargas</string>
|
||||
<string name="rotate_image_cw">Rotar la imagen en el sentido de las agujas del reloj</string>
|
||||
<string name="pick_file">Seleccionar archivo de imagen</string>
|
||||
<string name="pick_code_to_scan">Escoge el código para escanear</string>
|
||||
<string name="shortcut_decode">Escanear un código de barras</string>
|
||||
<string name="shortcut_encode">Crear un código de barras</string>
|
||||
<string name="rotate_image_cw">Rotar la imagen en sentido horario</string>
|
||||
<string name="pick_file">Seleccionar imagen</string>
|
||||
<string name="pick_code_to_scan">Seleccione el código a escanear</string>
|
||||
<string name="shortcut_decode">Escanear un código</string>
|
||||
<string name="shortcut_encode">Crear un código</string>
|
||||
<string name="shortcut_preferences">Ajustes</string>
|
||||
<string name="background_request_failed">Background request failed</string>
|
||||
<string name="background_request_failed">Solicitud de fondo fallida</string>
|
||||
<string name="entry_type">Type</string>
|
||||
<string name="wifi_network">Wi-Fi network</string>
|
||||
<string name="wifi_ssid">Name</string>
|
||||
<string name="wifi_type">Authentication type</string>
|
||||
<string name="wifi_password">Password</string>
|
||||
<string name="wifi_hidden">Hidden network</string>
|
||||
<string name="wifi_eap">EAP method</string>
|
||||
<string name="wifi_anonymous_identity">Anonymous identity</string>
|
||||
<string name="wifi_identity">Identity</string>
|
||||
<string name="wifi_phase2">Phase 2 method</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Cette application n\'a aucun sens sans accès possible à la caméra. Au revoir.</string>
|
||||
<string name="no_camera_no_fun">Cette application ne peut pas fonctionner sans accès à la caméra. Au revoir.</string>
|
||||
<string name="camera_error">Impossible d\'accéder à la caméra, veuillez réessayer</string>
|
||||
<string name="scan_code">Scanner le code</string>
|
||||
<string name="compose_barcode">Créer un code-barres</string>
|
||||
<string name="decode_barcode">Décoder le code-barres</string>
|
||||
<string name="content">Contenu</string>
|
||||
<string name="binary_data">(binary data)</string>
|
||||
<string name="binary_data">(données binaires)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%2$d caractère, %1$s</item>
|
||||
<item quantity="many">%2$d caractères, %1$s</item>
|
||||
<item quantity="other">%2$d caractères, %1$s</item>
|
||||
</plurals>
|
||||
<string name="error_correction_level">Niveau de correction d\'erreur</string>
|
||||
@@ -18,11 +19,12 @@
|
||||
<string name="issue_number">Issue number</string>
|
||||
<string name="orientation">Orientation</string>
|
||||
<string name="other_meta_data">Métadonnées</string>
|
||||
<string name="pdf417_extra_metadata">PDF417 métadonnées</string>
|
||||
<string name="pdf417_extra_metadata">Métadonnées PDF417</string>
|
||||
<string name="possible_country">Pays de fabrication possible</string>
|
||||
<string name="suggested_price">Prix suggéré</string>
|
||||
<string name="upc_ean_extension">UPC EAN extension</string>
|
||||
<string name="toggle_flash">Toggle flash</string>
|
||||
<string name="upc_ean_extension">Extension UPC EAN</string>
|
||||
<string name="toggle_flash">Activer le flash</string>
|
||||
<string name="error_flash">Error toggling flash</string>
|
||||
<string name="share">Partager</string>
|
||||
<string name="share_as">Partager comme ?</string>
|
||||
<string name="copy_to_clipboard">Copier dans le presse-papiers</string>
|
||||
@@ -43,24 +45,37 @@
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Changer de camera</string>
|
||||
<string name="bulk_mode">Scan continu</string>
|
||||
<string name="bulk_mode_summary">Toujours démarrer en scannant en continu.</string>
|
||||
<string name="bulk_mode_delay">Délai entre les scans continus</string>
|
||||
<string name="restrict_format">Restrict format</string>
|
||||
<string name="remove_restriction">Remove restriction</string>
|
||||
<string name="scan_format">Scan %s</string>
|
||||
<string name="quarter_second">Un quart de seconde</string>
|
||||
<string name="half_second">Une demi-seconde</string>
|
||||
<string name="a_second">Une seconde</string>
|
||||
<string name="two_seconds">Deux secondes</string>
|
||||
<string name="five_seconds">Cinq secondes</string>
|
||||
<string name="history">Historique</string>
|
||||
<string name="preferences">Préférences</string>
|
||||
<string name="scan_category">Scan</string>
|
||||
<string name="content_category">Contenu</string>
|
||||
<string name="locale_category">Langue</string>
|
||||
<string name="custom_locale">Local personnalisé</string>
|
||||
<string name="custom_locale">Langue personnalisée</string>
|
||||
<string name="follow_system_settings">Suivre les réglages du système</string>
|
||||
<string name="show_crop_handle">Afficher le limiter de capture</string>
|
||||
<string name="show_crop_handle">Afficher le bouton de rognage</string>
|
||||
<string name="show_crop_handle_summary">Restreindre le scan à une région personnalisée.</string>
|
||||
<string name="barcode_formats">Formats de codes-barres</string>
|
||||
<string name="zoom_by_swiping">Balayage haut/bas pour zoomer</string>
|
||||
<string name="zoom_by_swiping_summary">Balayez vers le haut ou vers le bas dans l’écran de la caméra pour contrôler le zoom de la caméra.</string>
|
||||
<string name="auto_rotate">Reconnaître les codes-barres 1D verticaux</string>
|
||||
<string name="auto_rotate_summary">Tourner automatiquement le cadre de la caméra pour reconnaître les codes-barres 1D verticaux. En fonction de l\'appareil, cela peut affecter les performances.</string>
|
||||
<string name="try_harder">Optimiser la précision du lecteur</string>
|
||||
<string name="try_harder_summary">Activez cette option pour les codes très difficiles à lire. Affectera les performances.</string>
|
||||
<string name="show_toast_in_bulk_mode">Afficher les données en mode continu</string>
|
||||
<string name="show_toast_in_bulk_mode_summary">Afficher brièvement les données lors d\'un scan en continu.</string>
|
||||
<string name="vibrate">Vibrer à la détection</string>
|
||||
<string name="vibrate_summary">Activez ceci si vous voulez que votre appareil vibre lorsqu’un code est reconnu.</string>
|
||||
<string name="use_history">Sauvegarder l\'histoire des scans</string>
|
||||
<string name="use_history">Sauvegarder l\'historique des scans</string>
|
||||
<string name="use_history_summary">Sauvegarder les codes reconnus sur votre appareil.</string>
|
||||
<string name="ignore_consecutive_duplicates">Ignorer les doublons</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">Ne pas sauvegarder les doublons dans l\'historique des scans.</string>
|
||||
@@ -70,25 +85,35 @@
|
||||
<string name="copy_immediately_summary">Copier automatiquement les contenus scannés dans le presse-papiers. Veuillez noter que les autres applications peuvent fouiner dans votre presse-papiers en arrière-plan.</string>
|
||||
<string name="show_meta_data">Afficher les méta-données</string>
|
||||
<string name="show_meta_data_summary">Afficher les données supplémentaires sur le code-barres scanné.</string>
|
||||
<string name="show_hex_dump">Show hex dump</string>
|
||||
<string name="show_hex_dump_summary">Show a hex dump of the scanned contents.</string>
|
||||
<string name="close_automatically">Go back after Copy/Share</string>
|
||||
<string name="close_automatically_summary">Automatically return to the scan screen after copying or sharing the read contents.</string>
|
||||
<string name="show_hex_dump">Afficher le dump hexa</string>
|
||||
<string name="show_hex_dump_summary">Affiche un dump hexadécimal du contenu scanné.</string>
|
||||
<string name="close_automatically">Revenir après une copie/partage</string>
|
||||
<string name="close_automatically_summary">Revient automatiquement sur l\'écran de scan après avoir copié ou partagé le contenu lu.</string>
|
||||
<string name="default_search_engine">Ouvrir les données inconnues dans</string>
|
||||
<string name="always_ask">Toujours demander</string>
|
||||
<string name="open_with_url">Ouvrir des données inconnues avec l’URL</string>
|
||||
<string name="clear_network_suggestions">Supprimer les suggestions de réseau</string>
|
||||
<string name="clear_network_suggestions_summary">Supprimez toutes les suggestions de réseau précédemment fournies par cette application.</string>
|
||||
<string name="really_remove_all_networks">Supprimer vraiment tous les réseaux ?</string>
|
||||
<string name="clear_network_suggestions_success">Suggestions de réseau supprimées</string>
|
||||
<string name="clear_network_suggestions_nothing_to_remove">Rien à supprimer</string>
|
||||
<string name="send_category">Transfert</string>
|
||||
<string name="send_scan_url">Envoyer chaque scan avec l\'URL</string>
|
||||
<string name="send_scan_active">Forward scans</string>
|
||||
<string name="send_scan_active_summary">Enable to forward scans to a the given URL</string>
|
||||
<string name="send_scan_url">Envoyer chaque scan vers une URL</string>
|
||||
<string name="send_scan_type">Type de requête pour chaque scan</string>
|
||||
<string name="send_type_get_add_content">GET et ajouter simplement le contenu</string>
|
||||
<string name="send_type_get_query_string">GET avec chaîne de requête complète</string>
|
||||
<string name="send_type_post_form">POST application/x-www-form-urlencoded</string>
|
||||
<string name="send_type_post_json">POST application/json</string>
|
||||
<string name="test_url">Test URL</string>
|
||||
<string name="send_type_external_browser">Ouvrir dans un navigateur externe</string>
|
||||
<string name="test_url">Tester l\'URL</string>
|
||||
<string name="really_remove_scan">Supprimer le scan ?</string>
|
||||
<string name="really_remove_all_scans">Supprimer tous les scans ?</string>
|
||||
<string name="really_remove_selected_scans">Supprimer les scans sélectionnés ?</string>
|
||||
<string name="clear_history">Effacer l\'historique</string>
|
||||
<string name="copy_scan">Copier scan</string>
|
||||
<string name="edit_scan">Editer l\'étiquette</string>
|
||||
<string name="copy_scan">Copier le scan</string>
|
||||
<string name="edit_scan">Éditer l\'étiquette</string>
|
||||
<string name="remove_scan">Supprimer le scan</string>
|
||||
<string name="enter_name">Saisir un nom décrivant le scan</string>
|
||||
<string name="enter_name_hint">Nom du scan</string>
|
||||
@@ -104,7 +129,7 @@
|
||||
<string name="wifi_config_failed">Impossible de configurer le WiFi</string>
|
||||
<string name="wifi_added">WiFi ajouté</string>
|
||||
<string name="sms_send">Envoyer SMS</string>
|
||||
<string name="sms_error">Ne pas envoyer de SMS</string>
|
||||
<string name="sms_error">Impossible d\'envoyer le SMS</string>
|
||||
<string name="tel_dial">Numéro de téléphone</string>
|
||||
<string name="tel_error">Impossible de composer le numéro</string>
|
||||
<string name="mail_send">Envoyer le mail</string>
|
||||
@@ -130,5 +155,14 @@
|
||||
<string name="shortcut_encode">Créer un code-barres</string>
|
||||
<string name="shortcut_preferences">Réglages</string>
|
||||
<string name="background_request_failed">Échec de la requête d\'arrière-plan</string>
|
||||
<string name="entry_type">Type</string>
|
||||
<string name="wifi_network">Réseau Wi-Fi</string>
|
||||
<string name="wifi_ssid">Nom</string>
|
||||
<string name="wifi_type">Type d\'identification</string>
|
||||
<string name="wifi_password">Mot de passe</string>
|
||||
<string name="wifi_hidden">Réseau caché</string>
|
||||
<string name="wifi_eap">Méthode EAP</string>
|
||||
<string name="wifi_anonymous_identity">Identité anonyme</string>
|
||||
<string name="wifi_identity">Identité</string>
|
||||
<string name="wifi_phase2">Méthode de la phase 2</string>
|
||||
</resources>
|
||||
|
||||
|
||||
@@ -10,25 +10,26 @@
|
||||
<item quantity="one">%2$d karakter, %1$s</item>
|
||||
<item quantity="other">%2$d karakter, %1$s</item>
|
||||
</plurals>
|
||||
<string name="error_correction_level">Error correction level</string>
|
||||
<string name="error_correction_level_l" formatted="false">Low (~7% correction)</string>
|
||||
<string name="error_correction_level_m" formatted="false">Medium (~15% correction)</string>
|
||||
<string name="error_correction_level_q" formatted="false">Quartile (~25% correction)</string>
|
||||
<string name="error_correction_level_h" formatted="false">High (~30% correction)</string>
|
||||
<string name="issue_number">Issue number</string>
|
||||
<string name="orientation">Orientation</string>
|
||||
<string name="other_meta_data">Metadata</string>
|
||||
<string name="pdf417_extra_metadata">PDF417 metadata</string>
|
||||
<string name="possible_country">Possible country of manufacture</string>
|
||||
<string name="suggested_price">Suggested price</string>
|
||||
<string name="upc_ean_extension">UPC EAN extension</string>
|
||||
<string name="error_correction_level">Hibajavítási szint</string>
|
||||
<string name="error_correction_level_l" formatted="false">Alacsony (~7% javítás)</string>
|
||||
<string name="error_correction_level_m" formatted="false">Közepes (~15% javítás)</string>
|
||||
<string name="error_correction_level_q" formatted="false">Kvartilis (~25% javítás)</string>
|
||||
<string name="error_correction_level_h" formatted="false">Magas (~30% javítás)</string>
|
||||
<string name="issue_number">Hibaszám</string>
|
||||
<string name="orientation">Tájolás</string>
|
||||
<string name="other_meta_data">Metaadat</string>
|
||||
<string name="pdf417_extra_metadata">PDF417 metaadat</string>
|
||||
<string name="possible_country">Gyártó lehetséges országa</string>
|
||||
<string name="suggested_price">Ajánlott ár</string>
|
||||
<string name="upc_ean_extension">UPC EAN kiterjesztés</string>
|
||||
<string name="toggle_flash">Vaku be- vagy kikapcsolása</string>
|
||||
<string name="error_flash">Error toggling flash</string>
|
||||
<string name="share">Megosztás</string>
|
||||
<string name="share_as">Share as?</string>
|
||||
<string name="share_as">Megosztja másként?</string>
|
||||
<string name="copy_to_clipboard">Másolás a vágólapra</string>
|
||||
<string name="copied_to_clipboard">Vágólapra helyezés</string>
|
||||
<string name="copy_password">Copy password into clipboard</string>
|
||||
<string name="copied_password_to_clipboard">Password copied to clipboard</string>
|
||||
<string name="copy_password">Jelszó másolása a vágólapra</string>
|
||||
<string name="copied_password_to_clipboard">Jelszó kimásolva a vágólapra</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>
|
||||
@@ -42,56 +43,79 @@
|
||||
<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="bulk_mode">Scan continuously</string>
|
||||
<string name="bulk_mode">Beolvasás folyamatosan</string>
|
||||
<string name="bulk_mode_summary">Mindig folyamatos beolvasást indítson.</string>
|
||||
<string name="bulk_mode_delay">Folyamatos beolvasások közti késleltetés</string>
|
||||
<string name="restrict_format">Restrict format</string>
|
||||
<string name="remove_restriction">Remove restriction</string>
|
||||
<string name="scan_format">Scan %s</string>
|
||||
<string name="quarter_second">Negyed másodperc</string>
|
||||
<string name="half_second">Fél másodperc</string>
|
||||
<string name="a_second">Egy másodperc</string>
|
||||
<string name="two_seconds">Két másodperc</string>
|
||||
<string name="five_seconds">Öt másodperc</string>
|
||||
<string name="history">Előzmények</string>
|
||||
<string name="preferences">Beállítások</string>
|
||||
<string name="scan_category">Scan</string>
|
||||
<string name="content_category">Content</string>
|
||||
<string name="locale_category">Language</string>
|
||||
<string name="custom_locale">Custom locale</string>
|
||||
<string name="follow_system_settings">Follow system settings</string>
|
||||
<string name="show_crop_handle">Show cropping limiter</string>
|
||||
<string name="show_crop_handle_summary">Restrict scanning to a customizable region.</string>
|
||||
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
|
||||
<string name="zoom_by_swiping_summary">Swipe up or down in camera screen to control the camera zoom.</string>
|
||||
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
|
||||
<string name="auto_rotate_summary">Automatically rotate the camera frame to recognize vertical 1D barcodes. Depending on the device, this may affect performance.</string>
|
||||
<string name="try_harder">Optimize reader for accuracy, not speed</string>
|
||||
<string name="try_harder_summary">Enable this option for very hard to read codes. Will affect performance.</string>
|
||||
<string name="vibrate">Vibrate on detection</string>
|
||||
<string name="vibrate_summary">Enable this if you want your device to vibrate when a code is recognized.</string>
|
||||
<string name="scan_category">Beolvasás</string>
|
||||
<string name="content_category">Tartalom</string>
|
||||
<string name="locale_category">Nyelv</string>
|
||||
<string name="custom_locale">Egyéni nyelv</string>
|
||||
<string name="follow_system_settings">Rendszerbeállítások követése</string>
|
||||
<string name="show_crop_handle">Levágási korlátozó megjelenítése</string>
|
||||
<string name="show_crop_handle_summary">Beolvasás korlátozása egy személyre szabható területre.</string>
|
||||
<string name="barcode_formats">Vonalkód formátumok</string>
|
||||
<string name="zoom_by_swiping">Kamera nagyítása fel/le pöccintéssel</string>
|
||||
<string name="zoom_by_swiping_summary">Pöccintsen fel vagy le a kamera képernyőjén a kamera nagyításának vezérléséhez.</string>
|
||||
<string name="auto_rotate">1D vonalkódok felismerése függőlegesen</string>
|
||||
<string name="auto_rotate_summary">A kamera keretének automatikus forgatása a függőleges 1D vonalkódok felismeréséhez. Az eszköztől függően ez hatással lehet a teljesítményre.</string>
|
||||
<string name="try_harder">Olvasó optimalizálása a pontossághoz a sebesség helyett</string>
|
||||
<string name="try_harder_summary">Nagyon nehezen olvasható kódoknál engedélyezze ezt a lehetőséget. Hatással lesz a teljesítményre.</string>
|
||||
<string name="show_toast_in_bulk_mode">Adatok megjelenítése folyamatos módban</string>
|
||||
<string name="show_toast_in_bulk_mode_summary">A beolvasott adatok gyors megjelenítése folyamatos beolvasáskor.</string>
|
||||
<string name="vibrate">Rezgetés felismeréskor</string>
|
||||
<string name="vibrate_summary">Akkor engedélyezze ezt, ha rezgetni szeretné az eszközét egy kód felismerésekor.</string>
|
||||
<string name="use_history">Beolvasási előzmények mentése</string>
|
||||
<string name="use_history_summary">Save recognized codes on your device.</string>
|
||||
<string name="ignore_consecutive_duplicates">Do not save consecutive duplicates</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">Do not save consecutive duplicates in the scan history.</string>
|
||||
<string name="use_history_summary">Felismert kódok mentése a saját eszközén.</string>
|
||||
<string name="ignore_consecutive_duplicates">Ne mentsen egymást követő azonos adatokat</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">Ne mentse az egymást követő azonos adatokat a beolvasási előzményekbe.</string>
|
||||
<string name="open_immediately">Vizsgálat kihagyása és a tartalom azonnali megnyitása</string>
|
||||
<string name="open_immediately_summary">Skip inspection and open contents immediately. May open harmful content if enabled.</string>
|
||||
<string name="copy_immediately">Automatically copy contents into clipboard</string>
|
||||
<string name="copy_immediately_summary">Automatically copy scanned contents into clipboard. Please note that other apps may snoop on your clipboard in the background.</string>
|
||||
<string name="show_meta_data">Show metadata</string>
|
||||
<string name="show_meta_data_summary">Show additional data about the scanned barcoded.</string>
|
||||
<string name="show_hex_dump">Show hex dump</string>
|
||||
<string name="show_hex_dump_summary">Show a hex dump of the scanned contents.</string>
|
||||
<string name="close_automatically">Go back after Copy/Share</string>
|
||||
<string name="close_automatically_summary">Automatically return to the scan screen after copying or sharing the read contents.</string>
|
||||
<string name="open_immediately_summary">Vizsgálat kihagyása és a tartalom azonnali megnyitása. Ártalmas tartalmat is megnyithat, ha engedélyezve van.</string>
|
||||
<string name="copy_immediately">Tartalom automatikus másolása a vágólapra</string>
|
||||
<string name="copy_immediately_summary">Beolvasott tartalom automatikus másolása a vágólapra. Ne feledje, hogy más alkalmazások megfigyelhetik a vágólapját a háttérben.</string>
|
||||
<string name="show_meta_data">Metaadatok megjelenítése</string>
|
||||
<string name="show_meta_data_summary">További adatok megjelenítése a beolvasott vonalkódokról.</string>
|
||||
<string name="show_hex_dump">Hexakiírás megjelenítése</string>
|
||||
<string name="show_hex_dump_summary">A beolvasott tartalom hexakiírásának megjelenítése.</string>
|
||||
<string name="close_automatically">Ugrás vissza másolás vagy megosztás után</string>
|
||||
<string name="close_automatically_summary">Automatikus visszatérés a beolvasási képernyőre a beolvasott tartalom másolása vagy megosztása után.</string>
|
||||
<string name="default_search_engine">Ismeretlen adatok megnyitása ebben</string>
|
||||
<string name="always_ask">Mindig kérdezzen</string>
|
||||
<string name="open_with_url">Ismeretlen adat megnyitása URL-lel</string>
|
||||
<string name="send_category">Forwarding</string>
|
||||
<string name="send_scan_url">Send every scan to URL</string>
|
||||
<string name="send_scan_type">Type of request for every scan</string>
|
||||
<string name="send_type_get_add_content">GET and simply add content</string>
|
||||
<string name="send_type_get_query_string">GET with compelete query string</string>
|
||||
<string name="clear_network_suggestions">Hálózati javaslatok törlése</string>
|
||||
<string name="clear_network_suggestions_summary">Az alkalmazás által korábban biztosított összes hálózati javaslat eltávolítása.</string>
|
||||
<string name="really_remove_all_networks">Valóban eltávolítja az összes hálózatot?</string>
|
||||
<string name="clear_network_suggestions_success">Hálózati javaslatok törölve</string>
|
||||
<string name="clear_network_suggestions_nothing_to_remove">Nincs mit eltávolítani</string>
|
||||
<string name="send_category">Továbbítás</string>
|
||||
<string name="send_scan_active">Forward scans</string>
|
||||
<string name="send_scan_active_summary">Enable to forward scans to a the given URL</string>
|
||||
<string name="send_scan_url">Összes beolvasás küldése egy URL-re</string>
|
||||
<string name="send_scan_type">Kérés típusa az egyes beolvasásoknál</string>
|
||||
<string name="send_type_get_add_content">GET és a tartalom egyszerű hozzáadása</string>
|
||||
<string name="send_type_get_query_string">GET a teljes lekérdezési karakterlánccal</string>
|
||||
<string name="send_type_post_form">POST application/x-www-form-urlencoded</string>
|
||||
<string name="send_type_post_json">POST application/json</string>
|
||||
<string name="send_type_external_browser">Open in external browser</string>
|
||||
<string name="test_url">Test URL</string>
|
||||
<string name="really_remove_scan">Valóban eltávolítja a beolvasást?</string>
|
||||
<string name="really_remove_all_scans">Valóban eltávolítja az összes beolvasását?</string>
|
||||
<string name="really_remove_selected_scans">Really remove selected scans?</string>
|
||||
<string name="really_remove_selected_scans">Valóban eltávolítja a kijelölt beolvasásokat?</string>
|
||||
<string name="clear_history">Előzmények törlése</string>
|
||||
<string name="copy_scan">Copy scan</string>
|
||||
<string name="edit_scan">Edit label</string>
|
||||
<string name="remove_scan">Remove scan</string>
|
||||
<string name="enter_name">Enter a name to describe the scan</string>
|
||||
<string name="enter_name_hint">Name of the scan</string>
|
||||
<string name="copy_scan">Beolvasás másolása</string>
|
||||
<string name="edit_scan">Címke szerkesztése</string>
|
||||
<string name="remove_scan">Beolvasás eltávolítása</string>
|
||||
<string name="enter_name">Adjon meg egy nevet a beolvasás leírásához</string>
|
||||
<string name="enter_name_hint">A beolvasás neve</string>
|
||||
<string name="no_barcode_found">Nem található vonalkód</string>
|
||||
<string name="pick_list_separator">Hogyan kell elválasztani a listaelemeket?</string>
|
||||
<string name="separator_line_break">Sortörés</string>
|
||||
@@ -100,9 +124,9 @@
|
||||
<string name="file_name">Fájlnév</string>
|
||||
<string name="error_saving_file">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="connect_to_wifi">Kapcsolódás Wi-Fi-hez</string>
|
||||
<string name="wifi_config_failed">Nem sikerült beállítani a Wi-Fi-t</string>
|
||||
<string name="wifi_added">Wi-Fi 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>
|
||||
@@ -115,19 +139,29 @@
|
||||
<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="search_web">Keresés a weben</string>
|
||||
<string name="search_scan">Search scan</string>
|
||||
<string name="search_scan">Beolvasás keresése</string>
|
||||
<string name="export_to_file">Exportálás fájlba</string>
|
||||
<string name="export_as">Export as?</string>
|
||||
<string name="export_csv_comma">CSV file with commas</string>
|
||||
<string name="export_csv_semicolon">CSV file with semicolons</string>
|
||||
<string name="export_json">JSON file</string>
|
||||
<string name="export_database">SQLite database</string>
|
||||
<string name="export_as">Exportálja másként?</string>
|
||||
<string name="export_csv_comma">CSV-fájl vesszővel</string>
|
||||
<string name="export_csv_semicolon">CSV-fájl pontosvesszővel</string>
|
||||
<string name="export_json">JSON-fájl</string>
|
||||
<string name="export_database">SQLite adatbázis</string>
|
||||
<string name="saved_in_downloads">Elmentve a letöltésekbe</string>
|
||||
<string name="rotate_image_cw">Kép forgatása jobbra</string>
|
||||
<string name="pick_file">Képfájl kiválasztása</string>
|
||||
<string name="pick_code_to_scan">Kód kiválasztása a beolvasáshoz</string>
|
||||
<string name="shortcut_decode">Scan a barcode</string>
|
||||
<string name="shortcut_encode">Create a barcode</string>
|
||||
<string name="shortcut_preferences">Settings</string>
|
||||
<string name="background_request_failed">Background request failed</string>
|
||||
<string name="shortcut_decode">Vonalkód beolvasása</string>
|
||||
<string name="shortcut_encode">Vonalkód létrehozása</string>
|
||||
<string name="shortcut_preferences">Beállítások</string>
|
||||
<string name="background_request_failed">A háttérkérés sikertelen</string>
|
||||
<string name="entry_type">Típus</string>
|
||||
<string name="wifi_network">Wi-Fi hálózat</string>
|
||||
<string name="wifi_ssid">Név</string>
|
||||
<string name="wifi_type">Hitelesítés típusa</string>
|
||||
<string name="wifi_password">Jelszó</string>
|
||||
<string name="wifi_hidden">Rejtett hálózat</string>
|
||||
<string name="wifi_eap">EAP módszer</string>
|
||||
<string name="wifi_anonymous_identity">Névtelen személyazonosság</string>
|
||||
<string name="wifi_identity">Személyazonosság</string>
|
||||
<string name="wifi_phase2">Phase 2 módszer</string>
|
||||
</resources>
|
||||
|
||||