Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ad62cb5a03 | ||
|
|
e1905b1ff2 | ||
|
|
9aecf0d83b | ||
|
|
2645366baa | ||
|
|
37ea4ac9af | ||
|
|
4a6f5c5f92 | ||
|
|
b1ddb0fe7a | ||
|
|
2f61db731d | ||
|
|
b0de90c702 | ||
|
|
8df09090c9 | ||
|
|
9feef33275 | ||
|
|
147ad22df0 | ||
|
|
be833b7645 | ||
|
|
0eb921d579 | ||
|
|
1d156e0dbd | ||
|
|
82c1225848 | ||
|
|
33c1697d80 | ||
|
|
1d4e5d25ae | ||
|
|
386b47fbed | ||
|
|
b2c2632d76 | ||
|
|
4d64a5704d | ||
|
|
8875937022 | ||
|
|
3d33f62d7b | ||
|
|
a4050cbf8f | ||
|
|
abb362230e | ||
|
|
967cab9c7b | ||
|
|
c8d7be95e9 | ||
|
|
18146ec41a | ||
|
|
5cd6114ba1 | ||
|
|
8a310a1144 | ||
|
|
afc597ab90 | ||
|
|
6d742c4d19 | ||
|
|
1776559d40 | ||
|
|
75c1eb6b64 | ||
|
|
c5cffc00c3 | ||
|
|
c0153e822c | ||
|
|
f070269c2c | ||
|
|
3e8df89db1 | ||
|
|
56237c43cd | ||
|
|
7fc565ae67 | ||
|
|
656c1e36a8 | ||
|
|
42a6043f63 | ||
|
|
6a5fdae584 | ||
|
|
899deb77b5 | ||
|
|
3419194bfd | ||
|
|
3b55d48f65 | ||
|
|
d34673ddc3 | ||
|
|
242bf4c0a1 | ||
|
|
ed2a12eef8 | ||
|
|
4dd5c12e85 | ||
|
|
d3fae01786 | ||
|
|
37b6e6f832 | ||
|
|
e3274fd065 | ||
|
|
c75b9f73ab | ||
|
|
1432669aee | ||
|
|
00a8a94b59 | ||
|
|
c8c3da91ae | ||
|
|
8cbc73b7f8 | ||
|
|
941d875b3e | ||
|
|
1ac578845e | ||
|
|
e816e8bf89 | ||
|
|
54c368fcd8 | ||
|
|
c225e7d9eb | ||
|
|
6a0ce45a65 | ||
|
|
11c97cc4ef | ||
|
|
e3f6732bb8 | ||
|
|
53ae706b9c | ||
|
|
26a7ad2e6e | ||
|
|
9c3984ce7a | ||
|
|
f7d07e0b8a | ||
|
|
3fe0f50289 | ||
|
|
e86cf9d2cc | ||
|
|
4f1482d2c9 | ||
|
|
c42ad7dbea | ||
|
|
fa3cc8c408 | ||
|
|
e4863bad4c | ||
|
|
484bbbc997 |
+156
@@ -1,5 +1,161 @@
|
||||
# Change Log
|
||||
|
||||
## 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
|
||||
* Show HTTP response for background requests
|
||||
* Improve SVG export
|
||||
* Automatically continue with an action after granting a permission
|
||||
* Update Chinese translation
|
||||
* Update French translation
|
||||
* Update Indonesian translation
|
||||
* Update Turkish translation
|
||||
* Update Ukrainian translation
|
||||
|
||||
## 1.35.0
|
||||
* Put images with transparent pixels on white/black
|
||||
* Use cropping limiter when scanning from images too
|
||||
* Support setting the error correction level when creating QR Codes
|
||||
* Support setting a custom deviating locale
|
||||
* Use adaptive icons for shortcut icons
|
||||
* Add exporting barcodes as plain text
|
||||
* Add Ukrainian translation
|
||||
* Add Turkish translation
|
||||
* Add Georgian translation
|
||||
* Update Indonesian translation
|
||||
* Update Russian translation
|
||||
* Update Italian translation
|
||||
* Update Simplified Chinese translation
|
||||
|
||||
## 1.34.0
|
||||
* Use Material Design settings with headers
|
||||
* Add a setting to choose the message type when forwarding scanned content
|
||||
|
||||
## 1.33.0
|
||||
* Add a setting to automatically send the scanned content to a custom URL
|
||||
* Add a setting to automatically put contents into clipboard
|
||||
* Add an explicit button to copy WiFi password into clipboard
|
||||
* Update Indonesian translation
|
||||
|
||||
## 1.32.1
|
||||
* Alternatively remove region of interest by dragging handle to a screen corner
|
||||
* Restrict region of interest to screen space
|
||||
* Remove extra action item to create barcode
|
||||
|
||||
## 1.32.0
|
||||
* Add bulk mode
|
||||
* Remember region of interest permanently
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+54
@@ -4,3 +4,57 @@ Binary Eye does not collect any personal information.
|
||||
|
||||
If you have any questions or suggestions about my Privacy Policy, do not
|
||||
hesitate to contact me.
|
||||
|
||||
## Permissions
|
||||
|
||||
### [ACCESS_FINE_LOCATION][ACCESS_FINE_LOCATION]
|
||||
|
||||
Required to add a WiFi network from a barcode (usually a QR Code).
|
||||
|
||||
Before Android Q, this feature requires using
|
||||
[WifiManager.getConfiguredNetworks][configuredNetworks]
|
||||
which requires `ACCESS_FINE_LOCATION`.
|
||||
|
||||
On Android Q an better, this permission is not requested nor required.
|
||||
|
||||
### [CAMERA][CAMERA]
|
||||
|
||||
Required to read a barcode from the camera image.
|
||||
|
||||
### [INTERNET][INTERNET]
|
||||
|
||||
Required to send a scanned code to a custom URL you can specify in settings.
|
||||
|
||||
### [VIBRATE][VIBRATE]
|
||||
|
||||
Required to vibrate on detection.
|
||||
|
||||
### [WRITE_EXTERNAL_STORAGE][WRITE_EXTERNAL_STORAGE]
|
||||
|
||||
Required to save generated barcodes as files before Android Q.
|
||||
|
||||
### [CHANGE_WIFI_STATE][CHANGE_WIFI_STATE]
|
||||
|
||||
Required to add a WiFi network from a barcode (usually a QR Code).
|
||||
|
||||
On Android Q and better, this feature requires using
|
||||
[WifiManager.addNetworkSuggestions][addNetworkSuggestions]
|
||||
which requires `CHANGE_WIFI_STATE`.
|
||||
|
||||
### [ACCESS_WIFI_STATE][ACCESS_FINE_LOCATION]
|
||||
|
||||
Required to add a WiFi network from a barcode (usually a QR Code).
|
||||
|
||||
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)
|
||||
|
||||
@@ -12,9 +12,14 @@ library.
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screencap-scanning.png"
|
||||
alt="Screenshot Gallery" width="160"/>
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screencap-scanning-cropped.png"
|
||||
alt="Screenshot Gallery" width="160"/>
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screencap-decoded.png"
|
||||
alt="Screenshot Theme" width="160"/>
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screencap-compose-barcode.png"
|
||||
alt="Screenshot Editor" width="160"/>
|
||||
|
||||
## Download
|
||||
|
||||
@@ -59,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.
|
||||
@@ -79,29 +132,6 @@ be thrown and the only option is to restart the app, this time with calling
|
||||
Calling `RenderScript.forceCompat()` means the processing is done in
|
||||
software so you probably don't want to enable it by default.
|
||||
|
||||
### macOS Catalina
|
||||
|
||||
At the time of writing, calling `RenderScript.forceCompat()` in a build made
|
||||
on macOS Catalina with build tools 29.0.3 will crash the app. This is a bug
|
||||
in the build tools for Catalina.
|
||||
|
||||
So *do not* build release builts on macOS Catalina with build tools 29.0.3.
|
||||
|
||||
Building on Linux with 29.0.3 works fine.
|
||||
|
||||
### Side by Side NDK
|
||||
|
||||
At the time of writing, it's important to _not_ have the NDK available when
|
||||
compiling the custom rotation kernel. This will produce a broken build for
|
||||
some ARMv7 devices running Android 6 (e.g. One Plus X, Yotaphone 2, Moto E)
|
||||
while it works for newer Android versions.
|
||||
|
||||
### App Bundle
|
||||
|
||||
At the time of writing, `./gradlew bundleRelease` will produce a broken
|
||||
App Bundle without the necessary RenderScript libraries. This is another
|
||||
bug in the build tools.
|
||||
|
||||
### 2D barcodes
|
||||
|
||||
If you want to fork this and are only interested in reading 2D barcodes
|
||||
|
||||
+38
-15
@@ -3,25 +3,28 @@ apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion sdk_version
|
||||
buildToolsVersion build_tools_version
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion sdk_version
|
||||
|
||||
versionCode 69
|
||||
versionName '1.32.0'
|
||||
versionCode 94
|
||||
versionName '1.49.0'
|
||||
|
||||
// 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
|
||||
|
||||
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
config {
|
||||
release {
|
||||
keyAlias System.getenv('ANDROID_KEY_ALIAS')
|
||||
keyPassword System.getenv('ANDROID_KEY_PASSWORD')
|
||||
storePassword System.getenv('ANDROID_STORE_PASSWORD')
|
||||
@@ -45,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'
|
||||
}
|
||||
}
|
||||
@@ -64,15 +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"
|
||||
// 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.android.support:preference-v7:$support_version"
|
||||
implementation "com.android.support:preference-v14:$support_version"
|
||||
implementation 'com.google.zxing:core:3.4.1'
|
||||
implementation 'com.github.markusfisch:CameraView:1.9.1'
|
||||
implementation 'com.github.markusfisch:ScalingImageView:1.2.0'
|
||||
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>
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.support.test.InstrumentationRegistry
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
import de.markusfisch.android.binaryeye.rs.Preprocessor
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
import org.junit.Assert.fail;
|
||||
@@ -15,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,7 +40,7 @@ class PreprocessorTest {
|
||||
val frameHeight = bitmap.height
|
||||
val frameOrientation = m.group(1) ?: return
|
||||
val preprocessor = Preprocessor(
|
||||
InstrumentationRegistry.getTargetContext(),
|
||||
rs,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
null
|
||||
@@ -55,9 +59,9 @@ class PreprocessorTest {
|
||||
}
|
||||
val result = zxing.decode(frameData, outWidth, outHeight)
|
||||
preprocessor.destroy()
|
||||
|
||||
checkNotNull(result) { "no barcode found in $sample" }
|
||||
}
|
||||
rs.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,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()
|
||||
}
|
||||
|
||||
@@ -5,19 +5,36 @@
|
||||
<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"/>
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="http"/>
|
||||
</intent>
|
||||
</queries>
|
||||
<application
|
||||
tools:ignore="UnusedAttribute"
|
||||
android:name=".app.BinaryEyeApp"
|
||||
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"
|
||||
@@ -25,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"/>
|
||||
@@ -37,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"/>
|
||||
@@ -51,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"/>
|
||||
@@ -74,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))
|
||||
}
|
||||
}
|
||||
|
||||
+27
-19
@@ -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(
|
||||
|
||||
+14
-5
@@ -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
|
||||
|
||||
+2
-1
@@ -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"),
|
||||
|
||||
+57
-54
@@ -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
|
||||
@@ -14,6 +18,7 @@ import android.support.design.widget.FloatingActionButton
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.support.v8.renderscript.RSRuntimeException
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
@@ -21,14 +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
|
||||
@@ -39,16 +51,20 @@ 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
|
||||
private lateinit var cameraView: CameraView
|
||||
private lateinit var detectorView: DetectorView
|
||||
@@ -56,16 +72,17 @@ 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 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
|
||||
|
||||
@@ -101,11 +118,21 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
base?.applyLocale(prefs.customLocale)
|
||||
super.attachBaseContext(base)
|
||||
}
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setContentView(R.layout.activity_camera)
|
||||
|
||||
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
// Necessary to get the right translation after setting a
|
||||
// custom locale.
|
||||
setTitle(R.string.scan_code)
|
||||
|
||||
rs = RenderScript.create(this)
|
||||
vibrator = getVibrator()
|
||||
|
||||
initSystemBars(this)
|
||||
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
|
||||
@@ -131,19 +158,51 @@ 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
|
||||
}
|
||||
isReturnUrl(intent) -> intent?.data?.let {
|
||||
returnUrlTemplate = it.getQueryParameter("ret")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetPreProcessor() {
|
||||
preprocessor?.destroy()
|
||||
preprocessor = null
|
||||
@@ -169,8 +228,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) {
|
||||
@@ -179,6 +236,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) {
|
||||
@@ -186,12 +244,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
|
||||
}
|
||||
@@ -215,11 +274,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
|
||||
@@ -233,6 +292,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 -> {
|
||||
@@ -257,16 +321,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) {
|
||||
@@ -308,8 +389,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) {
|
||||
@@ -327,7 +408,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
|
||||
@@ -335,8 +416,9 @@ class CameraActivity : AppCompatActivity() {
|
||||
saveZoom()
|
||||
}
|
||||
parameters.zoom = zoomBar.progress
|
||||
View.VISIBLE
|
||||
} else {
|
||||
zoomBar.visibility = View.GONE
|
||||
View.GONE
|
||||
}
|
||||
val sceneModes = parameters.supportedSceneModes
|
||||
sceneModes?.let {
|
||||
@@ -357,9 +439,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
|
||||
@@ -400,7 +482,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
progress: Int,
|
||||
fromUser: Boolean
|
||||
) {
|
||||
setZoom(progress)
|
||||
cameraView.camera?.setZoom(progress)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
@@ -411,16 +493,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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,22 +527,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) {
|
||||
@@ -478,20 +542,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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,10 +597,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
|
||||
}
|
||||
}
|
||||
@@ -553,7 +616,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
frameOrientation
|
||||
)
|
||||
val pp = Preprocessor(
|
||||
this,
|
||||
rs,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameRoi
|
||||
@@ -563,13 +626,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,
|
||||
@@ -590,14 +653,19 @@ class CameraActivity : AppCompatActivity() {
|
||||
) {
|
||||
null
|
||||
} else {
|
||||
// map ROI in detectorView to cameraView because cameraView may
|
||||
// be larger than the detectorView
|
||||
// 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.
|
||||
val previewLeft = max(0, previewRect.left)
|
||||
val previewTop = max(0, previewRect.top)
|
||||
val previewRoi = Rect(
|
||||
previewRect.left + viewRoi.left,
|
||||
previewRect.top + viewRoi.top,
|
||||
previewRect.left + viewRoi.right,
|
||||
previewRect.top + viewRoi.bottom
|
||||
previewLeft + viewRoi.left,
|
||||
previewTop + viewRoi.top,
|
||||
previewLeft + viewRoi.right,
|
||||
previewTop + viewRoi.bottom
|
||||
)
|
||||
val previewRectWidth = previewRect.width().toFloat()
|
||||
val previewRectHeight = previewRect.height().toFloat()
|
||||
@@ -607,52 +675,87 @@ 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 (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
|
||||
@@ -660,24 +763,47 @@ 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)
|
||||
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) {
|
||||
activity.toast(R.string.background_request_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bulkMode) {
|
||||
activity.startActivity(
|
||||
MainActivity.getDecodeIntent(activity, scan)
|
||||
@@ -725,3 +851,49 @@ fun getReturnIntent(result: Result): Intent {
|
||||
}
|
||||
return intent
|
||||
}
|
||||
|
||||
private fun isReturnUrl(intent: Intent?): Boolean {
|
||||
val dataString = intent?.dataString
|
||||
return listOf(
|
||||
"binaryeye://scan",
|
||||
"http://markusfisch.de/BinaryEye",
|
||||
"https://markusfisch.de/BinaryEye"
|
||||
).firstOrNull {
|
||||
val result = dataString?.startsWith(it) == true
|
||||
result
|
||||
} != 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
|
||||
|
||||
@@ -2,23 +2,39 @@ package de.markusfisch.android.binaryeye.activity
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.colorSystemAndToolBars
|
||||
import de.markusfisch.android.binaryeye.app.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.app.setFragment
|
||||
import de.markusfisch.android.binaryeye.app.*
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
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() {
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
when (requestCode) {
|
||||
PERMISSION_LOCATION, PERMISSION_WRITE -> if (grantResults.isNotEmpty() &&
|
||||
grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
permissionGrantedCallback?.invoke()
|
||||
permissionGrantedCallback = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
val fm = supportFragmentManager
|
||||
if (fm != null && fm.backStackEntryCount > 0) {
|
||||
@@ -29,6 +45,11 @@ class MainActivity : AppCompatActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
base?.applyLocale(prefs.customLocale)
|
||||
super.attachBaseContext(base)
|
||||
}
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setContentView(R.layout.activity_main)
|
||||
@@ -44,7 +65,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
if (state == null) {
|
||||
setFragment(supportFragmentManager, getFragmentForIntent(intent))
|
||||
supportFragmentManager?.setFragment(getFragmentForIntent(intent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +73,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private const val PREFERENCES = "preferences"
|
||||
private const val HISTORY = "history"
|
||||
private const val ENCODE = "encode"
|
||||
private const val DECODED = "decoded"
|
||||
const val DECODED = "decoded"
|
||||
|
||||
private fun getFragmentForIntent(intent: Intent?): Fragment {
|
||||
intent ?: return PreferencesFragment()
|
||||
|
||||
@@ -4,41 +4,67 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.os.Vibrator
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
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.colorSystemAndToolBars
|
||||
import de.markusfisch.android.binaryeye.app.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.app.vibrate
|
||||
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.view.doOnApplyWindowInsets
|
||||
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.*
|
||||
|
||||
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
|
||||
private lateinit var cropImageView: CropImageView
|
||||
private lateinit var detectorView: DetectorView
|
||||
|
||||
private var result: Result? = null
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
base?.applyLocale(prefs.customLocale)
|
||||
super.attachBaseContext(base)
|
||||
}
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setContentView(R.layout.activity_pick)
|
||||
|
||||
zxing.updateHints(true)
|
||||
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
// Necessary to get the right translation after setting a custom
|
||||
// locale.
|
||||
setTitle(R.string.pick_code_to_scan)
|
||||
|
||||
zxing.updateHints(
|
||||
true,
|
||||
prefs.barcodeFormats
|
||||
)
|
||||
rs = RenderScript.create(this)
|
||||
vibrator = getVibrator()
|
||||
|
||||
initSystemBars(this)
|
||||
val toolbar = findViewById(R.id.toolbar) as Toolbar
|
||||
@@ -49,68 +75,92 @@ class PickActivity : AppCompatActivity() {
|
||||
colorSystemAndToolBars(this@PickActivity)
|
||||
}
|
||||
|
||||
val bitmap = if (
|
||||
intent?.action == Intent.ACTION_SEND &&
|
||||
intent.type?.startsWith("image/") == true
|
||||
) {
|
||||
loadSentImage(intent)
|
||||
} else if (
|
||||
intent?.action == Intent.ACTION_VIEW &&
|
||||
intent.type?.startsWith("image/") == true
|
||||
) {
|
||||
loadImageToOpen(intent)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val bitmap = fixTransparency(rs, getBitmapFromIntent())
|
||||
if (bitmap == null) {
|
||||
applicationContext.toast(R.string.error_no_content)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
var result: Result? = null
|
||||
val scope = CoroutineScope(Dispatchers.IO)
|
||||
val scannedRect = Rect()
|
||||
fun scanWithinBounds() = crop(
|
||||
bitmap,
|
||||
cropImageView.normalizedRectInBounds,
|
||||
cropImageView = findViewById(R.id.image) as CropImageView
|
||||
cropImageView.restrictTranslation = false
|
||||
cropImageView.setImageBitmap(bitmap)
|
||||
cropImageView.onScan = {
|
||||
scanWithinBounds(bitmap)
|
||||
}
|
||||
|
||||
detectorView = findViewById(R.id.detector_view) as DetectorView
|
||||
detectorView.onRoiChanged = {
|
||||
scanWithinBounds(bitmap)
|
||||
}
|
||||
detectorView.setPaddingFromWindowInsets()
|
||||
detectorView.restoreCropHandlePos()
|
||||
|
||||
findViewById(R.id.scan).setOnClickListener {
|
||||
showResult()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBitmapFromIntent(): Bitmap? = if (
|
||||
intent?.action == Intent.ACTION_SEND &&
|
||||
intent.type?.startsWith("image/") == true
|
||||
) {
|
||||
loadSentImage(intent)
|
||||
} else if (
|
||||
intent?.action == Intent.ACTION_VIEW &&
|
||||
intent.type?.startsWith("image/") == true
|
||||
) {
|
||||
loadImageToOpen(intent)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private fun scanWithinBounds(bitmap: Bitmap) {
|
||||
val roi = Rect(detectorView.roi)
|
||||
val rectInView = if (roi.width() < 1) {
|
||||
cropImageView.getBoundsRect()
|
||||
} else {
|
||||
roi
|
||||
}
|
||||
val imageRect = cropImageView.mappedRect
|
||||
val rectInImage = normalizeRoi(imageRect, rectInView)
|
||||
val cropped = bitmap.crop(
|
||||
rectInImage,
|
||||
cropImageView.imageRotation
|
||||
)?.let {
|
||||
scannedRect.set(0, 0, it.width, it.height)
|
||||
scope.launch(Dispatchers.IO) {
|
||||
result = zxing.decodePositiveNegative(it)
|
||||
result?.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (!isFinishing) {
|
||||
vibrator.vibrate()
|
||||
}
|
||||
cropImageView.updateResultPoints(
|
||||
mapResult(
|
||||
scannedRect.width(),
|
||||
scannedRect.height(),
|
||||
0,
|
||||
cropImageView.getBoundsRect(),
|
||||
it
|
||||
)
|
||||
)
|
||||
) ?: return
|
||||
scope.launch {
|
||||
result = zxing.decodePositiveNegative(cropped)
|
||||
result?.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (isFinishing) {
|
||||
return@withContext
|
||||
}
|
||||
vibrator.vibrate()
|
||||
val rc = Rect()
|
||||
imageRect.round(rc)
|
||||
if (!rectInView.intersect(rc)) {
|
||||
return@withContext
|
||||
}
|
||||
detectorView.update(
|
||||
mapResultToView(
|
||||
cropped.width,
|
||||
cropped.height,
|
||||
0,
|
||||
rectInView,
|
||||
it.resultPoints,
|
||||
detectorView.coordinates
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cropImageView = findViewById(R.id.image) as CropImageView
|
||||
cropImageView.setImageBitmap(bitmap)
|
||||
cropImageView.onScan = {
|
||||
scanWithinBounds()
|
||||
}
|
||||
cropImageView.doOnApplyWindowInsets { v, insets ->
|
||||
(v as CropImageView).windowInsets.set(insets)
|
||||
}
|
||||
|
||||
findViewById(R.id.scan).setOnClickListener {
|
||||
scanImage(result)
|
||||
}
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
detectorView.saveCropHandlePos()
|
||||
rs.destroy()
|
||||
parentJob.cancel()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
@@ -136,20 +186,32 @@ 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 scanImage(result: Result?) {
|
||||
if (result != null) {
|
||||
showResult(this, result)
|
||||
private fun showResult() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalizeRoi(imageRect: RectF, roi: Rect): RectF {
|
||||
val w = imageRect.width()
|
||||
val h = imageRect.height()
|
||||
return RectF(
|
||||
(roi.left - imageRect.left) / w,
|
||||
(roi.top - imageRect.top) / h,
|
||||
(roi.right - imageRect.left) / w,
|
||||
(roi.bottom - imageRect.top) / h
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import java.util.*
|
||||
|
||||
fun Context.applyLocale(localeName: String) {
|
||||
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)
|
||||
}
|
||||
@@ -6,44 +6,42 @@ import android.content.pm.PackageManager
|
||||
import android.support.v4.app.ActivityCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
|
||||
var permissionGrantedCallback: (() -> Any)? = null
|
||||
|
||||
const val PERMISSION_CAMERA = 1
|
||||
fun hasCameraPermission(activity: Activity): Boolean {
|
||||
return hasPermission(
|
||||
activity,
|
||||
Manifest.permission.CAMERA,
|
||||
PERMISSION_CAMERA
|
||||
)
|
||||
}
|
||||
fun Activity.hasCameraPermission() = hasPermission(
|
||||
Manifest.permission.CAMERA,
|
||||
PERMISSION_CAMERA
|
||||
)
|
||||
|
||||
const val PERMISSION_WRITE = 2
|
||||
fun hasWritePermission(activity: Activity): 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): 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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
package de.markusfisch.android.binaryeye.content
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.Context
|
||||
@@ -11,7 +11,7 @@ fun Context.copyToClipboard(text: String) {
|
||||
(service as android.text.ClipboardManager).text = text
|
||||
} else {
|
||||
(service as android.content.ClipboardManager).setPrimaryClip(
|
||||
ClipData.newPlainText("simple text", text)
|
||||
ClipData.newPlainText("plain text", text)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -87,6 +86,8 @@ private fun Cursor.toCsvRecord(
|
||||
return sb.toString().toByteArray()
|
||||
}
|
||||
|
||||
private fun String.quoteAndEscape() = "\"${this
|
||||
.replace("\n", " ")
|
||||
.replace("\"", "\"\"")}\""
|
||||
private fun String.quoteAndEscape() = "\"${
|
||||
this
|
||||
.replace("\n", " ")
|
||||
.replace("\"", "\"\"")
|
||||
}\""
|
||||
|
||||
@@ -5,9 +5,23 @@ 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
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
class Database {
|
||||
private lateinit var db: SQLiteDatabase
|
||||
|
||||
@@ -25,7 +39,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 +61,7 @@ class Database {
|
||||
FROM $SCANS
|
||||
${getWhereClause(query)}
|
||||
ORDER BY $SCANS_DATETIME DESC
|
||||
""", getWhereArguments(query)
|
||||
""".trimMargin(), getWhereArguments(query)
|
||||
)
|
||||
|
||||
private fun getWhereClause(
|
||||
@@ -87,7 +101,7 @@ class Database {
|
||||
$SCANS_UPC_EAN_EXTENSION
|
||||
FROM $SCANS
|
||||
WHERE $SCANS_ID = ?
|
||||
""", arrayOf("$id")
|
||||
""".trimMargin(), arrayOf("$id")
|
||||
)?.use {
|
||||
if (it.moveToFirst()) {
|
||||
Scan(
|
||||
@@ -110,47 +124,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 +173,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 +244,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 +261,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,31 @@ package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
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.*
|
||||
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
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import de.markusfisch.android.binaryeye.zxing.Zxing
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import de.markusfisch.android.binaryeye.zxing.encodeAsBitmap
|
||||
import de.markusfisch.android.binaryeye.zxing.encodeAsSvg
|
||||
import de.markusfisch.android.binaryeye.zxing.encodeAsText
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
@@ -26,11 +35,15 @@ import java.util.*
|
||||
|
||||
class BarcodeFragment : Fragment() {
|
||||
private enum class FileType {
|
||||
PNG, SVG
|
||||
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
|
||||
private var content: String = ""
|
||||
private var format: BarcodeFormat? = null
|
||||
|
||||
@@ -56,10 +69,12 @@ class BarcodeFragment : Fragment() {
|
||||
val args = arguments ?: return view
|
||||
val content = args.getString(CONTENT) ?: return view
|
||||
val format = args.getSerializable(FORMAT) as BarcodeFormat? ?: return view
|
||||
val hints = args.getSerializable(HINTS) as EnumMap<EncodeHintType, Any>?
|
||||
val size = args.getInt(SIZE)
|
||||
try {
|
||||
barcodeBitmap = Zxing.encodeAsBitmap(content, format, size, size)
|
||||
barcodeSvg = Zxing.encodeAsSvg(content, format, size, size)
|
||||
barcodeBitmap = encodeAsBitmap(content, format, size, size, hints)
|
||||
barcodeSvg = encodeAsSvg(content, format, hints)
|
||||
barcodeTxt = encodeAsText(content, format, hints)
|
||||
} catch (e: Exception) {
|
||||
var message = e.message
|
||||
if (message == null || message.isEmpty()) {
|
||||
@@ -79,14 +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 {
|
||||
val bitmap = barcodeBitmap
|
||||
bitmap?.let {
|
||||
share(bitmap)
|
||||
it.context?.apply {
|
||||
pickFileType(R.string.share_as) { fileType ->
|
||||
shareAs(fileType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,29 +114,55 @@ 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)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.export_svg -> {
|
||||
askForFileNameAndSave(FileType.SVG)
|
||||
R.id.copy_to_clipboard -> {
|
||||
barcodeTxt?.let {
|
||||
context.copyToClipboard(it)
|
||||
context.toast(R.string.copied_to_clipboard)
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.export_png -> {
|
||||
askForFileNameAndSave(FileType.PNG)
|
||||
R.id.export_to_file -> {
|
||||
context.pickFileType(R.string.export_as) {
|
||||
askForFileNameAndSave(it)
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
// dialogs do not have a parent view
|
||||
private fun Context.pickFileType(
|
||||
title: Int,
|
||||
action: (FileType) -> Unit
|
||||
) {
|
||||
val fileTypes = FileType.values()
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(title)
|
||||
.setItems(fileTypes.map { it.name }.toTypedArray()) { _, which ->
|
||||
action(fileTypes[which])
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
// Dialogs do not have a parent view.
|
||||
@SuppressLint("InflateParams")
|
||||
private fun askForFileNameAndSave(fileType: FileType) {
|
||||
val ac = activity ?: return
|
||||
if (!hasWritePermission(ac)) {
|
||||
// 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)
|
||||
@@ -133,18 +175,26 @@ class BarcodeFragment : Fragment() {
|
||||
when (fileType) {
|
||||
FileType.PNG -> saveAs(
|
||||
addSuffixIfNotGiven(fileName, ".png"),
|
||||
"image/png"
|
||||
MIME_PNG
|
||||
) {
|
||||
barcodeBitmap?.saveAsPng(it)
|
||||
}
|
||||
FileType.SVG -> saveAs(
|
||||
addSuffixIfNotGiven(fileName, ".svg"),
|
||||
"image/svg+xmg"
|
||||
MIME_SVG
|
||||
) { outputStream ->
|
||||
barcodeSvg?.let {
|
||||
outputStream.write(it.toByteArray())
|
||||
}
|
||||
}
|
||||
FileType.TXT -> saveAs(
|
||||
addSuffixIfNotGiven(fileName, ".txt"),
|
||||
MIME_TXT
|
||||
) { outputStream ->
|
||||
barcodeTxt?.let {
|
||||
outputStream.write(it.toByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
@@ -158,18 +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 share(bitmap: Bitmap) {
|
||||
GlobalScope.launch {
|
||||
private fun Context.shareAs(fileType: FileType) {
|
||||
when (fileType) {
|
||||
FileType.PNG -> barcodeBitmap?.let { share(it) }
|
||||
FileType.SVG -> barcodeSvg?.let { shareText(it, MIME_SVG) }
|
||||
FileType.TXT -> barcodeTxt?.let { shareText(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.share(bitmap: Bitmap) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val file = File(
|
||||
context.externalCacheDir,
|
||||
externalCacheDir,
|
||||
"shared_barcode.png"
|
||||
)
|
||||
val success = try {
|
||||
@@ -180,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,16 +251,24 @@ class BarcodeFragment : Fragment() {
|
||||
companion object {
|
||||
private const val CONTENT = "content"
|
||||
private const val FORMAT = "format"
|
||||
private const val HINTS = "hints"
|
||||
private const val SIZE = "size"
|
||||
private const val MIME_PNG = "image/png"
|
||||
private const val MIME_SVG = "image/svg+xmg"
|
||||
private const val MIME_TXT = "text/plain"
|
||||
|
||||
fun newInstance(
|
||||
content: String,
|
||||
format: BarcodeFormat,
|
||||
size: Int
|
||||
size: Int,
|
||||
hints: EnumMap<EncodeHintType, Any>? = null
|
||||
): Fragment {
|
||||
val args = Bundle()
|
||||
args.putString(CONTENT, content)
|
||||
args.putSerializable(FORMAT, format)
|
||||
hints?.let {
|
||||
args.putSerializable(HINTS, it)
|
||||
}
|
||||
args.putInt(SIZE, size)
|
||||
val fragment = BarcodeFragment()
|
||||
fragment.arguments = args
|
||||
@@ -211,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,8 +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
|
||||
@@ -26,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
|
||||
@@ -36,8 +45,10 @@ class DecodeFragment : Fragment() {
|
||||
private val content: String
|
||||
get() = contentView.text.toString()
|
||||
|
||||
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?) {
|
||||
@@ -58,15 +69,16 @@ class DecodeFragment : Fragment() {
|
||||
false
|
||||
)
|
||||
|
||||
closeAutomatically = prefs.closeAutomatically &&
|
||||
activity?.intent?.hasExtra(MainActivity.DECODED) == true
|
||||
|
||||
val scan = arguments?.getParcelable(SCAN) as Scan?
|
||||
?: throw IllegalArgumentException("DecodeFragment needs a Scan")
|
||||
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)
|
||||
@@ -102,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 {
|
||||
@@ -111,18 +123,14 @@ 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 (action is WifiAction) {
|
||||
val password = (action as WifiAction).password
|
||||
if (password != null) {
|
||||
activity?.apply {
|
||||
copyToClipboard(password)
|
||||
toast(R.string.copied_password_to_clipboard)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isBinary) {
|
||||
fillDataView(dataView, inputContent)
|
||||
}
|
||||
|
||||
if (prefs.showMetaData) {
|
||||
@@ -135,6 +143,11 @@ class DecodeFragment : Fragment() {
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
parentJob.cancel()
|
||||
}
|
||||
|
||||
private fun updateViewsAndAction(bytes: ByteArray) {
|
||||
val prevAction = action
|
||||
if (!prevAction.canExecuteOn(bytes)) {
|
||||
@@ -143,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 ""
|
||||
@@ -160,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 = (ctx.resources.displayMetrics.density * 16f).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 {
|
||||
val spaceBetween = (16f * ctx.resources.displayMetrics.density).toInt()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -201,21 +244,32 @@ class DecodeFragment : Fragment() {
|
||||
if (id > 0L) {
|
||||
menu.findItem(R.id.remove).isVisible = true
|
||||
}
|
||||
if (action is WifiAction) {
|
||||
menu.findItem(R.id.copy_password).isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.copy_password -> {
|
||||
copyPasswordToClipboard()
|
||||
maybeBackOrFinish()
|
||||
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
|
||||
@@ -229,6 +283,24 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun textOrHex() = if (isBinary) {
|
||||
raw.toHexString()
|
||||
} else {
|
||||
content
|
||||
}
|
||||
|
||||
private fun copyPasswordToClipboard() {
|
||||
val ac = action
|
||||
if (ac is WifiAction) {
|
||||
ac.password?.let { password ->
|
||||
activity?.apply {
|
||||
copyToClipboard(password)
|
||||
toast(R.string.copied_password_to_clipboard)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyToClipboard(text: String) {
|
||||
activity?.apply {
|
||||
copyToClipboard(text)
|
||||
@@ -236,6 +308,12 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeBackOrFinish() {
|
||||
if (closeAutomatically) {
|
||||
backOrFinish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun backOrFinish() {
|
||||
val fm = fragmentManager
|
||||
if (fm != null && fm.backStackEntryCount > 0) {
|
||||
@@ -250,25 +328,28 @@ class DecodeFragment : Fragment() {
|
||||
if (content.isNotEmpty()) {
|
||||
if (action is WifiAction &&
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
|
||||
!hasLocationPermission(ac)
|
||||
!ac.hasLocationPermission { executeAction(content) }
|
||||
) {
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
action.execute(ac, content)
|
||||
maybeBackOrFinish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun askForFileNameAndSave(raw: ByteArray) {
|
||||
val ac = activity ?: return
|
||||
if (!hasWritePermission(ac)) {
|
||||
// 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"
|
||||
) {
|
||||
@@ -278,11 +359,6 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
parentJob.cancel()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SCAN = "scan"
|
||||
|
||||
@@ -296,7 +372,7 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
fun hexDump(bytes: ByteArray, charsPerLine: Int = 33): String {
|
||||
private fun hexDump(bytes: ByteArray, charsPerLine: Int = 33): String {
|
||||
if (charsPerLine < 4 || bytes.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
@@ -309,7 +385,7 @@ 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
|
||||
|
||||
@@ -2,17 +2,25 @@ package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
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.*
|
||||
|
||||
class EncodeFragment : Fragment() {
|
||||
private lateinit var formatView: Spinner
|
||||
private lateinit var errorCorrectionLabel: TextView
|
||||
private lateinit var errorCorrectionLevel: Spinner
|
||||
private lateinit var sizeView: TextView
|
||||
private lateinit var sizeBarView: SeekBar
|
||||
private lateinit var contentView: EditText
|
||||
@@ -31,11 +39,6 @@ class EncodeFragment : Fragment() {
|
||||
BarcodeFormat.UPC_A
|
||||
)
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
@@ -51,11 +54,38 @@ class EncodeFragment : Fragment() {
|
||||
)
|
||||
|
||||
formatView = view.findViewById(R.id.format)
|
||||
formatView.adapter = ArrayAdapter(
|
||||
val formatAdapter = ArrayAdapter(
|
||||
ac,
|
||||
android.R.layout.simple_list_item_1,
|
||||
writers.map { it.name }
|
||||
android.R.layout.simple_spinner_item,
|
||||
writers.map { prettifyFormatName(it.name) }
|
||||
)
|
||||
formatAdapter.setDropDownViewResource(
|
||||
android.R.layout.simple_spinner_dropdown_item
|
||||
)
|
||||
formatView.adapter = formatAdapter
|
||||
formatView.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(
|
||||
parentView: AdapterView<*>?,
|
||||
selectedItemView: View?,
|
||||
position: Int,
|
||||
id: Long
|
||||
) {
|
||||
val visibility = if (
|
||||
writers[position] == BarcodeFormat.QR_CODE
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
errorCorrectionLabel.visibility = visibility
|
||||
errorCorrectionLevel.visibility = visibility
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parentView: AdapterView<*>?) {}
|
||||
}
|
||||
|
||||
errorCorrectionLabel = view.findViewById(R.id.error_correction_label)
|
||||
errorCorrectionLevel = view.findViewById(R.id.error_correction_level)
|
||||
|
||||
sizeView = view.findViewById(R.id.size_display)
|
||||
sizeBarView = view.findViewById(R.id.size_bar)
|
||||
@@ -94,22 +124,17 @@ class EncodeFragment : Fragment() {
|
||||
prefs.indexOfLastSelectedFormat = formatView.selectedItemPosition
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.fragment_encode, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.next -> {
|
||||
encode()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun encode() {
|
||||
val hints = EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)
|
||||
val format = writers[formatView.selectedItemPosition]
|
||||
if (format == BarcodeFormat.QR_CODE) {
|
||||
hints[EncodeHintType.ERROR_CORRECTION] = arrayListOf(
|
||||
ErrorCorrectionLevel.L,
|
||||
ErrorCorrectionLevel.M,
|
||||
ErrorCorrectionLevel.Q,
|
||||
ErrorCorrectionLevel.H
|
||||
)[errorCorrectionLevel.selectedItemPosition]
|
||||
}
|
||||
val size = getSize(sizeBarView.progress)
|
||||
val content = contentView.text.toString()
|
||||
if (content.isEmpty()) {
|
||||
@@ -120,9 +145,8 @@ class EncodeFragment : Fragment() {
|
||||
).show()
|
||||
} else {
|
||||
activity?.hideSoftKeyboard(contentView)
|
||||
addFragment(
|
||||
fragmentManager,
|
||||
BarcodeFragment.newInstance(content, format, size)
|
||||
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.*
|
||||
|
||||
@@ -151,7 +155,7 @@ class HistoryFragment : Fragment() {
|
||||
}
|
||||
true
|
||||
}
|
||||
listView.setOnScrollListener(systemBarScrollListener)
|
||||
listView.setOnScrollListener(systemBarListViewScrollListener)
|
||||
|
||||
fab = view.findViewById(R.id.share)
|
||||
fab.setOnClickListener { v ->
|
||||
@@ -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,41 +358,46 @@ class HistoryFragment : Fragment() {
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun askToExportToFile(context: Context) = scope.launch {
|
||||
val ac = activity ?: return@launch
|
||||
progressView.useVisibility {
|
||||
if (!hasWritePermission(ac)) {
|
||||
return@useVisibility
|
||||
}
|
||||
val options = context.resources.getStringArray(
|
||||
R.array.export_options_values
|
||||
)
|
||||
val delimiter = alertDialog<String>(context) { resume ->
|
||||
setTitle(R.string.export_as)
|
||||
setItems(R.array.export_options_names) { _, which ->
|
||||
resume(options[which])
|
||||
private fun askToExportToFile() {
|
||||
scope.launch {
|
||||
val ac = activity ?: return@launch
|
||||
progressView.useVisibility {
|
||||
// Write permission is only required before Android Q.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
|
||||
!ac.hasWritePermission { askToExportToFile() }
|
||||
) {
|
||||
return@useVisibility
|
||||
}
|
||||
} ?: return@useVisibility
|
||||
val name = withContext(Dispatchers.Main) {
|
||||
ac.askForFileName(
|
||||
when (delimiter) {
|
||||
"db" -> ".db"
|
||||
"json" -> ".json"
|
||||
else -> ".csv"
|
||||
}
|
||||
val options = ac.resources.getStringArray(
|
||||
R.array.export_options_values
|
||||
)
|
||||
} ?: return@useVisibility
|
||||
val message = when (delimiter) {
|
||||
"db" -> exportDatabase(ac, name)
|
||||
else -> db.getScansDetailed(filter)?.use {
|
||||
when (delimiter) {
|
||||
"json" -> exportJson(context, name, it)
|
||||
else -> exportCsv(context, name, it, delimiter)
|
||||
val delimiter = alertDialog<String>(ac) { resume ->
|
||||
setTitle(R.string.export_as)
|
||||
setItems(R.array.export_options_names) { _, which ->
|
||||
resume(options[which])
|
||||
}
|
||||
} ?: false
|
||||
}.toSaveResult()
|
||||
withContext(Dispatchers.Main) {
|
||||
ac.toast(message)
|
||||
} ?: return@useVisibility
|
||||
val name = withContext(Dispatchers.Main) {
|
||||
ac.askForFileName(
|
||||
when (delimiter) {
|
||||
"db" -> ".db"
|
||||
"json" -> ".json"
|
||||
else -> ".csv"
|
||||
}
|
||||
)
|
||||
} ?: return@useVisibility
|
||||
val message = when (delimiter) {
|
||||
"db" -> ac.exportDatabase(name)
|
||||
else -> db.getScansDetailed(filter)?.use {
|
||||
when (delimiter) {
|
||||
"json" -> ac.exportJson(name, it)
|
||||
else -> ac.exportCsv(name, it, delimiter)
|
||||
}
|
||||
} ?: false
|
||||
}.toSaveResult()
|
||||
withContext(Dispatchers.Main) {
|
||||
ac.toast(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -414,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()
|
||||
}
|
||||
|
||||
+142
-102
@@ -1,114 +1,154 @@
|
||||
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.v4.app.Fragment
|
||||
import android.support.v7.widget.SwitchCompat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import 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.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 : Fragment() {
|
||||
private lateinit var showCropHandleSwitch: SwitchCompat
|
||||
private lateinit var zoomBySwipingSwitch: SwitchCompat
|
||||
private lateinit var autoResizeSwitch: SwitchCompat
|
||||
private lateinit var tryHarderSwitch: SwitchCompat
|
||||
private lateinit var vibrateSwitch: SwitchCompat
|
||||
private lateinit var useHistorySwitch: SwitchCompat
|
||||
private lateinit var ignoreConsecutiveDuplicatesSwitch: SwitchCompat
|
||||
private lateinit var openImmediatelySwitch: SwitchCompat
|
||||
private lateinit var showMetaDataSwitch: SwitchCompat
|
||||
private lateinit var showHexDumpSwitch: SwitchCompat
|
||||
private lateinit var openWithUrlInput: EditText
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View? {
|
||||
activity?.setTitle(R.string.preferences)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_preferences,
|
||||
container,
|
||||
false
|
||||
)
|
||||
|
||||
showCropHandleSwitch = view.findViewById(R.id.show_crop_handle)
|
||||
if (prefs.showCropHandle) {
|
||||
showCropHandleSwitch.toggle()
|
||||
class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
private val changeListener = object : OnSharedPreferenceChangeListener {
|
||||
override fun onSharedPreferenceChanged(
|
||||
sharedPreferences: SharedPreferences,
|
||||
key: String
|
||||
) {
|
||||
val preference = findPreference(key) ?: return
|
||||
prefs.update()
|
||||
if (preference.key == "custom_locale") {
|
||||
activity?.restartApp()
|
||||
} else {
|
||||
setSummary(preference)
|
||||
}
|
||||
}
|
||||
|
||||
zoomBySwipingSwitch = view.findViewById(R.id.zoom_by_swiping)
|
||||
if (prefs.zoomBySwiping) {
|
||||
zoomBySwipingSwitch.toggle()
|
||||
}
|
||||
|
||||
autoResizeSwitch = view.findViewById(R.id.auto_rotate)
|
||||
if (prefs.autoRotate) {
|
||||
autoResizeSwitch.toggle()
|
||||
}
|
||||
|
||||
tryHarderSwitch = view.findViewById(R.id.try_harder)
|
||||
if (prefs.tryHarder) {
|
||||
tryHarderSwitch.toggle()
|
||||
}
|
||||
|
||||
vibrateSwitch = view.findViewById(R.id.vibrate)
|
||||
if (prefs.vibrate) {
|
||||
vibrateSwitch.toggle()
|
||||
}
|
||||
|
||||
useHistorySwitch = view.findViewById(R.id.use_history)
|
||||
if (prefs.useHistory) {
|
||||
useHistorySwitch.toggle()
|
||||
}
|
||||
|
||||
ignoreConsecutiveDuplicatesSwitch = view.findViewById(
|
||||
R.id.ignore_consecutive_duplicates
|
||||
)
|
||||
if (prefs.ignoreConsecutiveDuplicates) {
|
||||
ignoreConsecutiveDuplicatesSwitch.toggle()
|
||||
}
|
||||
|
||||
openImmediatelySwitch = view.findViewById(R.id.open_immediately)
|
||||
if (prefs.openImmediately) {
|
||||
openImmediatelySwitch.toggle()
|
||||
}
|
||||
|
||||
showMetaDataSwitch = view.findViewById(R.id.show_meta_data)
|
||||
if (prefs.showMetaData) {
|
||||
showMetaDataSwitch.toggle()
|
||||
}
|
||||
|
||||
showHexDumpSwitch = view.findViewById(R.id.show_hex_dump)
|
||||
if (prefs.showHexDump) {
|
||||
showHexDumpSwitch.toggle()
|
||||
}
|
||||
|
||||
openWithUrlInput = view.findViewById(R.id.open_with_url)
|
||||
openWithUrlInput.setText(prefs.openWithUrl)
|
||||
|
||||
(view.findViewById(R.id.scroll_view) as View).setPaddingFromWindowInsets()
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
prefs.showCropHandle = showCropHandleSwitch.isChecked
|
||||
prefs.zoomBySwiping = zoomBySwipingSwitch.isChecked
|
||||
prefs.autoRotate = autoResizeSwitch.isChecked
|
||||
prefs.tryHarder = tryHarderSwitch.isChecked
|
||||
prefs.vibrate = vibrateSwitch.isChecked
|
||||
prefs.useHistory = useHistorySwitch.isChecked
|
||||
prefs.ignoreConsecutiveDuplicates = ignoreConsecutiveDuplicatesSwitch.isChecked
|
||||
prefs.openImmediately = openImmediatelySwitch.isChecked
|
||||
prefs.showMetaData = showMetaDataSwitch.isChecked
|
||||
prefs.showHexDump = showHexDumpSwitch.isChecked
|
||||
prefs.openWithUrl = openWithUrlInput.text.toString()
|
||||
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() {
|
||||
super.onResume()
|
||||
listView.setPaddingFromWindowInsets()
|
||||
listView.removeOnScrollListener(systemBarRecyclerViewScrollListener)
|
||||
listView.addOnScrollListener(systemBarRecyclerViewScrollListener)
|
||||
preferenceScreen.sharedPreferences
|
||||
.registerOnSharedPreferenceChangeListener(changeListener)
|
||||
setSummaries(preferenceScreen)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
preferenceScreen.sharedPreferences
|
||||
.unregisterOnSharedPreferenceChangeListener(changeListener)
|
||||
}
|
||||
|
||||
override fun onDisplayPreferenceDialog(preference: Preference) {
|
||||
if (preference is UrlPreference) {
|
||||
val fm = fragmentManager
|
||||
UrlDialogFragment.newInstance(preference.key).apply {
|
||||
setTargetFragment(this@PreferencesFragment, 0)
|
||||
show(fm, null)
|
||||
}
|
||||
} else {
|
||||
super.onDisplayPreferenceDialog(preference)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSummaries(screen: PreferenceGroup) {
|
||||
var i = screen.preferenceCount
|
||||
while (i-- > 0) {
|
||||
setSummary(screen.getPreference(i))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSummary(preference: Preference) {
|
||||
when (preference) {
|
||||
is UrlPreference -> {
|
||||
preference.setSummary(preference.getUrl())
|
||||
}
|
||||
is ListPreference -> {
|
||||
preference.setSummary(preference.entry)
|
||||
}
|
||||
is MultiSelectListPreference -> {
|
||||
preference.setSummary(
|
||||
preference.values.joinToString(", ") {
|
||||
it.replace(Regex("_"), " ")
|
||||
}
|
||||
)
|
||||
}
|
||||
is PreferenceGroup -> {
|
||||
setSummaries(preference)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Activity.restartApp() {
|
||||
val intent = Intent(this, SplashActivity::class.java)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
intent.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
)
|
||||
}
|
||||
startActivity(intent)
|
||||
finish()
|
||||
// Restart to begin with an unmodified Locale to follow system settings.
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.preference.PreferenceDialogFragmentCompat
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
import de.markusfisch.android.binaryeye.net.sendAsync
|
||||
import de.markusfisch.android.binaryeye.preference.UrlPreference
|
||||
|
||||
class UrlDialogFragment : PreferenceDialogFragmentCompat() {
|
||||
private var urlView: TextView? = null
|
||||
private var testButton: TextView? = null
|
||||
|
||||
override fun onBindDialogView(view: View?) {
|
||||
super.onBindDialogView(view)
|
||||
urlView = view?.findViewById(R.id.url)
|
||||
testButton = view?.findViewById(R.id.test_url)
|
||||
testButton?.setOnClickListener {
|
||||
testUrl(testButton)
|
||||
}
|
||||
urlView?.text = urlPreference().getUrl()
|
||||
urlView?.hint = getString(
|
||||
when (prefs.sendScanType) {
|
||||
"0" -> R.string.url_hint_add_content
|
||||
else -> R.string.url_hint
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
if (positiveResult) {
|
||||
urlPreference().setUrl(getUrl())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUrl() = completeUrl(urlView?.text.toString())
|
||||
|
||||
private fun urlPreference() = preference as UrlPreference
|
||||
|
||||
private fun testUrl(textView: TextView?) {
|
||||
val url = getUrl()
|
||||
if (url.isEmpty()) {
|
||||
return
|
||||
}
|
||||
textView ?: return
|
||||
textView.text = "…"
|
||||
Scan(
|
||||
"test",
|
||||
null,
|
||||
"none",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
).sendAsync(url, prefs.sendScanType) { code, body ->
|
||||
textView.text = when {
|
||||
code != null -> "$code"
|
||||
body != null -> body
|
||||
else -> getString(R.string.background_request_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(key: String): UrlDialogFragment {
|
||||
val args = Bundle()
|
||||
args.putString(ARG_KEY, key)
|
||||
val fragment = UrlDialogFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun completeUrl(template: String): String {
|
||||
var s = template.trim()
|
||||
if (s.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
if (!s.startsWith("http")) {
|
||||
s = "http://${s}"
|
||||
}
|
||||
if (prefs.sendScanType == "0" &&
|
||||
!s.matches(".*/[a-zA-Z._-]*\\?[a-zA-Z0-9&=_-]+=$".toRegex())
|
||||
) {
|
||||
s = "${s}?content="
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -7,11 +7,13 @@ import android.graphics.Matrix
|
||||
import android.graphics.RectF
|
||||
import android.net.Uri
|
||||
import java.io.IOException
|
||||
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(
|
||||
@@ -20,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) {
|
||||
@@ -47,16 +49,18 @@ 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())
|
||||
val y = max(0, (rect.top * h).roundToInt())
|
||||
Bitmap.createBitmap(
|
||||
erected,
|
||||
(rect.left * w).roundToInt(),
|
||||
(rect.top * h).roundToInt(),
|
||||
(rect.width() * w).roundToInt(),
|
||||
(rect.height() * h).roundToInt()
|
||||
x,
|
||||
y,
|
||||
min(w - x, (rect.right * w).roundToInt() - x),
|
||||
min(h - y, (rect.bottom * h).roundToInt() - y)
|
||||
)
|
||||
} catch (e: OutOfMemoryError) {
|
||||
null
|
||||
@@ -64,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
|
||||
}
|
||||
|
||||
+6
-7
@@ -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 {
|
||||
@@ -0,0 +1,124 @@
|
||||
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
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.ProtocolException
|
||||
import java.net.URL
|
||||
|
||||
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) {
|
||||
callback(response.code, response.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Scan.send(url: String, type: String): Response {
|
||||
return when (type) {
|
||||
"1" -> request(
|
||||
url + "?" + asUrlArguments()
|
||||
)
|
||||
"2" -> request(url) { con ->
|
||||
con.requestMethod = "POST"
|
||||
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
|
||||
con.outputStream.apply {
|
||||
write(asUrlArguments().toByteArray())
|
||||
close()
|
||||
}
|
||||
}
|
||||
"3" -> request(url) { con ->
|
||||
con.requestMethod = "POST"
|
||||
con.setRequestProperty("Content-Type", "application/json")
|
||||
con.outputStream.apply {
|
||||
write(asJson().toString().toByteArray())
|
||||
close()
|
||||
}
|
||||
}
|
||||
else -> request(
|
||||
url + content.urlEncode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Scan.asUrlArguments(): String = getMap().map { (k, v) ->
|
||||
"${k}=${v.urlEncode()}"
|
||||
}.joinToString("&")
|
||||
|
||||
private fun Scan.asJson() = JSONObject().apply {
|
||||
getMap().forEach { (k, v) -> put(k, v) }
|
||||
}
|
||||
|
||||
private fun Scan.getMap(): Map<String, String> = mapOf(
|
||||
"content" to content,
|
||||
"raw" to raw?.toHexString(),
|
||||
"format" to format,
|
||||
"errorCorrectionLevel" to errorCorrectionLevel,
|
||||
"issueNumber" to issueNumber,
|
||||
"orientation" to orientation,
|
||||
"otherMetaData" to otherMetaData,
|
||||
"pdf417ExtraMetaData" to pdf417ExtraMetaData,
|
||||
"possibleCountry" to possibleCountry,
|
||||
"suggestedPrice" to suggestedPrice,
|
||||
"upcEanExtension" to upcEanExtension,
|
||||
"timestamp" to dateTime
|
||||
).filterNullValues()
|
||||
|
||||
private fun Map<String, String?>.filterNullValues() =
|
||||
filterValues { it != null }.mapValues { it.value as String }
|
||||
|
||||
private fun request(
|
||||
url: String,
|
||||
writer: ((HttpURLConnection) -> Any)? = null
|
||||
): Response {
|
||||
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)
|
||||
} catch (e: ProtocolException) {
|
||||
Response(null, e.message)
|
||||
} catch (e: IOException) {
|
||||
try {
|
||||
val body = con?.errorStream?.readHead() ?: e.message
|
||||
Response(con?.responseCode, body)
|
||||
} catch (e: IOException) {
|
||||
Response(null, e.message)
|
||||
}
|
||||
} finally {
|
||||
con?.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun InputStream.readHead(): String {
|
||||
val sb = StringBuilder()
|
||||
val br = BufferedReader(InputStreamReader(this))
|
||||
var line = br.readLine()
|
||||
var i = 0
|
||||
while (line != null && sb.length < 240) {
|
||||
sb.append(line)
|
||||
line = br.readLine()
|
||||
++i
|
||||
}
|
||||
br.close()
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private data class Response(val code: Int?, val body: String?)
|
||||
@@ -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)
|
||||
@@ -61,6 +108,11 @@ class Preferences {
|
||||
apply(IGNORE_CONSECUTIVE_DUPLICATES, value)
|
||||
field = value
|
||||
}
|
||||
var copyImmediately = false
|
||||
set(value) {
|
||||
apply(COPY_IMMEDIATELY, value)
|
||||
field = value
|
||||
}
|
||||
var openImmediately = false
|
||||
set(value) {
|
||||
apply(OPEN_IMMEDIATELY, value)
|
||||
@@ -76,20 +128,50 @@ class Preferences {
|
||||
apply(SHOW_HEX_DUMP, value)
|
||||
field = value
|
||||
}
|
||||
var closeAutomatically = false
|
||||
set(value) {
|
||||
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)
|
||||
field = value
|
||||
}
|
||||
var sendScanType: String = "0"
|
||||
set(value) {
|
||||
apply(SEND_SCAN_TYPE, value)
|
||||
field = value
|
||||
}
|
||||
var customLocale: String = ""
|
||||
set(value) {
|
||||
apply(CUSTOM_LOCALE, value)
|
||||
field = value
|
||||
}
|
||||
var indexOfLastSelectedFormat: Int = 0
|
||||
set(value) {
|
||||
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
|
||||
}
|
||||
@@ -100,31 +182,72 @@ 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(
|
||||
IGNORE_CONSECUTIVE_DUPLICATES,
|
||||
ignoreConsecutiveDuplicates
|
||||
)
|
||||
copyImmediately = preferences.getBoolean(
|
||||
COPY_IMMEDIATELY,
|
||||
copyImmediately
|
||||
)
|
||||
openImmediately = preferences.getBoolean(
|
||||
OPEN_IMMEDIATELY,
|
||||
openImmediately
|
||||
)
|
||||
showMetaData = preferences.getBoolean(SHOW_META_DATA, showMetaData)
|
||||
showHexDump = preferences.getBoolean(SHOW_HEX_DUMP, showHexDump)
|
||||
closeAutomatically = preferences.getBoolean(
|
||||
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
|
||||
}
|
||||
preferences.getString(SEND_SCAN_TYPE, sendScanType)?.also {
|
||||
sendScanType = it
|
||||
}
|
||||
preferences.getString(CUSTOM_LOCALE, customLocale)?.also {
|
||||
customLocale = it
|
||||
}
|
||||
indexOfLastSelectedFormat = preferences.getInt(
|
||||
INDEX_OF_LAST_SELECTED_FORMAT,
|
||||
indexOfLastSelectedFormat
|
||||
@@ -135,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()
|
||||
}
|
||||
@@ -151,22 +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 SHOW_HEX_DUMP = "show_hex_dump"
|
||||
const val SHOW_META_DATA = "show_meta_data"
|
||||
const val OPEN_WITH_URL = "open_with_url"
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.markusfisch.android.binaryeye.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.preference.DialogPreference
|
||||
import android.util.AttributeSet
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
class UrlPreference(
|
||||
context: Context?,
|
||||
attrs: AttributeSet?
|
||||
) : DialogPreference(context, attrs) {
|
||||
private var url: String? = null
|
||||
|
||||
init {
|
||||
dialogLayoutResource = R.layout.dialog_url
|
||||
}
|
||||
|
||||
fun getUrl() = url
|
||||
|
||||
fun setUrl(url: String) {
|
||||
this.url = url
|
||||
persistString(url)
|
||||
}
|
||||
|
||||
override fun onSetInitialValue(
|
||||
restorePersistedValue: Boolean,
|
||||
defaultValue: Any?
|
||||
) {
|
||||
setUrl(
|
||||
if (restorePersistedValue) {
|
||||
getPersistedString(url)
|
||||
} else {
|
||||
defaultValue as String
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package de.markusfisch.android.binaryeye.rs
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.support.v8.renderscript.Allocation
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
|
||||
fun fixTransparency(rs: RenderScript, bitmap: Bitmap?): Bitmap? {
|
||||
bitmap ?: return null
|
||||
val result = analyze(rs, bitmap)
|
||||
return if (result.hasTransparentPixels) {
|
||||
val copy = Bitmap.createBitmap(
|
||||
bitmap.width,
|
||||
bitmap.height,
|
||||
bitmap.config
|
||||
)
|
||||
Canvas(copy).apply {
|
||||
drawColor(result.backgroundColor)
|
||||
drawBitmap(bitmap, 0f, 0f, null)
|
||||
}
|
||||
copy
|
||||
} else {
|
||||
bitmap
|
||||
}
|
||||
}
|
||||
|
||||
private fun analyze(rs: RenderScript, bitmap: Bitmap): Result {
|
||||
val analyzeScript = ScriptC_analyze(rs)
|
||||
val bitmapAllocation = Allocation.createFromBitmap(rs, bitmap)
|
||||
val result = analyzeScript.reduce_analyze(bitmapAllocation).get()
|
||||
bitmapAllocation.destroy()
|
||||
analyzeScript.destroy()
|
||||
return Result(
|
||||
result.x > 0,
|
||||
if (result.y > 0) 0xff000000.toInt() else 0xffffffff.toInt()
|
||||
)
|
||||
}
|
||||
|
||||
private data class Result(
|
||||
val hasTransparentPixels: Boolean,
|
||||
val backgroundColor: Int
|
||||
)
|
||||
@@ -1,6 +1,5 @@
|
||||
package de.markusfisch.android.binaryeye.rs
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.support.v8.renderscript.*
|
||||
import kotlin.math.max
|
||||
@@ -9,7 +8,7 @@ import kotlin.math.roundToInt
|
||||
private const val SCALE_FACTOR = .75f
|
||||
|
||||
class Preprocessor(
|
||||
context: Context,
|
||||
rs: RenderScript,
|
||||
width: Int,
|
||||
height: Int,
|
||||
private val roi: Rect?
|
||||
@@ -17,7 +16,6 @@ class Preprocessor(
|
||||
var outWidth = 0
|
||||
var outHeight = 0
|
||||
|
||||
private val rs = RenderScript.create(context)
|
||||
private val resizeScript = ScriptIntrinsicResize.create(rs)
|
||||
private val rotateScript = ScriptC_rotate(rs)
|
||||
|
||||
@@ -62,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 {
|
||||
@@ -118,7 +116,6 @@ class Preprocessor(
|
||||
rotatedAlloc = null
|
||||
resizeScript.destroy()
|
||||
rotateScript.destroy()
|
||||
rs.destroy()
|
||||
}
|
||||
|
||||
fun resizeOnly(frame: ByteArray) {
|
||||
@@ -133,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)
|
||||
@@ -147,7 +144,7 @@ class Preprocessor(
|
||||
0, 0,
|
||||
roi.width(), roi.height(),
|
||||
yuvAlloc,
|
||||
roi.left, roi.top
|
||||
max(0, roi.left), max(0, roi.top)
|
||||
)
|
||||
roiAlloc
|
||||
} else {
|
||||
|
||||
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
package de.markusfisch.android.binaryeye.view
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
+30
-16
@@ -1,4 +1,4 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
package de.markusfisch.android.binaryeye.view
|
||||
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
@@ -6,35 +6,44 @@ import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Build
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.AbsListView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
val systemBarScrollListener = object : AbsListView.OnScrollListener {
|
||||
val systemBarListViewScrollListener = object : AbsListView.OnScrollListener {
|
||||
override fun onScroll(
|
||||
view: AbsListView,
|
||||
firstVisibleItem: Int,
|
||||
visibleItemCount: Int,
|
||||
totalItemCount: Int
|
||||
) {
|
||||
// give Android some time to settle down before running this,
|
||||
// not putting it on the queue makes it only work sometimes
|
||||
// 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))
|
||||
val scrollable = if (scrolled) {
|
||||
true
|
||||
} else {
|
||||
totalItemCount > 0 && lastChildOutOfView(view)
|
||||
}
|
||||
val scrollable = scrolled || totalItemCount > 0 && lastChildOutOfView(view)
|
||||
colorSystemAndToolBars(view.context, scrolled, scrollable)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onScrollStateChanged(
|
||||
view: AbsListView,
|
||||
scrollState: Int
|
||||
) {
|
||||
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
val systemBarRecyclerViewScrollListener = object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
||||
val scrolled = layoutManager.findFirstCompletelyVisibleItemPosition() != 0
|
||||
val scrollable = scrolled || layoutManager.findLastVisibleItemPosition() <
|
||||
recyclerView.adapter.itemCount - 1
|
||||
colorSystemAndToolBars(recyclerView.context, scrolled, scrollable)
|
||||
}
|
||||
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +59,12 @@ private fun lastChildOutOfView(listView: AbsListView): Boolean {
|
||||
|
||||
fun initSystemBars(activity: AppCompatActivity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
activity.window.decorView.systemUiVisibility =
|
||||
// Keeps the soft keyboard from repositioning the layout.
|
||||
val window = activity.window
|
||||
window.setSoftInputMode(
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
)
|
||||
window.decorView.systemUiVisibility =
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
@@ -95,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()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -2,53 +2,18 @@ package de.markusfisch.android.binaryeye.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.util.AttributeSet
|
||||
import de.markusfisch.android.binaryeye.graphics.Dots
|
||||
import de.markusfisch.android.binaryeye.graphics.getDashedBorderPaint
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class CropImageView(context: Context, attr: AttributeSet) :
|
||||
ConfinedScalingImageView(context, attr) {
|
||||
val windowInsets = Rect()
|
||||
|
||||
var onScan: (() -> Unit)? = null
|
||||
|
||||
private val dots = Dots(context)
|
||||
private val boundsPaint = context.getDashedBorderPaint()
|
||||
private val lastMappedRect = RectF()
|
||||
private val padding: Int = (24f * context.resources.displayMetrics.density).roundToInt()
|
||||
private val onScanRunnable = Runnable { onScan?.invoke() }
|
||||
|
||||
private var resultPoints: List<Point>? = null
|
||||
|
||||
init {
|
||||
scaleType = ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
fun updateResultPoints(points: List<Point>) {
|
||||
resultPoints = points
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onLayout(
|
||||
changed: Boolean,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int
|
||||
) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
setBoundsWithPadding(
|
||||
left + windowInsets.left,
|
||||
top + windowInsets.top,
|
||||
right - windowInsets.right,
|
||||
bottom - windowInsets.bottom
|
||||
)
|
||||
}
|
||||
|
||||
fun getBoundsRect() = Rect(
|
||||
bounds.left.roundToInt(),
|
||||
bounds.top.roundToInt(),
|
||||
@@ -56,40 +21,12 @@ class CropImageView(context: Context, attr: AttributeSet) :
|
||||
bounds.bottom.roundToInt()
|
||||
)
|
||||
|
||||
private fun setBoundsWithPadding(
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int
|
||||
) {
|
||||
val width = right - left
|
||||
val height = bottom - top
|
||||
val size = if (width < height)
|
||||
width - padding * 2
|
||||
else
|
||||
height - padding * 2
|
||||
val hpad = (width - size) / 2
|
||||
val vpad = (height - size) / 2
|
||||
setBounds(
|
||||
left.toFloat() + hpad,
|
||||
top.toFloat() + vpad,
|
||||
right.toFloat() - hpad,
|
||||
bottom.toFloat() - vpad
|
||||
)
|
||||
center(bounds)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
canvas.drawRect(bounds, boundsPaint)
|
||||
resultPoints?.let {
|
||||
dots.draw(canvas, it)
|
||||
}
|
||||
resultPoints = null
|
||||
val mr = mappedRect ?: return
|
||||
if (mr != lastMappedRect) {
|
||||
removeCallbacks(onScanRunnable)
|
||||
postDelayed(onScanRunnable, 500)
|
||||
postDelayed(onScanRunnable, 300)
|
||||
lastMappedRect.set(mr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,19 +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 orientation = resources.configuration.orientation
|
||||
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
|
||||
)
|
||||
@@ -48,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
|
||||
@@ -58,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
|
||||
@@ -73,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 {
|
||||
@@ -84,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)
|
||||
@@ -144,11 +164,12 @@ class DetectorView : View {
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (handleGrabbed) {
|
||||
handlePos.set(x, y)
|
||||
if (distSq(handlePos, touchDown) > minMoveThresholdSq) {
|
||||
handleActive = true
|
||||
handleActive =
|
||||
handleActive or (distSq(handlePos, touchDown) > minMoveThresholdSq)
|
||||
if (handleActive) {
|
||||
updateClipRect()
|
||||
invalidate()
|
||||
}
|
||||
updateClipRect()
|
||||
invalidate()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -164,17 +185,14 @@ class DetectorView : View {
|
||||
MotionEvent.ACTION_UP -> {
|
||||
if (handleGrabbed) {
|
||||
if (!handleActive) {
|
||||
val mn = min(center.x, center.y) * .8f
|
||||
handlePos.set(
|
||||
(center.x + mn).roundToInt(),
|
||||
(center.y + mn).roundToInt()
|
||||
)
|
||||
handleActive = true
|
||||
invalidate()
|
||||
setHandleToDefaultRoi()
|
||||
} else {
|
||||
snap(x, y)
|
||||
}
|
||||
updateClipRect()
|
||||
if (handleActive) {
|
||||
updateClipRect()
|
||||
invalidate()
|
||||
}
|
||||
onRoiChanged?.invoke()
|
||||
handleGrabbed = false
|
||||
}
|
||||
@@ -184,9 +202,26 @@ class DetectorView : View {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setHandleToDefaultRoi() {
|
||||
val mn = min(center.x, center.y) * .8f
|
||||
handlePos.set(
|
||||
(center.x + mn).roundToInt(),
|
||||
(center.y + mn).roundToInt()
|
||||
)
|
||||
handleActive = true
|
||||
}
|
||||
|
||||
private fun snap(x: Int, y: Int) {
|
||||
if (abs(x - center.x) < distToFull ||
|
||||
abs(y - center.y) < distToFull
|
||||
val cx = clampX(x)
|
||||
val cy = clampY(y)
|
||||
val dx = abs(cx - center.x)
|
||||
val dy = abs(cy - center.y)
|
||||
// check if handle is close to the vertical or horizontal center line
|
||||
if (dx < distToFull ||
|
||||
dy < distToFull ||
|
||||
// check if handle is close to a screen corner
|
||||
((abs(cy - minY) < distToFull || abs(maxY - cy) < distToFull) &&
|
||||
abs(dx - center.x) < distToFull)
|
||||
) {
|
||||
reset()
|
||||
invalidate()
|
||||
@@ -201,9 +236,6 @@ class DetectorView : View {
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
if (!changed) {
|
||||
return
|
||||
}
|
||||
val width = right - left
|
||||
val height = bottom - top
|
||||
center.set(
|
||||
@@ -216,6 +248,9 @@ class DetectorView : View {
|
||||
width - handleXRadius - paddingRight - padding,
|
||||
height - handleYRadius - paddingBottom - fabHeight
|
||||
)
|
||||
if (handlePos.x == -2) {
|
||||
setHandleToDefaultRoi()
|
||||
}
|
||||
if (handleActive) {
|
||||
updateClipRect()
|
||||
} else {
|
||||
@@ -224,13 +259,10 @@ class DetectorView : View {
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
canvas.drawColor(0, PorterDuff.Mode.CLEAR)
|
||||
if (handleActive) {
|
||||
drawClip(canvas)
|
||||
}
|
||||
marks?.let {
|
||||
dots.draw(canvas, it)
|
||||
}
|
||||
drawDots(canvas)
|
||||
if (prefs.showCropHandle) {
|
||||
canvas.drawBitmap(
|
||||
handleBitmap,
|
||||
@@ -241,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()
|
||||
@@ -259,7 +303,7 @@ class DetectorView : View {
|
||||
radius
|
||||
)
|
||||
)
|
||||
canvas.drawColor(shadeColor, PorterDuff.Mode.SRC)
|
||||
canvas.drawColor(shadeColor)
|
||||
canvas.restore()
|
||||
} else {
|
||||
canvas.drawRect(roi, roiPaint)
|
||||
@@ -284,10 +328,14 @@ class DetectorView : View {
|
||||
}
|
||||
|
||||
private fun clampHandlePos() {
|
||||
handlePos.x = min(center.x * 2, max(0, handlePos.x))
|
||||
handlePos.y = min(maxY, max(minY, handlePos.y))
|
||||
handlePos.x = clampX(handlePos.x)
|
||||
handlePos.y = clampY(handlePos.y)
|
||||
}
|
||||
|
||||
private fun clampX(x: Int) = min(center.x * 2, max(0, x))
|
||||
|
||||
private fun clampY(y: Int) = min(maxY, max(minY, y))
|
||||
|
||||
internal class SavedState : BaseSavedState {
|
||||
val savedHandlePos = Point()
|
||||
|
||||
|
||||
@@ -4,13 +4,19 @@ import android.content.Context
|
||||
import android.widget.Toast
|
||||
|
||||
fun Context.toast(message: Int) = Toast.makeText(
|
||||
this,
|
||||
applicationContext,
|
||||
message,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
fun Context.toast(message: String) = Toast.makeText(
|
||||
this,
|
||||
message,
|
||||
applicationContext,
|
||||
message.ellipsize(128),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
private fun String.ellipsize(max: Int) = if (length < max) {
|
||||
this
|
||||
} else {
|
||||
"${take(max)}…"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.markusfisch.android.binaryeye.zxing
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.google.zxing.*
|
||||
import com.google.zxing.common.BitMatrix
|
||||
import com.google.zxing.common.HybridBinarizer
|
||||
import java.util.*
|
||||
|
||||
@@ -10,44 +11,30 @@ class Zxing(possibleResultPoint: ResultPointCallback? = null) {
|
||||
private val multiFormatReader = MultiFormatReader()
|
||||
|
||||
init {
|
||||
val decodeFormats = EnumSet.noneOf<BarcodeFormat>(
|
||||
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(
|
||||
@@ -55,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,
|
||||
@@ -65,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 {
|
||||
@@ -98,100 +80,122 @@ class Zxing(possibleResultPoint: ResultPointCallback? = null) {
|
||||
private fun decodeLuminanceSource(
|
||||
source: LuminanceSource,
|
||||
invert: Boolean
|
||||
): Result? {
|
||||
return decodeLuminanceSource(
|
||||
if (invert) {
|
||||
source.invert()
|
||||
} else {
|
||||
source
|
||||
}
|
||||
): Result? = decodeLuminanceSource(
|
||||
if (invert) {
|
||||
source.invert()
|
||||
} else {
|
||||
source
|
||||
}
|
||||
)
|
||||
|
||||
private fun decodeLuminanceSource(source: LuminanceSource): Result? = try {
|
||||
multiFormatReader.decode(
|
||||
BinaryBitmap(HybridBinarizer(source)),
|
||||
hints
|
||||
)
|
||||
}
|
||||
|
||||
private fun decodeLuminanceSource(source: LuminanceSource): Result? {
|
||||
val bitmap = BinaryBitmap(HybridBinarizer(source))
|
||||
return try {
|
||||
multiFormatReader.decode(bitmap, hints)
|
||||
} catch (e: ReaderException) {
|
||||
null
|
||||
} finally {
|
||||
multiFormatReader.reset()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BLACK = 0xff000000.toInt()
|
||||
private const val WHITE = 0xffffffff.toInt()
|
||||
|
||||
fun encodeAsBitmap(
|
||||
text: String,
|
||||
format: BarcodeFormat,
|
||||
width: Int,
|
||||
height: Int
|
||||
): Bitmap? {
|
||||
val hints = EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)
|
||||
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||
val result = MultiFormatWriter().encode(
|
||||
text,
|
||||
format,
|
||||
width,
|
||||
height,
|
||||
hints
|
||||
)
|
||||
val w = result.width
|
||||
val h = result.height
|
||||
val pixels = IntArray(w * h)
|
||||
var offset = 0
|
||||
for (y in 0 until h) {
|
||||
for (x in 0 until w) {
|
||||
pixels[offset + x] = if (result.get(x, y)) {
|
||||
BLACK
|
||||
} else {
|
||||
WHITE
|
||||
}
|
||||
}
|
||||
offset += w
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||
bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
fun encodeAsSvg(
|
||||
text: String,
|
||||
format: BarcodeFormat,
|
||||
width: Int,
|
||||
height: Int
|
||||
): String {
|
||||
val hints = EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)
|
||||
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||
val result = MultiFormatWriter().encode(
|
||||
text,
|
||||
format,
|
||||
0,
|
||||
0,
|
||||
hints
|
||||
)
|
||||
val sb = StringBuilder()
|
||||
sb.append("<svg width=\"$width\" height=\"$height\"")
|
||||
sb.append(" viewBox=\"0 0 $width $height\"")
|
||||
sb.append(" xmlns=\"http://www.w3.org/2000/svg\">\n")
|
||||
val w = result.width
|
||||
val h = result.height
|
||||
val xf = width.toFloat() / w
|
||||
val yf = height.toFloat() / h
|
||||
for (y in 0 until h) {
|
||||
for (x in 0 until w) {
|
||||
if (result.get(x, y)) {
|
||||
val ox = x * xf
|
||||
val oy = y * yf
|
||||
sb.append("<rect x=\"$ox\" y=\"$oy\"")
|
||||
sb.append(" width=\"$xf\" height=\"$yf\"/>\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.append("</svg>\n")
|
||||
return sb.toString()
|
||||
}
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
|
||||
private const val BLACK = 0xff000000.toInt()
|
||||
private const val WHITE = 0xffffffff.toInt()
|
||||
|
||||
fun encodeAsBitmap(
|
||||
text: String,
|
||||
format: BarcodeFormat,
|
||||
width: Int,
|
||||
height: Int,
|
||||
hints: EnumMap<EncodeHintType, Any>? = null
|
||||
): Bitmap? {
|
||||
val bitMatrix = encode(text, format, hints, width, height)
|
||||
val w = bitMatrix.width
|
||||
val h = bitMatrix.height
|
||||
val pixels = IntArray(w * h)
|
||||
var offset = 0
|
||||
for (y in 0 until h) {
|
||||
for (x in 0 until w) {
|
||||
pixels[offset + x] = if (bitMatrix.get(x, y)) {
|
||||
BLACK
|
||||
} else {
|
||||
WHITE
|
||||
}
|
||||
}
|
||||
offset += w
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||
bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
fun encodeAsSvg(
|
||||
text: String,
|
||||
format: BarcodeFormat,
|
||||
hints: EnumMap<EncodeHintType, Any>? = null
|
||||
): String {
|
||||
val bitMatrix = encode(text, format, hints)
|
||||
val sb = StringBuilder()
|
||||
val w = bitMatrix.width
|
||||
var h = bitMatrix.height
|
||||
val moduleHeight = if (h == 1) w / 2 else 1
|
||||
for (y in 0 until h) {
|
||||
for (x in 0 until w) {
|
||||
if (bitMatrix.get(x, y)) {
|
||||
sb.append(" M${x},${y}h1v${moduleHeight}h-1z")
|
||||
}
|
||||
}
|
||||
}
|
||||
h *= moduleHeight
|
||||
return """<svg width="$w" height="$h"
|
||||
viewBox="0 0 $w $h"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="$sb"/>
|
||||
</svg>
|
||||
"""
|
||||
}
|
||||
|
||||
fun encodeAsText(
|
||||
text: String,
|
||||
format: BarcodeFormat,
|
||||
hints: EnumMap<EncodeHintType, Any>? = null
|
||||
): String {
|
||||
val bitMatrix = encode(text, format, hints)
|
||||
val w = bitMatrix.width
|
||||
val h = bitMatrix.height
|
||||
val sb = StringBuilder()
|
||||
for (y in 0 until h) {
|
||||
for (x in 0 until w) {
|
||||
sb.append(if (bitMatrix.get(x, y)) "█" else " ")
|
||||
}
|
||||
sb.append("\n")
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun encode(
|
||||
text: String,
|
||||
format: BarcodeFormat,
|
||||
encodeHints: EnumMap<EncodeHintType, Any>? = null,
|
||||
width: Int = 0,
|
||||
height: Int = 0
|
||||
): BitMatrix {
|
||||
val hints = encodeHints ?: EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)
|
||||
if (!hints.contains(EncodeHintType.CHARACTER_SET)) {
|
||||
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||
}
|
||||
return MultiFormatWriter().encode(
|
||||
text,
|
||||
format,
|
||||
width,
|
||||
height,
|
||||
hints
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/accent"/>
|
||||
<foreground android:drawable="@drawable/ic_shortcut_decode_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +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="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>
|
||||
@@ -0,0 +1,4 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/accent"/>
|
||||
<foreground android:drawable="@drawable/ic_shortcut_encode_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +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.25h-13.5v13.5h-4.5v-13.5h-13.5v-4.5h13.5v-13.5h4.5v13.5h13.5z" android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/accent"/>
|
||||
<foreground android:drawable="@drawable/ic_shortcut_preferences_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +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.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>
|
||||
@@ -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="#FFFFFF" android:pathData="M12.65 10C11.83 7.67 9.61 6 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6c2.61 0 4.83-1.67 5.65-4H17v4h4v-4h2v-4H12.65zM7 14c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,15 @@
|
||||
<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,11h8V3H3V11zM5,5h4v4H5V5z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M3,21h8v-8H3V21zM5,15h4v4H5V15z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M13,3v8h8V3H13zM19,9h-4V5h4V9z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,19h2v2h-2z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M13,13h2v2h-2z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M15,15h2v2h-2z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M13,17h2v2h-2z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M15,19h2v2h-2z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M17,17h2v2h-2z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M17,13h2v2h-2z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,15h2v2h-2z"/>
|
||||
</vector>
|
||||
@@ -1,3 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFFFF" android:pathData="M4.413 15.067h-0.88v1.93h-1.37v-5.69h2.27c0.433 0 0.817 0.08 1.15 0.24 0.34 0.16 0.6 0.39 0.78 0.69 0.187 0.293 0.28 0.627 0.28 1 0 0.553-0.2 0.997-0.6 1.33-0.4 0.333-0.943 0.5-1.63 0.5zm-0.88-2.7v1.64h0.9c0.267 0 0.47-0.067 0.61-0.2s0.21-0.32 0.21-0.56c0-0.267-0.073-0.48-0.22-0.64-0.14-0.16-0.337-0.24-0.59-0.24h-0.91zm8.587-1.06v5.69h-1.36l-2.02-3.54v3.54H7.37v-5.69h1.37l2.01 3.54v-3.54h1.37zm5.527 2.65v2.34c-0.213 0.233-0.523 0.42-0.93 0.56-0.407 0.147-0.853 0.22-1.34 0.22-0.74 0-1.333-0.23-1.78-0.69-0.447-0.453-0.687-1.087-0.72-1.9v-0.49c0-0.56 0.1-1.05 0.3-1.47 0.193-0.413 0.477-0.733 0.85-0.96 0.367-0.227 0.793-0.34 1.28-0.34 0.707 0 1.257 0.163 1.65 0.49 0.387 0.32 0.613 0.803 0.68 1.45h-1.32c-0.047-0.32-0.147-0.547-0.3-0.68-0.16-0.133-0.383-0.2-0.67-0.2-0.34 0-0.603 0.143-0.79 0.43-0.193 0.293-0.29 0.71-0.29 1.25v0.35c0 0.567 0.097 0.993 0.29 1.28 0.193 0.28 0.5 0.42 0.92 0.42 0.353 0 0.617-0.077 0.79-0.23v-0.89h-0.95v-0.94h2.33zm4.733-6.991L18.481 6.96l1.463 1.464-2.065 2.066 0.975 0.975L20.921 9.4l1.463 1.464-0.005-3.898z"/>
|
||||
</vector>
|
||||
@@ -1,3 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFFFF" android:pathData="M5.45 15.487c0-0.2-0.07-0.357-0.21-0.47-0.14-0.113-0.39-0.23-0.75-0.35-0.353-0.12-0.643-0.237-0.87-0.35-0.74-0.36-1.11-0.857-1.11-1.49 0-0.313 0.093-0.59 0.28-0.83 0.18-0.24 0.44-0.427 0.78-0.56 0.333-0.14 0.71-0.21 1.13-0.21 0.407 0 0.773 0.073 1.1 0.22 0.327 0.147 0.577 0.357 0.75 0.63 0.18 0.267 0.27 0.573 0.27 0.92H5.46c0-0.233-0.073-0.413-0.22-0.54-0.14-0.127-0.33-0.19-0.57-0.19-0.247 0-0.44 0.053-0.58 0.16-0.14 0.107-0.21 0.243-0.21 0.41 0 0.147 0.077 0.28 0.23 0.4 0.153 0.12 0.427 0.243 0.82 0.37 0.393 0.127 0.717 0.263 0.97 0.41 0.613 0.353 0.92 0.84 0.92 1.46 0 0.5-0.187 0.89-0.56 1.17-0.373 0.287-0.887 0.43-1.54 0.43-0.46 0-0.877-0.083-1.25-0.25-0.373-0.167-0.657-0.393-0.85-0.68-0.187-0.287-0.28-0.617-0.28-0.99h1.38c0 0.3 0.077 0.523 0.23 0.67 0.16 0.147 0.417 0.22 0.77 0.22 0.227 0 0.407-0.05 0.54-0.15 0.127-0.1 0.19-0.237 0.19-0.41zm3.158-4.18l1.13 4.14 1.13-4.14h1.53l-1.91 5.69h-1.5l-1.9-5.69h1.52zm8.814 2.65v2.34c-0.213 0.233-0.523 0.42-0.93 0.56-0.407 0.147-0.853 0.22-1.34 0.22-0.74 0-1.333-0.23-1.78-0.69-0.447-0.453-0.687-1.087-0.72-1.9v-0.49c0-0.56 0.1-1.05 0.3-1.47 0.193-0.413 0.477-0.733 0.85-0.96 0.367-0.227 0.793-0.34 1.28-0.34 0.707 0 1.257 0.163 1.65 0.49 0.387 0.32 0.613 0.803 0.68 1.45h-1.32c-0.047-0.32-0.147-0.547-0.3-0.68-0.16-0.133-0.383-0.2-0.67-0.2-0.34 0-0.603 0.143-0.79 0.43-0.193 0.293-0.29 0.71-0.29 1.25v0.35c0 0.567 0.097 0.993 0.29 1.28 0.193 0.28 0.5 0.42 0.92 0.42 0.353 0 0.617-0.077 0.79-0.23v-0.89h-0.95v-0.94h2.33zm4.164-7.051L17.69 6.9l1.463 1.464-2.065 2.066 0.975 0.975 2.065-2.066 1.464 1.464-0.005-3.898z"/>
|
||||
</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,3 +0,0 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#b6d46f"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,34 @@
|
||||
<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="M45,45m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M45.263,45.264m-13.262,0a13.262,13.262 0,1 1,26.524 0a13.262,13.262 0,1 1,-26.524 0"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M44.885,45.704m-7.255,0a7.255,7.255 0,1 1,14.51 0a7.255,7.255 0,1 1,-14.51 0"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="M49.785,40.407m-4.768,0a4.768,4.768 0,1 1,9.536 0a4.768,4.768 0,1 1,-9.536 0"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="10"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"/>
|
||||
</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"
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/view_barcode"/>
|
||||
<de.markusfisch.android.binaryeye.widget.DetectorView
|
||||
android:id="@+id/detector_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -13,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>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<EditText
|
||||
tools:targetApi="o"
|
||||
android:id="@+id/url"
|
||||
android:inputType="textUri"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/url_hint"
|
||||
android:importantForAutofill="no"/>
|
||||
<Button
|
||||
android:id="@+id/test_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/test_url"/>
|
||||
</LinearLayout>
|
||||
@@ -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"
|
||||
|
||||
@@ -23,6 +23,18 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<TextView
|
||||
android:id="@+id/error_correction_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/error_correction_level"/>
|
||||
<Spinner
|
||||
android:id="@+id/error_correction_level"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:entries="@array/error_correction_levels"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
<de.markusfisch.android.binaryeye.widget.ConfinedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/scroll_view"
|
||||
android:clipToPadding="false"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
android:orientation="vertical">
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/show_crop_handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/show_crop_handle"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/zoom_by_swiping"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/zoom_by_swiping"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/auto_rotate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/auto_rotate"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/try_harder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/try_harder"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/vibrate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/vibrate"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/use_history"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/use_history"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/ignore_consecutive_duplicates"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/ignore_consecutive_duplicates"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/open_immediately"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/open_immediately"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/show_meta_data"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/show_meta_data"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/show_hex_dump"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/show_hex_dump"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/open_with_url"/>
|
||||
<EditText
|
||||
tools:targetApi="o"
|
||||
android:id="@+id/open_with_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:gravity="start|top"
|
||||
android:typeface="monospace"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:inputType="textUri"
|
||||
android:hint="@string/open_with_url_hint"
|
||||
android:importantForAutofill="no"/>
|
||||
</LinearLayout>
|
||||
</de.markusfisch.android.binaryeye.widget.ConfinedScrollView>
|
||||
@@ -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"
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:material="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/export_svg"
|
||||
android:title="@string/export_svg"
|
||||
android:icon="@drawable/ic_action_save_svg"
|
||||
android:id="@+id/copy_to_clipboard"
|
||||
android:title="@string/copy_to_clipboard"
|
||||
android:icon="@drawable/ic_action_copy"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/export_png"
|
||||
android:title="@string/export_png"
|
||||
android:icon="@drawable/ic_action_save_png"
|
||||
android:id="@+id/export_to_file"
|
||||
android:title="@string/export_to_file"
|
||||
android:icon="@drawable/ic_action_save"
|
||||
material:showAsAction="ifRoom"/>
|
||||
</menu>
|
||||
|
||||
@@ -6,20 +6,26 @@
|
||||
android:title="@string/remove_scan"
|
||||
android:icon="@drawable/ic_action_remove"
|
||||
android:visible="false"
|
||||
material:showAsAction="always"/>
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/copy_password"
|
||||
android:title="@string/copy_password"
|
||||
android:icon="@drawable/ic_action_copy_password"
|
||||
android:visible="false"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/copy_to_clipboard"
|
||||
android:title="@string/copy_to_clipboard"
|
||||
android:icon="@drawable/ic_action_copy"
|
||||
material:showAsAction="always"/>
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/share"
|
||||
android:title="@string/share"
|
||||
android:icon="@drawable/ic_action_share"
|
||||
material:showAsAction="always"/>
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/create"
|
||||
android:title="@string/compose_barcode"
|
||||
android:icon="@drawable/ic_action_create"
|
||||
material:showAsAction="always"/>
|
||||
material:showAsAction="ifRoom"/>
|
||||
</menu>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:material="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/next"
|
||||
android:title="@string/encode"
|
||||
android:icon="@drawable/ic_action_next"
|
||||
material:showAsAction="ifRoom"/>
|
||||
</menu>
|
||||
@@ -1,4 +1,5 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<background android:drawable="@color/accent"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -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>
|
||||
@@ -11,6 +11,10 @@
|
||||
<item quantity="other">%2$d Zeichen, %1$s</item>
|
||||
</plurals>
|
||||
<string name="error_correction_level">Fehlerkorrektur-Level</string>
|
||||
<string name="error_correction_level_l" formatted="false">Low (~7% wiederherstellbar)</string>
|
||||
<string name="error_correction_level_m" formatted="false">Medium (~15% wiederherstellbar)</string>
|
||||
<string name="error_correction_level_q" formatted="false">Quartile (~25% wiederherstellbar)</string>
|
||||
<string name="error_correction_level_h" formatted="false">High (~30% wiederherstellbar)</string>
|
||||
<string name="issue_number">Ausgabe Nummer</string>
|
||||
<string name="orientation">Orientierung</string>
|
||||
<string name="other_meta_data">Metadaten</string>
|
||||
@@ -19,9 +23,12 @@
|
||||
<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>
|
||||
<string name="copied_to_clipboard">Inhalt wurde in die Zwischenablage kopiert</string>
|
||||
<string name="copy_password">Passwort in die Zwischenablage kopieren</string>
|
||||
<string name="copied_password_to_clipboard">Passwort wurde in die Zwischenablage kopiert</string>
|
||||
<string name="open_url">URL öffnen</string>
|
||||
<string name="cannot_resolve_action">Keine App kann das öffnen</string>
|
||||
@@ -37,19 +44,69 @@
|
||||
<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="show_crop_handle">Bereich verkleinern anzeigen</string>
|
||||
<string name="zoom_by_swiping">Hoch/Runter streichen um zu zoomen</string>
|
||||
<string name="auto_rotate">Vertikale Erkennung von 1D Barcodes</string>
|
||||
<string name="try_harder">Suche länger und gründlicher nach Barcodes</string>
|
||||
<string name="scan_category">Scannen</string>
|
||||
<string name="content_category">Inhalt</string>
|
||||
<string name="locale_category">Sprache</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>
|
||||
<string name="ignore_consecutive_duplicates">Keine aufeinanderfolgenden Duplikate speichern</string>
|
||||
<string name="open_immediately">Inspektion überspringen und Inhalte sofort öffnen</string>
|
||||
<string name="use_history_summary">Eingelesene Barcodes auf dem Gerät speichern.</string>
|
||||
<string name="ignore_consecutive_duplicates">Duplikate ignorieren</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">Aufeinanderfolgende Duplikate werden nicht in der Scan-Historie gespeichert.</string>
|
||||
<string name="open_immediately">Inhalte sofort öffnen</string>
|
||||
<string name="open_immediately_summary">Inspektion überspringen und Inhalt sofort öffnen. Kann schädliche Inhalte öffnen, wenn aktiviert.</string>
|
||||
<string name="copy_immediately">Automatisch in Zwischenablage</string>
|
||||
<string name="copy_immediately_summary">Eingelesene Inhalte werden automatisch in die Zwischenablage kopiert. Bitte beachten Sie, dass andere Anwendungen die Zwischenablage im Hintergrund auslesen können.</string>
|
||||
<string name="show_meta_data">Meta Daten anzeigen</string>
|
||||
<string name="show_meta_data_summary">Zusätzliche Daten über den eingelesenen Barcode anzeigen.</string>
|
||||
<string name="show_hex_dump">Hex Dump anzeigen</string>
|
||||
<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_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>
|
||||
<string name="really_remove_selected_scans">Alle ausgewählten Codes entfernen?</string>
|
||||
@@ -84,18 +141,27 @@
|
||||
<string name="search_web">Suche im Internet</string>
|
||||
<string name="search_scan">Code suchen</string>
|
||||
<string name="export_to_file">In Datei exportieren</string>
|
||||
<string name="export_png">Als PNG exportieren</string>
|
||||
<string name="export_svg">Als SVG exportieren</string>
|
||||
<string name="export_as">Exportieren als?</string>
|
||||
<string name="export_csv_comma">CSV mit Kommas</string>
|
||||
<string name="export_csv_semicolon">CSV mit Semikolons</string>
|
||||
<string name="export_json">Als JSON exportieren</string>
|
||||
<string name="export_database">SQLite Datenbank exportieren</string>
|
||||
<string name="export_csv_comma">CSV Datei mit Kommas</string>
|
||||
<string name="export_csv_semicolon">CSV Datei mit Semikolons</string>
|
||||
<string name="export_json">JSON Datei</string>
|
||||
<string name="export_database">SQLite Datenbank</string>
|
||||
<string name="saved_in_downloads">In den Downloads gespeichert</string>
|
||||
<string name="rotate_image_cw">Bild im Uhrzeigersinn rotieren</string>
|
||||
<string name="pick_file">Bilddatei auswählen</string>
|
||||
<string name="pick_code_to_scan">Code im Bild auswählen</string>
|
||||
<string name="shortcut_decode">Barcode einlesen</string>
|
||||
<string name="shortcut_decode">Barcode lesen</string>
|
||||
<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,30 +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">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">¿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">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>
|
||||
@@ -32,28 +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="show_crop_handle">Show cropping limiter</string>
|
||||
<string name="zoom_by_swiping">Aumento de cámara al deslizar hacia arriba/abajo</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="try_harder">Optimizar el lector para la precisión, no la velocidad</string>
|
||||
<string name="vibrate">Vibración en la detección</string>
|
||||
<string name="use_history">Guardar historial de escaneados</string>
|
||||
<string name="ignore_consecutive_duplicates">No guardar duplicados consecutivos</string>
|
||||
<string name="open_immediately">Saltar la inspección y abrir el contenido inmediatamente</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_hex_dump">Mostrar volcado hexagonal</string>
|
||||
<string name="open_with_url">Abrir datos desconocidos con URL</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="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>
|
||||
@@ -67,35 +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_png">Export as PNG</string>
|
||||
<string name="export_svg">Export as SVG</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">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,101 +1,168 @@
|
||||
<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 characters, %1$s</item>
|
||||
<item quantity="other">%2$d characters, %1$s</item>
|
||||
<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">Error correction level</string>
|
||||
<string name="error_correction_level">Niveau de correction d\'erreur</string>
|
||||
<string name="error_correction_level_l" formatted="false">Faible (~7% de correction)</string>
|
||||
<string name="error_correction_level_m" formatted="false">Moyen (~15% de correction)</string>
|
||||
<string name="error_correction_level_q" formatted="false">Quartile (~25% de correction)</string>
|
||||
<string name="error_correction_level_h" formatted="false">Élevé (~30% de 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="toggle_flash">Toggle flash</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="copy_to_clipboard">Copier dans le presse-papier</string>
|
||||
<string name="copied_to_clipboard">Mis dans le presse-papiers</string>
|
||||
<string name="copied_password_to_clipboard">Password copied to clipboard</string>
|
||||
<string name="other_meta_data">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">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>
|
||||
<string name="copied_to_clipboard">Copié dans le presse-papiers</string>
|
||||
<string name="copy_password">Copier le mot de passe dans le presse-papiers</string>
|
||||
<string name="copied_password_to_clipboard">Mot de passe copié dans le presse-papiers</string>
|
||||
<string name="open_url">Ouvrir l\'url</string>
|
||||
<string name="cannot_resolve_action">Aucune application ne peut ouvrir ça</string>
|
||||
<string name="pick_search_engine">Pick a search engine</string>
|
||||
<string name="cannot_resolve_action">Aucune application ne peut ouvrir cela</string>
|
||||
<string name="pick_search_engine">Choisir un moteur de recherche</string>
|
||||
<string name="format">Format</string>
|
||||
<string name="size">Taille en pixels</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Entrez votre contenu ici</string>
|
||||
<string name="input_content_here">Votre contenu ici</string>
|
||||
<string name="encode">ENCODER</string>
|
||||
<string name="error_no_content">Contenu manquant</string>
|
||||
<string name="error_encoding_barcode">Barcode cannot be generated</string>
|
||||
<string name="error_encoding_barcode">Le code-barres ne peut pas être généré</string>
|
||||
<string name="view_barcode">Voir le code barre</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Changer de camera</string>
|
||||
<string name="bulk_mode">Scan continuously</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">Preferences</string>
|
||||
<string name="show_crop_handle">Show cropping limiter</string>
|
||||
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
|
||||
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
|
||||
<string name="try_harder">Optimize reader for accuracy, not speed</string>
|
||||
<string name="vibrate">Vibrate on detection</string>
|
||||
<string name="use_history">Sauvegarder l\'histoire des scans</string>
|
||||
<string name="ignore_consecutive_duplicates">Do not save consecutive duplicates</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="show_meta_data">Show metadata</string>
|
||||
<string name="show_hex_dump">Show hex dump</string>
|
||||
<string name="open_with_url">Open unknown data with URL</string>
|
||||
<string name="really_remove_scan">Supprimer le scan?</string>
|
||||
<string name="really_remove_all_scans">Supprimer tous les scans?</string>
|
||||
<string name="really_remove_selected_scans">Really remove selected scans?</string>
|
||||
<string name="clear_history">Clear history</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="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">Langue personnalisée</string>
|
||||
<string name="follow_system_settings">Suivre les réglages du système</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\'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>
|
||||
<string name="open_immediately">Ouvrir immédiatement</string>
|
||||
<string name="open_immediately_summary">Sauter l\'inspection et ouvrir le contenu immédiatement. Si activé, cela peut ouvrir un contenu nocif.</string>
|
||||
<string name="copy_immediately">Mettre dans le presse-papiers</string>
|
||||
<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">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_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="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 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>
|
||||
<string name="no_barcode_found">Aucun code-barres trouvé</string>
|
||||
<string name="pick_list_separator">How to separate list items?</string>
|
||||
<string name="separator_line_break">Line break</string>
|
||||
<string name="separator_ruler">Ruler</string>
|
||||
<string name="save_as_file_name">Save in Downloads as file?</string>
|
||||
<string name="file_name">File name</string>
|
||||
<string name="error_saving_file">Cannot save file</string>
|
||||
<string name="error_file_exists">File already exists</string>
|
||||
<string name="connect_to_wifi">Connect to WiFi</string>
|
||||
<string name="wifi_config_failed">Could not configure WiFi</string>
|
||||
<string name="wifi_added">WiFi added</string>
|
||||
<string name="sms_send">Send SMS</string>
|
||||
<string name="sms_error">Could not send SMS</string>
|
||||
<string name="tel_dial">Dial number</string>
|
||||
<string name="tel_error">Could not dial number</string>
|
||||
<string name="mail_send">Send mail</string>
|
||||
<string name="mail_error">Could not send mail</string>
|
||||
<string name="vcard_add">Add to contacts</string>
|
||||
<string name="vcard_failed">Could not add to contacts</string>
|
||||
<string name="vevent_add">Add to calendar</string>
|
||||
<string name="vevent_failed">Could not add to calendar</string>
|
||||
<string name="otpauth_add">Add 2FA</string>
|
||||
<string name="search_web">Search the web</string>
|
||||
<string name="search_scan">Search scan</string>
|
||||
<string name="export_to_file">Export to file</string>
|
||||
<string name="export_png">Export as PNG</string>
|
||||
<string name="export_svg">Export as SVG</string>
|
||||
<string name="export_as">Export as?</string>
|
||||
<string name="export_csv_comma">CSV with commas</string>
|
||||
<string name="export_csv_semicolon">CSV with semicolons</string>
|
||||
<string name="export_json">Export as JSON</string>
|
||||
<string name="export_database">Export SQLite database</string>
|
||||
<string name="saved_in_downloads">Saved in downloads</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
<string name="shortcut_decode">Scan a barcode</string>
|
||||
<string name="shortcut_encode">Create a barcode</string>
|
||||
<string name="shortcut_preferences">Settings</string>
|
||||
<string name="pick_list_separator">Comment séparer les éléments listés ?</string>
|
||||
<string name="separator_line_break">Saut de ligne</string>
|
||||
<string name="separator_ruler">Règle</string>
|
||||
<string name="save_as_file_name">Sauvegarder dans les Téléchargements ?</string>
|
||||
<string name="file_name">Nom du fichier</string>
|
||||
<string name="error_saving_file">Impossible de sauvegarder le fichier</string>
|
||||
<string name="error_file_exists">Fichier déjà existant</string>
|
||||
<string name="connect_to_wifi">Connecter au WiFi</string>
|
||||
<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">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>
|
||||
<string name="mail_error">Impossible d\'envoyer le mail</string>
|
||||
<string name="vcard_add">Ajouter aux contacts</string>
|
||||
<string name="vcard_failed">Impossible d\'ajouter aux contacts</string>
|
||||
<string name="vevent_add">Ajouter à l\'agenda</string>
|
||||
<string name="vevent_failed">Impossible d\'ajouter à l\'agenda</string>
|
||||
<string name="otpauth_add">Ajouter 2FA</string>
|
||||
<string name="search_web">Rechercher sur internet</string>
|
||||
<string name="search_scan">Rechercher un scan</string>
|
||||
<string name="export_to_file">Exporter comme fichier</string>
|
||||
<string name="export_as">Exporter comme ?</string>
|
||||
<string name="export_csv_comma">CSV avec virgules</string>
|
||||
<string name="export_csv_semicolon">CSV avec points-virgules</string>
|
||||
<string name="export_json">Exporter comme JSON</string>
|
||||
<string name="export_database">Exporter la base de données SQLite</string>
|
||||
<string name="saved_in_downloads">Sauvegardé dans les Téléchargements</string>
|
||||
<string name="rotate_image_cw">Tourner l\'image dans le sens horaire</string>
|
||||
<string name="pick_file">Choisir un fichier image</string>
|
||||
<string name="pick_code_to_scan">Choisissez le code à scanner</string>
|
||||
<string name="shortcut_decode">Scanner un code-barres</string>
|
||||
<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,19 +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="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">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="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>
|
||||
@@ -36,29 +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="show_crop_handle">Show cropping limiter</string>
|
||||
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
|
||||
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
|
||||
<string name="try_harder">Optimize reader for accuracy, not speed</string>
|
||||
<string name="vibrate">Vibrate on detection</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="ignore_consecutive_duplicates">Do not save consecutive duplicates</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="show_meta_data">Show metadata</string>
|
||||
<string name="show_hex_dump">Show hex dump</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="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>
|
||||
@@ -67,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>
|
||||
@@ -82,20 +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_png">Export as PNG</string>
|
||||
<string name="export_svg">Export as SVG</string>
|
||||
<string name="export_as">Export as?</string>
|
||||
<string name="export_csv_comma">CSV with commas</string>
|
||||
<string name="export_csv_semicolon">CSV with semicolons</string>
|
||||
<string name="export_json">Export as JSON</string>
|
||||
<string name="export_database">Export 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="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>
|
||||
|
||||
@@ -10,18 +10,25 @@
|
||||
<item quantity="other">%1$s, %2$d karakter</item>
|
||||
</plurals>
|
||||
<string name="error_correction_level">Level koreksi kesalahan</string>
|
||||
<string name="issue_number">Nomor pemindaian</string>
|
||||
<string name="error_correction_level_l" formatted="false">Rendah (~7% koreksi)</string>
|
||||
<string name="error_correction_level_m" formatted="false">Medium (~15% koreksi)</string>
|
||||
<string name="error_correction_level_q" formatted="false">Kuartil (~25% koreksi)</string>
|
||||
<string name="error_correction_level_h" formatted="false">Tinggi (~30% koreksi)</string>
|
||||
<string name="issue_number">Nomor pindaian</string>
|
||||
<string name="orientation">Orientasi</string>
|
||||
<string name="other_meta_data">Metadata</string>
|
||||
<string name="pdf417_extra_metadata">Metadata PDF417</string>
|
||||
<string name="possible_country">Kemungkinan negara pembuat</string>
|
||||
<string name="suggested_price">Harga yang disarankan</string>
|
||||
<string name="upc_ean_extension">Ekstensi UPC EAN</string>
|
||||
<string name="toggle_flash">Nyala/Matikan blitz</string>
|
||||
<string name="toggle_flash">Nyala/matikan blitz</string>
|
||||
<string name="error_flash">Error toggling flash</string>
|
||||
<string name="share">Bagikan</string>
|
||||
<string name="copy_to_clipboard">Salin ke papan klip</string>
|
||||
<string name="copied_to_clipboard">Simpan ke papan klip</string>
|
||||
<string name="copied_password_to_clipboard">Sandi disalin ke papan klip</string>
|
||||
<string name="share_as">Bagikan sebagai?</string>
|
||||
<string name="copy_to_clipboard">Salin ke clipboard</string>
|
||||
<string name="copied_to_clipboard">Disalin ke clipboard</string>
|
||||
<string name="copy_password">Salin sandi ke clipboard</string>
|
||||
<string name="copied_password_to_clipboard">Sandi disalin ke clipboard</string>
|
||||
<string name="open_url">Buka url</string>
|
||||
<string name="cannot_resolve_action">Tidak ada aplikasi yang bisa membukanya</string>
|
||||
<string name="pick_search_engine">Pilih mesin pencari</string>
|
||||
@@ -35,34 +42,84 @@
|
||||
<string name="view_barcode">Lihat barcode</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Ganti kamera</string>
|
||||
<string name="bulk_mode">Scan continuously</string>
|
||||
<string name="bulk_mode">Pindai tanpa henti</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">Riwayat</string>
|
||||
<string name="preferences">Preferensi</string>
|
||||
<string name="scan_category">Pemindaian</string>
|
||||
<string name="content_category">Konten</string>
|
||||
<string name="locale_category">Bahasa</string>
|
||||
<string name="custom_locale">Bahasa lainnya</string>
|
||||
<string name="follow_system_settings">Ikuti pengaturan sistem</string>
|
||||
<string name="show_crop_handle">Tampilkan pembatas pemotongan</string>
|
||||
<string name="zoom_by_swiping">Perbesar kamera dengan menggeser ke atas/bawah</string>
|
||||
<string name="auto_rotate">Kenali barcode 1D secara vertikal</string>
|
||||
<string name="try_harder">Optimalkan akurasi, bukan kecepatan</string>
|
||||
<string name="show_crop_handle_summary">Batasi pemindaian ke batas area tertentu.</string>
|
||||
<string name="barcode_formats">Barcode formats</string>
|
||||
<string name="zoom_by_swiping">Geser ke atas/bawah untuk memperbesar</string>
|
||||
<string name="zoom_by_swiping_summary">Geser layar kamera ke atas atau ke bawah untuk mengontrol perbesaran gambar.</string>
|
||||
<string name="auto_rotate">Kenali barcode vertikal 1D</string>
|
||||
<string name="auto_rotate_summary">Otomatis putar frame kamera mengenali barcode vertikal 1D. Tergantung pada perangkat, mungkin akan berpengaruh pada performa.</string>
|
||||
<string name="try_harder">Optimalkan akurasi pembaca</string>
|
||||
<string name="try_harder_summary">Aktifkan opsi ini untuk kode yang susah dibaca. Akan berpengaruh pada performa.</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">Bergetar saat mendeteksi</string>
|
||||
<string name="vibrate_summary">Aktifkan opsi ini jika anda ingin perangkat anda bergetar ketika kode berhasil dipindai.</string>
|
||||
<string name="use_history">Simpan riwayat pemindaian</string>
|
||||
<string name="ignore_consecutive_duplicates">Jangan simpan duplikat berturutan</string>
|
||||
<string name="open_immediately">Lewati inspeksi dan segera buka konten</string>
|
||||
<string name="use_history_summary">Simpan kode yang berhasil dipindai pada perangkat anda.</string>
|
||||
<string name="ignore_consecutive_duplicates">Abaikan duplikat</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">Jangan simpan duplikat yang berturutan di dalam riwayat pemindaian.</string>
|
||||
<string name="open_immediately">Buka segera</string>
|
||||
<string name="open_immediately_summary">Lewati inspeksi dan segera buka konten. Mungkin membuka konten berbahaya jika diaktifkan.</string>
|
||||
<string name="copy_immediately">Simpan ke dalam clipboard</string>
|
||||
<string name="copy_immediately_summary">Otomatis menyalin konten yang dipindai ke dalam clipboard. Harap diingat bahwa apl lain bisa melihat isi dari clipboard anda di latar belakang.</string>
|
||||
<string name="show_meta_data">Tampilkan metadata</string>
|
||||
<string name="show_meta_data_summary">Tampilkan data tambahan tentang barcode yang dipindai.</string>
|
||||
<string name="show_hex_dump">Tampilkan hex dump</string>
|
||||
<string name="show_hex_dump_summary">Tampilkan hex dump dari konten hasil pemindaian.</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="default_search_engine">Open unknown data in</string>
|
||||
<string name="always_ask">Always ask</string>
|
||||
<string name="open_with_url">Buka data tak dikenal dengan URL</string>
|
||||
<string name="really_remove_scan">Yakin menghapus pemindaian?</string>
|
||||
<string name="really_remove_all_scans">Yakin menghapus semua pemindaian?</string>
|
||||
<string name="really_remove_selected_scans">Yakin menghapus semua pemindaian?</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">Penerusan</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">Kirim setiap pindaian ke URL</string>
|
||||
<string name="send_scan_type">Tipe permintaan untuk setiap pindaian</string>
|
||||
<string name="send_type_get_add_content">GET dan tambahkan konten</string>
|
||||
<string name="send_type_get_query_string">GET dengan kueri lema lengkap</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">Yakin menghapus pindaian?</string>
|
||||
<string name="really_remove_all_scans">Yakin menghapus semua pindaian?</string>
|
||||
<string name="really_remove_selected_scans">Yakin menghapus pindaian yang dipilih?</string>
|
||||
<string name="clear_history">Hapus riwayat</string>
|
||||
<string name="copy_scan">Salin pemindaian</string>
|
||||
<string name="copy_scan">Salin pindaian</string>
|
||||
<string name="edit_scan">Sunting label</string>
|
||||
<string name="remove_scan">Buang pindaian</string>
|
||||
<string name="enter_name">Masukkan keterangan pemindaian</string>
|
||||
<string name="enter_name">Masukkan keterangan pindaian</string>
|
||||
<string name="enter_name_hint">Nama pindaian</string>
|
||||
<string name="no_barcode_found">Tidak ada barcode yang ditemukan</string>
|
||||
<string name="pick_list_separator">Pilih pemisah daftar item?</string>
|
||||
<string name="separator_line_break">Baris baru</string>
|
||||
<string name="separator_ruler">Penggaris</string>
|
||||
<string name="save_as_file_name">Simpan sebagai berkas?</string>
|
||||
<string name="save_as_file_name">Simpan sebagai berkas Unduhan?</string>
|
||||
<string name="file_name">Nama berkas</string>
|
||||
<string name="error_saving_file">Tidak bisa menyimpan berkas</string>
|
||||
<string name="error_file_exists">Berkas sudah ada</string>
|
||||
@@ -83,8 +140,6 @@
|
||||
<string name="search_web">Cari secara daring</string>
|
||||
<string name="search_scan">Cari pindaian</string>
|
||||
<string name="export_to_file">Ekspor ke berkas</string>
|
||||
<string name="export_png">Ekspor sebagai PNG</string>
|
||||
<string name="export_svg">Ekspor sebagai SVG</string>
|
||||
<string name="export_as">Ekspor sebagai?</string>
|
||||
<string name="export_csv_comma">CSV dengan koma</string>
|
||||
<string name="export_csv_semicolon">CSV dengan titik koma</string>
|
||||
@@ -97,4 +152,15 @@
|
||||
<string name="shortcut_decode">Pindai barcode</string>
|
||||
<string name="shortcut_encode">Buat barcode</string>
|
||||
<string name="shortcut_preferences">Pengaturan</string>
|
||||
<string name="background_request_failed">Permintaan latar belakang gagal</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>
|
||||
|
||||
@@ -11,17 +11,24 @@
|
||||
<item quantity="other">%2$d caratteri, %1$s</item>
|
||||
</plurals>
|
||||
<string name="error_correction_level">Livello di correzione errori</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">Numero di edizione</string>
|
||||
<string name="orientation">Orientamento</string>
|
||||
<string name="other_meta_data">Metadati</string>
|
||||
<string name="pdf417_extra_metadata">Metadati PDF417</string>
|
||||
<string name="possible_country">Probabile paese di fabbricazione</string>
|
||||
<string name="possible_country">Probabile Paese di fabbricazione</string>
|
||||
<string name="suggested_price">Prezzo consigliato</string>
|
||||
<string name="upc_ean_extension">Estensione UPC EAN</string>
|
||||
<string name="toggle_flash">Flash</string>
|
||||
<string name="error_flash">Error toggling flash</string>
|
||||
<string name="share">Condividi</string>
|
||||
<string name="share_as">Share as?</string>
|
||||
<string name="copy_to_clipboard">Copia negli appunti</string>
|
||||
<string name="copied_to_clipboard">Metti negli appunti</string>
|
||||
<string name="copied_to_clipboard">Copiato negli appunti</string>
|
||||
<string name="copy_password">Copia password negli appunti</string>
|
||||
<string name="copied_password_to_clipboard">Password copiata negli appunti</string>
|
||||
<string name="open_url">Apri URL</string>
|
||||
<string name="cannot_resolve_action">Nessuna app può aprirlo</string>
|
||||
@@ -36,25 +43,75 @@
|
||||
<string name="view_barcode">Vedi codice a barre</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Cambia fotocamera</string>
|
||||
<string name="bulk_mode">Scan continuously</string>
|
||||
<string name="bulk_mode">Scansione continua</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">Cronologia</string>
|
||||
<string name="preferences">Preferenze</string>
|
||||
<string name="show_crop_handle">Show cropping limiter</string>
|
||||
<string name="scan_category">Scansione</string>
|
||||
<string name="content_category">Contenuto</string>
|
||||
<string name="locale_category">Language</string>
|
||||
<string name="custom_locale">Custom language</string>
|
||||
<string name="follow_system_settings">Follow system settings</string>
|
||||
<string name="show_crop_handle">Mostra riquadro di limite</string>
|
||||
<string name="show_crop_handle_summary">Limita la scansione ad una regione personalizzata.</string>
|
||||
<string name="barcode_formats">Barcode formats</string>
|
||||
<string name="zoom_by_swiping">Zoom fotocamera scorrendo su/giù</string>
|
||||
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
|
||||
<string name="zoom_by_swiping_summary">Scorri su o giù per controllare lo zoom della fotocamera.</string>
|
||||
<string name="auto_rotate">Riconosci i codici a barre 1D verticalmente</string>
|
||||
<string name="auto_rotate_summary">Ruota automaticamente la fotocamera per riconoscere i codici a barre 1D verticali. In base al dispositivo, potrebbe ridurre le prestazioni.</string>
|
||||
<string name="try_harder">Ottimizza il lettore per accuratezza, non velocità</string>
|
||||
<string name="try_harder_summary">Attiva questa opzione per codici molto difficili da leggere. Riduce le prestazioni.</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">Vibra quando rilevato</string>
|
||||
<string name="vibrate_summary">Attivalo se vuoi che il dispositivo vibri quando viene riconosciuto un codice.</string>
|
||||
<string name="use_history">Salva cronologia delle scansioni</string>
|
||||
<string name="use_history_summary">Salva i codici riconosciuti sul dispositivo.</string>
|
||||
<string name="ignore_consecutive_duplicates">Non salvare doppi consecutivi</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">Non salvare doppi consecutivi nella cronologia di scansione.</string>
|
||||
<string name="open_immediately">Salta l\'analisi e apri i contenuti direttamente</string>
|
||||
<string name="open_immediately_summary">Salta l\'analisi e apri i contenuti direttamente. Se attivo potrebbe aprire contenuti dannosi.</string>
|
||||
<string name="copy_immediately">Copia negli appunti</string>
|
||||
<string name="copy_immediately_summary">Copia automaticamente i contenuti scansionati negli appunti. Nota che altre app potrebbero leggere gli appunti in secondo piano.</string>
|
||||
<string name="show_meta_data">Mostra metadati</string>
|
||||
<string name="show_meta_data_summary">Mostra dati aggiuntivi sul codice a barre scansionato.</string>
|
||||
<string name="show_hex_dump">Mostra dump esadecimale</string>
|
||||
<string name="show_hex_dump_summary">Mostra un dump esadecimale dei contenuti scansionati.</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="default_search_engine">Open unknown data in</string>
|
||||
<string name="always_ask">Always ask</string>
|
||||
<string name="open_with_url">Apri dati sconosciuti con 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">Inoltro</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">Invia ogni scansione ad un URL</string>
|
||||
<string name="send_scan_type">Tipo di richiesta per ogni scansione</string>
|
||||
<string name="send_type_get_add_content">GET e aggiungi il contenuto</string>
|
||||
<string name="send_type_get_query_string">GET con stringa completa della richiesta</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">Vuoi veramente rimuovere la scansione?</string>
|
||||
<string name="really_remove_all_scans">Vuoi veramente rimuovere tutte le scansioni?</string>
|
||||
<string name="really_remove_selected_scans">Really remove selected scans?</string>
|
||||
<string name="really_remove_selected_scans">Vuoi veramente rimuovere le scansioni selezionate?</string>
|
||||
<string name="clear_history">Pulisci cronologia</string>
|
||||
<string name="copy_scan">Copy scan</string>
|
||||
<string name="copy_scan">Copia scansione</string>
|
||||
<string name="edit_scan">Modifica etichetta</string>
|
||||
<string name="remove_scan">Rimuovi scansione</string>
|
||||
<string name="enter_name">Inserisci un nome per descrivere la scansione</string>
|
||||
@@ -84,12 +141,10 @@
|
||||
<string name="search_web">Cerca nel web</string>
|
||||
<string name="search_scan">Cerca scansione</string>
|
||||
<string name="export_to_file">Esporta su file</string>
|
||||
<string name="export_png">Export as PNG</string>
|
||||
<string name="export_svg">Export as SVG</string>
|
||||
<string name="export_as">Esportare come?</string>
|
||||
<string name="export_csv_comma">CSV con virgole</string>
|
||||
<string name="export_csv_semicolon">CSV con punti e virgola</string>
|
||||
<string name="export_json">Export as JSON</string>
|
||||
<string name="export_json">Esporta come JSON</string>
|
||||
<string name="export_database">Esporta database SQLite</string>
|
||||
<string name="saved_in_downloads">Salvato nei download</string>
|
||||
<string name="rotate_image_cw">Ruota immagine in senso orario</string>
|
||||
@@ -98,4 +153,15 @@
|
||||
<string name="shortcut_decode">Scansiona un codice a barre</string>
|
||||
<string name="shortcut_encode">Crea un codice a barre</string>
|
||||
<string name="shortcut_preferences">Impostazioni</string>
|
||||
<string name="background_request_failed">Richiesta in secondo piano fallita</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,166 @@
|
||||
<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="other">%2$d 文字, %1$s</item>
|
||||
</plurals>
|
||||
<string name="error_correction_level">誤り訂正レベル</string>
|
||||
<string name="error_correction_level_l" formatted="false">低 (~7% の正確性)</string>
|
||||
<string name="error_correction_level_m" formatted="false">中 (~15% の正確性)</string>
|
||||
<string name="error_correction_level_q" formatted="false">高 (~25% の正確性)</string>
|
||||
<string name="error_correction_level_h" formatted="false">最高 (~30% の正確性)</string>
|
||||
<string name="issue_number">発行番号</string>
|
||||
<string name="orientation">方向</string>
|
||||
<string name="other_meta_data">メタデータ</string>
|
||||
<string name="pdf417_extra_metadata">PDF417 メタデータ</string>
|
||||
<string name="possible_country">製造国</string>
|
||||
<string name="suggested_price">価格</string>
|
||||
<string name="upc_ean_extension">UPC/EAN 拡張型</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">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">バーコード形式</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">16進数を表示</string>
|
||||
<string name="show_hex_dump_summary">スキャンされたコンテンツの16進数データを確認できるようにします。</string>
|
||||
<string name="close_automatically">コピー/共有後に戻る</string>
|
||||
<string name="close_automatically_summary">コンテンツをコピー/共有した後、自動的にスキャン画面に戻るようにします。</string>
|
||||
<string name="default_search_engine">未知のデータの表示方法</string>
|
||||
<string name="always_ask">常に尋ねる</string>
|
||||
<string name="open_with_url">未知のデータを次のURLから開く</string>
|
||||
<string name="clear_network_suggestions">ネットワークの提案を消去</string>
|
||||
<string name="clear_network_suggestions_summary">このアプリのネットワークの提案をすべて削除します。</string>
|
||||
<string name="really_remove_all_networks">ネットワークの提案を消去してもよろしいですか?</string>
|
||||
<string name="clear_network_suggestions_success">ネットワークの提案は消去されました</string>
|
||||
<string name="clear_network_suggestions_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">スキャンしたデータを次の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">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">WiFiに接続</string>
|
||||
<string name="wifi_config_failed">WiFiに接続できませんでした</string>
|
||||
<string name="wifi_added">WiFiは登録されました</string>
|
||||
<string name="sms_send">SMSを送信</string>
|
||||
<string name="sms_error">SMSを送信できませんでした</string>
|
||||
<string name="tel_dial">電話番号</string>
|
||||
<string name="tel_error">電話を開始できませんでした</string>
|
||||
<string name="mail_send">メールを送信</string>
|
||||
<string name="mail_error">メールを送信できませんでした</string>
|
||||
<string name="vcard_add">連絡先に追加</string>
|
||||
<string name="vcard_failed">連絡先に追加できませんでした</string>
|
||||
<string name="vevent_add">カレンダーに追加</string>
|
||||
<string name="vevent_failed">カレンダーに追加できませんでした</string>
|
||||
<string name="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">JSONファイル</string>
|
||||
<string name="export_database">SQLiteデータベース</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,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">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">გამოშვების ნომერი</string>
|
||||
<string name="orientation">ორიენტაცია</string>
|
||||
<string name="other_meta_data">მეტამონაცემები</string>
|
||||
<string name="pdf417_extra_metadata">PDF417 მეტამონაცემები</string>
|
||||
<string name="possible_country">შესაძლო მწარმოებელი ქვეყანა</string>
|
||||
<string name="suggested_price">დაახლოებითი ფასი</string>
|
||||
<string name="upc_ean_extension">UPC EAN გაფართოება</string>
|
||||
<string name="toggle_flash">განათების ჩართვა</string>
|
||||
<string name="error_flash">Error toggling flash</string>
|
||||
<string name="share">გაზიარება</string>
|
||||
<string name="share_as">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">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">ისტორია</string>
|
||||
<string name="preferences">პარამეტრები</string>
|
||||
<string name="scan_category">სკანირება</string>
|
||||
<string name="content_category">კონტენტი</string>
|
||||
<string name="locale_category">Language</string>
|
||||
<string name="custom_locale">Custom language</string>
|
||||
<string name="follow_system_settings">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">1D ვერტიკალური შტრიხ-კოდების ამოცნობა</string>
|
||||
<string name="auto_rotate_summary">კამერის ავტომატური შეტრიალება ვერტიკალური 1D შტრიხ-კოდების ამოსაცნობად. მოწყობილობიდან გამომდინარე, შესაძლოა იმოქმედოს წარმადობაზე.</string>
|
||||
<string name="try_harder">წაკითხვის ოპტიმიზირება სიზუსტეზე და არა სისწრაფეზე</string>
|
||||
<string name="try_harder_summary">ეს პარამეტრი ჩართეთ იმ შტრიხ-კოდებისთვის, რომელთა ამოცნობაც ძალიან ძნელია. მოქმედებს წარმადობაზე.</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">ვიბრირება პოვნისას</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">თექვსმეტობითი dump-ის ჩვენება</string>
|
||||
<string name="show_hex_dump_summary">სკანირებული შტრიხ-კოდის კონტენტის თექვსმეტობითი dump-ის ჩვენება.</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="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">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">დაკავშირება WiFi-სთან</string>
|
||||
<string name="wifi_config_failed">WiFi კონფიგურაცია ვერ მოხერხდა</string>
|
||||
<string name="wifi_added">WiFi დამატებულია</string>
|
||||
<string name="sms_send">შეტყობინების გაგზავნა</string>
|
||||
<string name="sms_error">შეტყობინების გაგზავნა ვერ მოხერხდა</string>
|
||||
<string name="tel_dial">ნომრის აკრეფა</string>
|
||||
<string name="tel_error">ნომრის აკრეფა ვერ მოხერხდა</string>
|
||||
<string name="mail_send">წერილის გაგზავნა</string>
|
||||
<string name="mail_error">წერილი ვერ გაიგზავნა</string>
|
||||
<string name="vcard_add">კონტაქტებში დამატება</string>
|
||||
<string name="vcard_failed">კონტაქტებში დამატება ვერ მოხერხდა</string>
|
||||
<string name="vevent_add">კალენდარში დამატება</string>
|
||||
<string name="vevent_failed">კალენდარში დამატება ვერ მოხერხდა</string>
|
||||
<string name="otpauth_add">დავამატოთ 2FA</string>
|
||||
<string name="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">JSON</string>
|
||||
<string name="export_database">SQLite მონაცემთა ბაზა</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>
|
||||
@@ -1,101 +1,167 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Deze app kan niet werken zonder cameratoegang. Tot ziens.</string>
|
||||
<string name="camera_error">Kan camera niet openen; probeer het opnieuw.</string>
|
||||
<string name="camera_error">Geen toegang tot camera, probeer opnieuw.</string>
|
||||
<string name="scan_code">Scan een code</string>
|
||||
<string name="compose_barcode">Scan een barcode</string>
|
||||
<string name="compose_barcode">Barcode scannen</string>
|
||||
<string name="decode_barcode">Barcode decoderen</string>
|
||||
<string name="content">Inhoud</string>
|
||||
<string name="binary_data">(binaire data)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%2$d character, %1$s</item>
|
||||
<item quantity="other">%2$d characters, %1$s</item>
|
||||
<item quantity="one">%2$d teken, %1$s</item>
|
||||
<item quantity="other">%2$d tekens, %1$s</item>
|
||||
</plurals>
|
||||
<string name="error_correction_level">Error correction level</string>
|
||||
<string name="issue_number">Issue number</string>
|
||||
<string name="orientation">Orientation</string>
|
||||
<string name="error_correction_level">Foutcorrectie niveau</string>
|
||||
<string name="error_correction_level_l" formatted="false">Laag (~7% correction)</string>
|
||||
<string name="error_correction_level_m" formatted="false">Middelhoog (~15% correction)</string>
|
||||
<string name="error_correction_level_q" formatted="false">Kwartiel (~25% correction)</string>
|
||||
<string name="error_correction_level_h" formatted="false">Hoog (~30% correction)</string>
|
||||
<string name="issue_number">Uitgiftenummer</string>
|
||||
<string name="orientation">Oriëntatie</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="toggle_flash">Flitslicht schakelen</string>
|
||||
<string name="possible_country">Mogelijk land van fabricage</string>
|
||||
<string name="suggested_price">Adviesprijs</string>
|
||||
<string name="upc_ean_extension">UPC EAN extensie</string>
|
||||
<string name="toggle_flash">Zaklamp aan/uit</string>
|
||||
<string name="error_flash">Error toggling flash</string>
|
||||
<string name="share">Delen</string>
|
||||
<string name="copy_to_clipboard">Kopiëren naar klembord</string>
|
||||
<string name="copied_to_clipboard">Kopieer de inhoud naar het klembord</string>
|
||||
<string name="copied_password_to_clipboard">Password copied to clipboard</string>
|
||||
<string name="share_as">Delen als?</string>
|
||||
<string name="copy_to_clipboard">Naar klembord kopiëren</string>
|
||||
<string name="copied_to_clipboard">Naar klembord gekopieerd</string>
|
||||
<string name="copy_password">Kopieer wachtwoord naar klembord</string>
|
||||
<string name="copied_password_to_clipboard">Wachtwoord gekopieerd naar klembord</string>
|
||||
<string name="open_url">URL openen</string>
|
||||
<string name="cannot_resolve_action">Er is geen app aangetroffen die dit kan openen</string>
|
||||
<string name="pick_search_engine">Een zoekmachine kiezen</string>
|
||||
<string name="format">Formaat</string>
|
||||
<string name="size">Grootte, in pixels</string>
|
||||
<string name="cannot_resolve_action">Geen enkele toepassing kan dat openen</string>
|
||||
<string name="pick_search_engine">Zoekmachine kiezen</string>
|
||||
<string name="format">Format</string>
|
||||
<string name="size">Afmeting in pixels</string>
|
||||
<string name="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Voer hier de inhoud in</string>
|
||||
<string name="encode">CODEREN</string>
|
||||
<string name="encode">Coderen</string>
|
||||
<string name="error_no_content">Inhoud ontbreekt</string>
|
||||
<string name="error_encoding_barcode">Barcode kan niet gegenereerd worden</string>
|
||||
<string name="error_encoding_barcode">Kan geen barcode aanmaken</string>
|
||||
<string name="view_barcode">Barcode bekijken</string>
|
||||
<string name="info">Informatie</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Camera wisselen</string>
|
||||
<string name="bulk_mode">Scan continuously</string>
|
||||
<string name="history">Geschiedenis</string>
|
||||
<string name="bulk_mode">Continu scannen</string>
|
||||
<string name="bulk_mode_summary">Begin altijd continu te scannen</string>
|
||||
<string name="bulk_mode_delay">Vertraging tussen ononderbroken 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">¼ seconde</string>
|
||||
<string name="half_second">½ seconde</string>
|
||||
<string name="a_second">1 seconde</string>
|
||||
<string name="two_seconds">2 seconden</string>
|
||||
<string name="five_seconds">5 seconden</string>
|
||||
<string name="history">Opgeslagen codes</string>
|
||||
<string name="preferences">Voorkeuren</string>
|
||||
<string name="show_crop_handle">Show cropping limiter</string>
|
||||
<string name="zoom_by_swiping">Zoom camera by swiping up/down</string>
|
||||
<string name="auto_rotate">Recognize 1D barcodes vertically</string>
|
||||
<string name="try_harder">Optimize reader for accuracy, not speed</string>
|
||||
<string name="vibrate">Vibrate on detection</string>
|
||||
<string name="use_history">Scan-geschiedenis behouden</string>
|
||||
<string name="ignore_consecutive_duplicates">Do not save consecutive duplicates</string>
|
||||
<string name="open_immediately">Skip inspection and open contents immediately</string>
|
||||
<string name="show_meta_data">Show metadata</string>
|
||||
<string name="show_hex_dump">Show hex dump</string>
|
||||
<string name="scan_category">Scannen</string>
|
||||
<string name="content_category">Inhoud</string>
|
||||
<string name="locale_category">Taal</string>
|
||||
<string name="custom_locale">Taal instelling</string>
|
||||
<string name="follow_system_settings">Systeeminstellingen volgen</string>
|
||||
<string name="show_crop_handle">Bijsnijd-begrenzer tonen</string>
|
||||
<string name="show_crop_handle_summary">Scannen beperken tot een aanpasbaar gebied</string>
|
||||
<string name="barcode_formats">Barcode formats</string>
|
||||
<string name="zoom_by_swiping">Camera zoomen door omhoog/omlaag te vegen</string>
|
||||
<string name="zoom_by_swiping_summary">De zoom van de camera bedienen door in het camerascherm omhoog of omlaag te vegen.</string>
|
||||
<string name="auto_rotate">1D-barcodes verticaal herkennen</string>
|
||||
<string name="auto_rotate_summary">Automatisch camerakader draaien om verticale 1D-barcodes te herkennen. Afhankelijk van het toestel beinvloedt dit de prestaties.</string>
|
||||
<string name="try_harder">Optimaliseer de scanner voor nauwkeurigheid</string>
|
||||
<string name="try_harder_summary">Activeer deze optie voor zeer moeilijk te lezen codes. Beïnvloedt de prestaties.</string>
|
||||
<string name="show_toast_in_bulk_mode">Data tonen in continue modus</string>
|
||||
<string name="show_toast_in_bulk_mode_summary">Kort de gescande gegevens tonen bij continu scannen.</string>
|
||||
<string name="vibrate">Trillen bij herkennen</string>
|
||||
<string name="vibrate_summary">Schakel dit in als u wilt dat uw toestel trilt wanneer een code wordt herkend.</string>
|
||||
<string name="use_history">Scancodes bewaren</string>
|
||||
<string name="use_history_summary">Bewaar herkende codes op je toestel.</string>
|
||||
<string name="ignore_consecutive_duplicates">Duplicaten negeren</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">Bewaar geen opeenvolgende duplicaten in de scangeschiedenis.</string>
|
||||
<string name="open_immediately">Inhoud direct tonen, zonder inspectie</string>
|
||||
<string name="open_immediately_summary">Sla inspectie over en open meteen de inhoud. Indien geactiveerd, kan schadelijke inhoud worden geopend.</string>
|
||||
<string name="copy_immediately">Meteen op klembord zetten</string>
|
||||
<string name="copy_immediately_summary">Kopieer de gescande inhoud automatisch naar het klembord. Let op: andere apps kunnen mogelijk meelezen op de achtergrond.</string>
|
||||
<string name="show_meta_data">Metadata tonen</string>
|
||||
<string name="show_meta_data_summary">Toon extra gegevens over de gescande barcode.</string>
|
||||
<string name="show_hex_dump">Hex-dump tonen</string>
|
||||
<string name="show_hex_dump_summary">Hex-dump van de gescande inhoud tonen.</string>
|
||||
<string name="close_automatically">Teruggaan na kopiëren/delen</string>
|
||||
<string name="close_automatically_summary">Automatisch terugkeren naar het scanscherm na het kopiëren of delen van de gelezen inhoud.</string>
|
||||
<string name="default_search_engine">Onbekende data openen in</string>
|
||||
<string name="always_ask">Altijd vragen</string>
|
||||
<string name="open_with_url">URL om niet bekende data te openen</string>
|
||||
<string name="clear_network_suggestions">Network suggesties wissen</string>
|
||||
<string name="clear_network_suggestions_summary">Verwijder alle netwerksuggesties die eerder door deze app werden gegeven.</string>
|
||||
<string name="really_remove_all_networks">Echt alle netwerken verwijderen?</string>
|
||||
<string name="clear_network_suggestions_success">Netwerksuggesties gewist</string>
|
||||
<string name="clear_network_suggestions_nothing_to_remove">Niets te wissen</string>
|
||||
<string name="send_category">Doorsturen</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">Stuur elke scan naar URL</string>
|
||||
<string name="send_scan_type">Type aanvraag voor elke scan</string>
|
||||
<string name="send_type_get_add_content">GET en gewoon inhoud toevoegen</string>
|
||||
<string name="send_type_get_query_string">GET met volledige query string</string>
|
||||
<string name="send_type_post_form">POST toepassing/x-www-form-urlencoded</string>
|
||||
<string name="send_type_post_json">POST toepassing/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">Scan echt verwijderen?</string>
|
||||
<string name="really_remove_all_scans">Echt alle scans verwijderen?</string>
|
||||
<string name="really_remove_selected_scans">Really remove selected scans?</string>
|
||||
<string name="clear_history">Geschiedenis wissen</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="really_remove_all_scans">Alle scans echt verwijderen?</string>
|
||||
<string name="really_remove_selected_scans">Geselecteerde scans verwijderen?</string>
|
||||
<string name="clear_history">Wis opgeslagen codes</string>
|
||||
<string name="copy_scan">Scan kopiëren</string>
|
||||
<string name="edit_scan">Label bewerken</string>
|
||||
<string name="remove_scan">Scan verwijderen</string>
|
||||
<string name="enter_name">Naam van de scan</string>
|
||||
<string name="enter_name_hint">Voer een naam in om de scan te beschrijven</string>
|
||||
<string name="no_barcode_found">Geen barcode gevonden</string>
|
||||
<string name="separator_line_break">Nieuwe regel</string>
|
||||
<string name="separator_ruler">Horizontale streep</string>
|
||||
<string name="save_as_file_name">Als bestand opslaan?</string>
|
||||
<string name="file_name">Naam van het bestand</string>
|
||||
<string name="pick_list_separator">Hoe lijstitems te scheiden?</string>
|
||||
<string name="separator_line_break">Regeleinde</string>
|
||||
<string name="separator_ruler">Koppelteken</string>
|
||||
<string name="save_as_file_name">Opslaan in Downloads als?</string>
|
||||
<string name="file_name">Bestandsnaam</string>
|
||||
<string name="error_saving_file">Kan bestand niet opslaan</string>
|
||||
<string name="error_file_exists">Bestand bestaat al</string>
|
||||
<string name="connect_to_wifi">Met WiFi verbinden</string>
|
||||
<string name="wifi_config_failed">Kon WiFi niet configureren</string>
|
||||
<string name="connect_to_wifi">Verbinden met WiFi</string>
|
||||
<string name="wifi_config_failed">Kan WiFi niet configureren</string>
|
||||
<string name="wifi_added">WiFi toegevoegd</string>
|
||||
<string name="sms_send">SMS versturen</string>
|
||||
<string name="sms_error">Kon SMS niet versturen</string>
|
||||
<string name="sms_send">SMS verzenden</string>
|
||||
<string name="sms_error">Kan geen SMS verzenden</string>
|
||||
<string name="tel_dial">Nummer bellen</string>
|
||||
<string name="tel_error">Kon nummer niet bellen</string>
|
||||
<string name="mail_send">Mail versturen</string>
|
||||
<string name="mail_error">Kon mail niet versturen</string>
|
||||
<string name="vcard_add">Aan contacten toevoegen</string>
|
||||
<string name="vcard_failed">Kon niet aan contacten toevoegen</string>
|
||||
<string name="vevent_add">Aan kalender toevoegen</string>
|
||||
<string name="tel_error">Kan geen nummer bellen</string>
|
||||
<string name="mail_send">E-mail zenden</string>
|
||||
<string name="mail_error">Kan geen e-mail zenden</string>
|
||||
<string name="vcard_add">Toevoegen aan contacten</string>
|
||||
<string name="vcard_failed">Kan niet aan contacten toevoegen</string>
|
||||
<string name="vevent_add">In kalender zetten</string>
|
||||
<string name="vevent_failed">Kan niet in kalender gezet</string>
|
||||
<string name="otpauth_add">2FA toevoegen</string>
|
||||
<string name="search_web">Het net doorzoeken</string>
|
||||
<string name="search_scan">Search scan</string>
|
||||
<string name="export_to_file">Naar bestand exporteren</string>
|
||||
<string name="export_png">Export as PNG</string>
|
||||
<string name="export_svg">Export as SVG</string>
|
||||
<string name="export_as">Export as?</string>
|
||||
<string name="export_csv_comma">CSV with commas</string>
|
||||
<string name="export_csv_semicolon">CSV with semicolons</string>
|
||||
<string name="export_json">Export as JSON</string>
|
||||
<string name="export_database">Export SQLite database</string>
|
||||
<string name="error_saving_file">Kon bestand niet opslaan</string>
|
||||
<string name="open_with_url">Niet herkende data met URL openen</string>
|
||||
<string name="pick_list_separator">Hoe de lijstonderdelen te separeren?</string>
|
||||
<string name="saved_in_downloads">In downloads opgeslagen</string>
|
||||
<string name="vevent_failed">Kon niet aan kalender toevoegen</string>
|
||||
<string name="rotate_image_cw">Rotate image clockwise</string>
|
||||
<string name="pick_file">Pick image file</string>
|
||||
<string name="pick_code_to_scan">Pick code to scan</string>
|
||||
<string name="shortcut_decode">Scan a barcode</string>
|
||||
<string name="shortcut_encode">Create a barcode</string>
|
||||
<string name="shortcut_preferences">Settings</string>
|
||||
<string name="search_web">Zoeken op internet</string>
|
||||
<string name="search_scan">Scan op internet zoeken</string>
|
||||
<string name="export_to_file">Uitvoer naar bestand</string>
|
||||
<string name="export_as">Exporteren als?</string>
|
||||
<string name="export_csv_comma">CSV bestand met komma\'s</string>
|
||||
<string name="export_csv_semicolon">CSV bestand met puntkomma\'s</string>
|
||||
<string name="export_json">JSON bestand</string>
|
||||
<string name="export_database">SQLite database</string>
|
||||
<string name="saved_in_downloads">Opgeslagen in downloads</string>
|
||||
<string name="rotate_image_cw">Afbeelding rechtsom draaien</string>
|
||||
<string name="pick_file">Beeldbestand kiezen</string>
|
||||
<string name="pick_code_to_scan">Kies code om te scannen</string>
|
||||
<string name="shortcut_decode">Een barcode scannen</string>
|
||||
<string name="shortcut_encode">Een barcode aanmaken</string>
|
||||
<string name="shortcut_preferences">Voorkeuren</string>
|
||||
<string name="background_request_failed">Achtergrondverzoek mislukt</string>
|
||||
<string name="entry_type">Type</string>
|
||||
<string name="wifi_network">WiFi netwerk</string>
|
||||
<string name="wifi_ssid">Naam</string>
|
||||
<string name="wifi_type">Authenticatie type</string>
|
||||
<string name="wifi_password">Wachtwoord</string>
|
||||
<string name="wifi_hidden">Verborgen netwerk</string>
|
||||
<string name="wifi_eap">EAP methode</string>
|
||||
<string name="wifi_anonymous_identity">Anonieme identiteit</string>
|
||||
<string name="wifi_identity">Identiteit</string>
|
||||
<string name="wifi_phase2">Phase 2 methode</string>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user