Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c63f5b3ac | ||
|
|
21985b4533 | ||
|
|
012ac644aa | ||
|
|
19f19739fd | ||
|
|
f01f9fcd91 | ||
|
|
4177b248c3 | ||
|
|
e4182a265b | ||
|
|
98df5a447c | ||
|
|
a0a1405359 | ||
|
|
76922c421c | ||
|
|
cac9dd16da | ||
|
|
a1f9ba1056 | ||
|
|
0b67c272b6 | ||
|
|
9b8be5305d | ||
|
|
a026ade13b | ||
|
|
b1771bc5ea | ||
|
|
5199460669 | ||
|
|
fd040e1660 | ||
|
|
2e1528364e | ||
|
|
d21af142bb | ||
|
|
8395e135f9 | ||
|
|
c0caa0ce44 | ||
|
|
96927ef9b2 | ||
|
|
84d9a3b55a | ||
|
|
ffe8e4c5ba | ||
|
|
bc7c5dca97 | ||
|
|
d9737f4df1 | ||
|
|
0e8147713a | ||
|
|
3d425a097a | ||
|
|
c26765cc63 | ||
|
|
fb6dc8ba55 | ||
|
|
70f4d166a0 | ||
|
|
6c136ce719 | ||
|
|
c6dee80c0a | ||
|
|
1f43dd4827 | ||
|
|
a366157977 | ||
|
|
6bb72672a3 | ||
|
|
04774527a0 | ||
|
|
6f3e644aa8 | ||
|
|
5e3176b703 | ||
|
|
0f0aaff284 | ||
|
|
f0d09e81b4 | ||
|
|
771258cb5c | ||
|
|
9482086666 | ||
|
|
bcad50c3e8 | ||
|
|
dbd055699e | ||
|
|
d67f8273d6 | ||
|
|
649be0ceb6 | ||
|
|
f5eb5dd8d2 | ||
|
|
eb805dad2f | ||
|
|
202bb32b29 | ||
|
|
50b8a4d329 | ||
|
|
fa15478fb4 | ||
|
|
20c2a5a8aa | ||
|
|
00b95678ed | ||
|
|
5df3517563 | ||
|
|
fd6946d2c2 | ||
|
|
c74d229e1f | ||
|
|
fa99a19db5 | ||
|
|
b083fcf213 | ||
|
|
46930ddea6 | ||
|
|
7bef77b96e | ||
|
|
c5f4c088b4 | ||
|
|
73f7210e18 | ||
|
|
469429e663 | ||
|
|
1a7ac59740 | ||
|
|
c13c59e728 | ||
|
|
bc17e25717 | ||
|
|
5017bd4c8d | ||
|
|
a44a913eca | ||
|
|
007e83e243 | ||
|
|
de8f97c246 | ||
|
|
1287591e53 | ||
|
|
8c17238572 | ||
|
|
5dbb36e6a9 | ||
|
|
ca7dfb3a33 | ||
|
|
419b7d5765 | ||
|
|
fa38866669 | ||
|
|
19ca521f59 | ||
|
|
be657d86cf | ||
|
|
5199a4a526 | ||
|
|
ff48a543c9 | ||
|
|
2eadd01a87 | ||
|
|
dd2abe7d89 | ||
|
|
8e05caac70 | ||
|
|
46aeced09f | ||
|
|
3681c5d011 | ||
|
|
20b4f7aac5 | ||
|
|
bc6e350271 | ||
|
|
c9c1b2f5bf | ||
|
|
1dbc0f0fa1 | ||
|
|
ab8170ee72 | ||
|
|
551464dc6f | ||
|
|
c3aac0a1e1 | ||
|
|
d678e1cfd0 | ||
|
|
a445ade877 | ||
|
|
1fd0b7637f | ||
|
|
fdc88df897 | ||
|
|
d0f133ae00 | ||
|
|
67646261e7 | ||
|
|
faeae9e904 | ||
|
|
77b21a9e83 | ||
|
|
cbe7860a3d | ||
|
|
fc29267d3d | ||
|
|
890506328b | ||
|
|
04036b6cd6 | ||
|
|
2602021c1c | ||
|
|
17a0bdc0a9 | ||
|
|
d71a220eca | ||
|
|
0c2e711d79 | ||
|
|
ca342a83d3 | ||
|
|
3c44ff56eb | ||
|
|
bcae64c0d0 | ||
|
|
c2f673729b | ||
|
|
2043e0f349 | ||
|
|
b93eb5963e | ||
|
|
5189fc9c5a | ||
|
|
2d311ff92a | ||
|
|
14bb08003d | ||
|
|
42f9887030 | ||
|
|
a59d241b50 | ||
|
|
9f31160c7b | ||
|
|
2eb4e51c17 | ||
|
|
53a554e7a2 | ||
|
|
7f6887716a | ||
|
|
5ef93802ce | ||
|
|
c1eff08bb9 | ||
|
|
8798d44ec7 | ||
|
|
d4e31ff97e | ||
|
|
ae8b08a85a | ||
|
|
09430d776e | ||
|
|
ef88c3179d | ||
|
|
c732ec726b | ||
|
|
801c768df4 | ||
|
|
552a2f6d57 | ||
|
|
7fa8810197 | ||
|
|
254af25bbb | ||
|
|
a27656c174 | ||
|
|
2ca55ebccc | ||
|
|
ec1d40942d | ||
|
|
7852e5ce9d | ||
|
|
f36a81d4a0 | ||
|
|
d86ea4073c | ||
|
|
a69530621d | ||
|
|
3c0f29a768 | ||
|
|
ccf5157fa7 | ||
|
|
c1ebb1c154 | ||
|
|
a4778bcc02 | ||
|
|
24d2c6d8d6 | ||
|
|
0f3a07f602 | ||
|
|
751d8932f8 | ||
|
|
9c2d063c24 | ||
|
|
0f9bbf5b2f | ||
|
|
530280981e | ||
|
|
ddfbb305c7 | ||
|
|
8d1a29922b | ||
|
|
c710f7c6ab | ||
|
|
111c587eef | ||
|
|
264e494487 | ||
|
|
8135b2a3dc | ||
|
|
dce62b70d3 | ||
|
|
533812c638 | ||
|
|
5dc5bed281 | ||
|
|
2a13561962 | ||
|
|
1476f41cda | ||
|
|
0da1ffbe02 | ||
|
|
980679b6a3 | ||
|
|
28cbb55299 | ||
|
|
1a4e7d100a | ||
|
|
265e43b439 | ||
|
|
03a58c3412 | ||
|
|
327cad48f0 | ||
|
|
8e64fe2f56 | ||
|
|
368d4dbb66 | ||
|
|
c8c3c094db | ||
|
|
aa4aefe35f | ||
|
|
536b3d77c1 | ||
|
|
bbf2a80981 | ||
|
|
ae99214d6c | ||
|
|
73b5a0969c | ||
|
|
bf5dc96ac9 | ||
|
|
301d9c2513 | ||
|
|
0a11611b8f | ||
|
|
249e310e43 | ||
|
|
6279c5731a | ||
|
|
df98637951 | ||
|
|
08b3558e46 | ||
|
|
648ef6fdf8 | ||
|
|
807f075a51 | ||
|
|
27789beeed | ||
|
|
97bee27827 | ||
|
|
3fee49301e | ||
|
|
8a6073291b | ||
|
|
47151de4a4 | ||
|
|
248c4edca1 | ||
|
|
2c7386d536 | ||
|
|
e19cad4c87 | ||
|
|
585e0f3372 | ||
|
|
2b7f611774 | ||
|
|
05b2a0d9c9 | ||
|
|
e7287eb088 | ||
|
|
4b45ad3fe7 | ||
|
|
449d8fdbbd | ||
|
|
f44cff9912 | ||
|
|
24730dfde3 | ||
|
|
be6539d41f | ||
|
|
60dd40554e | ||
|
|
837119b6ec | ||
|
|
abe11b82e5 | ||
|
|
4fc1e36d04 | ||
|
|
54aa15e1be | ||
|
|
876d8fc7b3 | ||
|
|
7bdcdaaa5d | ||
|
|
fd44728e89 | ||
|
|
8242520a0b | ||
|
|
2e7e9a942b | ||
|
|
dcc9d5b3ff | ||
|
|
607aab854c | ||
|
|
cc2b2f076b | ||
|
|
90e9723de5 | ||
|
|
ab6787e60b | ||
|
|
698686a45a | ||
|
|
95bdaa1252 | ||
|
|
f0c02e5156 | ||
|
|
a8c50308d0 | ||
|
|
6b359d4c65 | ||
|
|
863caed387 | ||
|
|
e4f17b9bdf | ||
|
|
c2071d8d81 | ||
|
|
4758c2d337 | ||
|
|
91336fe15c | ||
|
|
2c437f3771 | ||
|
|
cb082b20fe | ||
|
|
bce3630686 | ||
|
|
4feb4d066c | ||
|
|
e7d84cd916 | ||
|
|
84e2917694 | ||
|
|
b87464ec40 | ||
|
|
269e0f41a6 | ||
|
|
aefbc4a296 | ||
|
|
c64984dc6a | ||
|
|
24c7ad0ba4 | ||
|
|
643a935852 | ||
|
|
4fcb836b53 | ||
|
|
48ed5f1a2c | ||
|
|
3aa2371eeb | ||
|
|
88f92943c0 | ||
|
|
39710d4ab9 | ||
|
|
93f7cd3cb8 | ||
|
|
69baa3b824 | ||
|
|
bb05e38278 | ||
|
|
554f5a5e73 | ||
|
|
2dcf7772f2 | ||
|
|
3c7012e3c4 | ||
|
|
08e11c3c40 | ||
|
|
31854dab0c | ||
|
|
4f12c3e270 | ||
|
|
3ec43dabce | ||
|
|
0d4f536f71 | ||
|
|
b064a293fb | ||
|
|
d124c29919 | ||
|
|
2e89f1530a | ||
|
|
ef7e2a4b0d | ||
|
|
f680533819 | ||
|
|
a8c977c40e | ||
|
|
1e89b22953 | ||
|
|
11f028e0f3 | ||
|
|
1f7f4a23b5 | ||
|
|
41581d07ec | ||
|
|
dc819a6e1a | ||
|
|
5b51072f1a | ||
|
|
1b10e82cb2 | ||
|
|
eeae1eb0ad | ||
|
|
21c29ca373 | ||
|
|
8bcb72e91b | ||
|
|
8f345fbfbe | ||
|
|
2c88dbca02 | ||
|
|
bd1c629c85 | ||
|
|
f89e6612c4 | ||
|
|
7a0cd0ce19 | ||
|
|
8544affa1e | ||
|
|
48d3588341 | ||
|
|
783f6e4892 | ||
|
|
032cd64daf | ||
|
|
96626e6b00 | ||
|
|
11ae65ec3e | ||
|
|
91e3acb647 | ||
|
|
40c2f5947b | ||
|
|
58b9466cfc | ||
|
|
12210e1768 | ||
|
|
ad2ac457c9 | ||
|
|
634b6e8d2b | ||
|
|
6dd47f4dd0 | ||
|
|
8426f66cfb | ||
|
|
3dfa84a33e | ||
|
|
38f658e253 | ||
|
|
287d99e2b4 | ||
|
|
5cfcde9d4a | ||
|
|
b2730259fc | ||
|
|
61a2d3023c | ||
|
|
239ac7bd1a |
+144
@@ -1,5 +1,149 @@
|
||||
# Change Log
|
||||
|
||||
## 1.68.0
|
||||
* Add automated actions
|
||||
* Improve managing profiles
|
||||
|
||||
## 1.67.1
|
||||
* Fix opening file:// Uris
|
||||
* Fix auto-return/immediate opening
|
||||
* Improve Bluetooth stability
|
||||
|
||||
## 1.67.0
|
||||
* Add preference profiles
|
||||
* Support regex when searching for a barcode
|
||||
* Improve feedback when searching for a barcode
|
||||
* Update translations
|
||||
|
||||
## 1.66.2
|
||||
* Remove forcing `?content=` on forwarding URL
|
||||
* Update Spanish translation
|
||||
|
||||
## 1.66.1
|
||||
* Show elements of invalid Wifi configurations too
|
||||
* Update translations
|
||||
|
||||
## 1.66.0
|
||||
* Support deep links to encode content via web links
|
||||
* Parse International Driver Licenses (IDL) in PDF417 barcodes
|
||||
* Add WiFis from system settings
|
||||
* Show parsed data items
|
||||
* Update translations
|
||||
|
||||
## 1.65.2
|
||||
* Sanity check ROI metrics
|
||||
|
||||
## 1.65.1
|
||||
* Improve simple mode
|
||||
* Remember quiet zone settings
|
||||
* Reduce height for 1D barcodes
|
||||
* Add execute argument for binaryeye://encode
|
||||
* Update Russian translation
|
||||
|
||||
## 1.65.0
|
||||
* Add a welcome screen to choose a mode
|
||||
* Add a deep link to encode a barcode
|
||||
* Remember zoom level of generated barcodes
|
||||
* Remove Human Readable Interpretation (HRI) decoration
|
||||
* Update Italian translation
|
||||
|
||||
## 1.64.2
|
||||
* Separate crop window settings for image scanning
|
||||
* Fix initializing dataMask
|
||||
* Update Russian translation
|
||||
|
||||
## 1.64.1
|
||||
* Handle empty input for recreation
|
||||
* Fix setting up database
|
||||
|
||||
## 1.64.0
|
||||
* Find barcode by content
|
||||
* Immediately replace toasts
|
||||
* Use detected barcode for recreation
|
||||
* Show data mask of QR Codes
|
||||
* Fix deep linking
|
||||
* Replace margin with quiet zone switch when generating barcodes
|
||||
* Update Russian translation
|
||||
* Update Portuguese (Brazil) translation
|
||||
|
||||
## 1.63.13
|
||||
* Fix UTF-8 conversion of binary escape sequences when creating barcodes
|
||||
* Fix scan order for intervals under one second
|
||||
* Add arabic translation
|
||||
* Update French translation
|
||||
|
||||
## 1.63.12
|
||||
* Fix magnifying barcode recreation
|
||||
|
||||
## 1.63.11
|
||||
* Update parcel tracking URL
|
||||
* Update multiple translations
|
||||
* Only ask for barcode size when exporting/sharing
|
||||
|
||||
## 1.63.10
|
||||
* Use maximum brightness when showing a generated barcode
|
||||
* Allow file input from share menu
|
||||
* Update multiple translations
|
||||
|
||||
## 1.63.9
|
||||
* Export/share as JPEG
|
||||
* Ignore "URL:" before URLs in barcode content
|
||||
* Fix exporting/sharing multiple history items
|
||||
* Update multiple translations
|
||||
|
||||
## 1.63.8
|
||||
* Add an option to show a checksum for the decoded content
|
||||
* Add support for encoding escape sequences for non-printable characters
|
||||
* Update Italian translation
|
||||
|
||||
## 1.63.7
|
||||
* Update ZXingCpp for Aztec Runes
|
||||
* Update Italian translation
|
||||
|
||||
## 1.63.6
|
||||
* Add history shortcut
|
||||
* Return Codabar start/stop codes
|
||||
* Update French, Russian, Spanish and Ukrainian translations
|
||||
|
||||
## 1.63.5
|
||||
* Add an option to ignore any duplicates
|
||||
* Fix invalid bluetooth connection
|
||||
* Update Russian translation
|
||||
* Update Ukrainian translation
|
||||
|
||||
## 1.63.4
|
||||
* Update Chinese translation
|
||||
* Improve support for old devices
|
||||
|
||||
## 1.63.3
|
||||
* Fix SCAN Intent
|
||||
|
||||
## 1.63.2
|
||||
* Add DX Film Edge barcode format
|
||||
* Update Turkish translation
|
||||
* Update Ukrainian translation
|
||||
|
||||
## 1.63.1
|
||||
* Include scan label in exports/shares
|
||||
* Update Russian translation
|
||||
|
||||
## 1.63.0
|
||||
* Edit scan label in decoding screen
|
||||
* Add a button to add generated codes to the history
|
||||
* Improve scanning from images
|
||||
* Improve asking for camera permission
|
||||
* Fix margin for recreated barcodes
|
||||
* Limit duration of error beep to 1s
|
||||
* Update Ukrainian translation
|
||||
* Update Spanish translation
|
||||
|
||||
## 1.62.3
|
||||
* Extend selection with single taps in history listing
|
||||
* Update Brazilian Portuguese translation
|
||||
|
||||
## 1.62.2
|
||||
* Include rMQR Code for updates
|
||||
|
||||
## 1.62.1
|
||||
* Make rMQR Code selectable
|
||||
|
||||
|
||||
@@ -16,5 +16,10 @@ to squash when merging into master:
|
||||
Then write a [good commit message][commit_messages] to keep the history
|
||||
meaningful and useful. One feature, one commit.
|
||||
|
||||
## Translations
|
||||
|
||||
For translations use [Toolate][toolate].
|
||||
|
||||
[android_best_practices]: https://developer.android.com/distribute/best-practices/develop/
|
||||
[commit_messages]: https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines
|
||||
[toolate]: https://toolate.othing.xyz/projects/binaryeye/
|
||||
|
||||
@@ -28,6 +28,26 @@ testscan:
|
||||
-c android.intent.category.BROWSABLE \
|
||||
-d 'binaryeye://scan/?ret=http%3A%2F%2Fmarkusfisch.de%2F%3Fresult%3D{RESULT}'
|
||||
|
||||
testencode:
|
||||
adb shell am start -a android.intent.action.VIEW \
|
||||
-c android.intent.category.BROWSABLE \
|
||||
-d 'binaryeye://encode?content=Test\&format=QR_CODE'
|
||||
|
||||
testencodenotqr:
|
||||
adb shell am start -a android.intent.action.VIEW \
|
||||
-c android.intent.category.BROWSABLE \
|
||||
-d 'binaryeye://encode?content=Test\&format=DATA_MATRIX'
|
||||
|
||||
testencodeexec:
|
||||
adb shell am start -a android.intent.action.VIEW \
|
||||
-c android.intent.category.BROWSABLE \
|
||||
-d 'binaryeye://encode?content=Test\&format=QR_CODE\&execute'
|
||||
|
||||
testencodeurl:
|
||||
adb shell am start -a android.intent.action.VIEW \
|
||||
-c android.intent.category.BROWSABLE \
|
||||
-d 'http://markusfisch.de/encode?content=Test\&format=QR_CODE'
|
||||
|
||||
testurl:
|
||||
adb shell am start -a android.intent.action.VIEW \
|
||||
-c android.intent.category.BROWSABLE \
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ Before Android Q, this feature requires using
|
||||
[WifiManager.getConfiguredNetworks][getConfiguredNetworks]
|
||||
which requires `ACCESS_FINE_LOCATION`.
|
||||
|
||||
On Android Q an better, this permission is not requested nor required.
|
||||
On Android Q and better, this permission is not requested nor required.
|
||||
|
||||
### [BLUETOOTH][BLUETOOTH], [BLUETOOTH_ADMIN][BLUETOOTH_ADMIN] and [BLUETOOTH_CONNECT][BLUETOOTH_CONNECT]
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Yet another barcode scanner for Android. As if there weren't [enough][play].
|
||||
|
||||
This one is free, without any ads and open source.
|
||||
This one is free, without any ads, and open source.
|
||||
|
||||
Works in portrait and landscape orientation, can read inverted codes,
|
||||
comes in Material Design and can also generate barcodes.
|
||||
@@ -10,6 +10,23 @@ comes in Material Design and can also generate barcodes.
|
||||
Binary Eye uses the [ZXing-C++][zxing_cpp] ("Zebra Crossing") barcode
|
||||
scanning library.
|
||||
|
||||
If you find this app useful and wish to support its continued development,
|
||||
you can [buy me a coffee](https://www.buymeacoffee.com/markusfisch) or
|
||||
send some Bitcoin decimals to `bc1q2guk2rpll587aymrfadkdtpq32448x5khk5j8z`.
|
||||
|
||||
<a href="https://www.buymeacoffee.com/markusfisch" target="_blank"><img
|
||||
src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png"
|
||||
alt="Buy Me A Coffee"
|
||||
style="height: 60px !important;width: 217px !important;"/></a> <a
|
||||
href="https://liberapay.com/markusfisch/" target="_blank"><img
|
||||
src="https://liberapay.com/assets/widgets/donate.svg"
|
||||
alt="Liberapay"
|
||||
style="height: 60px !important"/></a> <a
|
||||
href="https://ko-fi.com/markusfisch" target="_blank"><img
|
||||
src="https://storage.ko-fi.com/cdn/brandasset/kofi_s_tag_dark.png"
|
||||
alt="Ko-fi"
|
||||
style="height: 60px !important"/></a>
|
||||
|
||||
## Screenshots
|
||||
|
||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screencap-scanning.png"
|
||||
@@ -36,6 +53,7 @@ ZXing can read the following barcode formats:
|
||||
* [CODE 93][code_93]
|
||||
* [CODE 128][code_128]
|
||||
* [DATA MATRIX][data_matrix]
|
||||
* [DX FILM EDGE][dx_film_edge]
|
||||
* [EAN 8][ean_8]
|
||||
* [EAN 13][ean_13]
|
||||
* [ITF][itf]
|
||||
@@ -65,14 +83,14 @@ ZXing can generate the following barcode formats:
|
||||
* [QR CODE][qr_code]
|
||||
* [UPC A][upc_a]
|
||||
|
||||
## Deep Links and Intents
|
||||
|
||||
### Deep Links
|
||||
## Deep Links
|
||||
|
||||
You can invoke Binary Eye with a web URI intent from anything that can
|
||||
open URIs. There are two options:
|
||||
open URIs.
|
||||
|
||||
1. [binaryeye://scan](binaryeye://scan)
|
||||
### Decoding
|
||||
|
||||
1. [binaryeye://scan](binaryeye://scan) (note: GitHub does not render this as clickable link)
|
||||
2. [http(s)://markusfisch.de/BinaryEye](http://markusfisch.de/BinaryEye)
|
||||
|
||||
If you want to get the scanned contents, you can add a `ret` query
|
||||
@@ -86,10 +104,32 @@ Supported symbols are:
|
||||
* `RESULT_BYTES` - raw result as a hex string
|
||||
* `FORMAT` - barcode format
|
||||
|
||||
### Encoding
|
||||
|
||||
1. [binaryeye://encode](binaryeye://encode)
|
||||
|
||||
You can use the URL arguments `content` and `format` to automatically
|
||||
preset this data. For example:
|
||||
|
||||
1. [binaryeye://encode?content=Test&format=QR_CODE](binaryeye://encode?content=Test&format=QR_CODE)
|
||||
2. [http(s)://markusfisch.de/encode?content=Test&format=QR_CODE](http://markusfisch.de/encode?content=Test&format=QR_CODE)
|
||||
3. [http(s)://markusfisch.de/encode?content=Test&format=DATA_MATRIX](http://markusfisch.de/encode?content=Test2&format=DATA_MATRIX&execute)
|
||||
|
||||
If you want the code to be generated immediately, add `execute=true` (or
|
||||
just `execute`).
|
||||
|
||||
## Intents
|
||||
|
||||
You can also use Binary Eye from other apps by using an [Intent][intent].
|
||||
|
||||
If you prefer to integrate a barcode scanner into your app, take a look at
|
||||
[BarcodeScannerView][barcode_scanner_view] (if you also want to use
|
||||
[ZXing-C++][zxing_cpp]) or read how to
|
||||
[scan barcodes with ML Kit on Android][mlkit_barcode_scanning].
|
||||
|
||||
### SCAN Intent
|
||||
|
||||
You can also use Binary Eye from other apps by using the
|
||||
`com.google.zxing.client.android.SCAN` Intent with
|
||||
Use the `com.google.zxing.client.android.SCAN` `Intent` with
|
||||
[startActivityForResult()][start_activity] like this:
|
||||
|
||||
```kotlin
|
||||
@@ -121,10 +161,12 @@ If you're using AndroidX, this would be the new,
|
||||
[recommended way][intent_result]:
|
||||
|
||||
```kotlin
|
||||
class YourActivity : Activity() {
|
||||
private val resultLauncher = registerForActivityResult(StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val scan = result.data?.getStringExtra("SCAN_RESULT")
|
||||
class YourActivity : AppCompatActivity() {
|
||||
private val resultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val content = result.data?.getStringExtra("SCAN_RESULT")
|
||||
…
|
||||
}
|
||||
}
|
||||
@@ -135,6 +177,21 @@ class YourActivity : Activity() {
|
||||
}
|
||||
```
|
||||
|
||||
## Forwarding scans via Bluetooth
|
||||
|
||||
**Note:** The companion apps aren't developed nor maintained by the
|
||||
author of Binary Eye.
|
||||
|
||||
In order to set up Bluetooth forwarding to a computer:
|
||||
|
||||
1. Pair your devices (phone and pc) if not paired already
|
||||
2. Download and run a companion app:
|
||||
- Windows: https://github.com/KamaleiZestri/BinaryReceptorWindows
|
||||
- Linux: https://github.com/sean666888/bin_eye_bt_receiver
|
||||
3. In the settings of Binary Eye, enable "Forward scans with Bluetooth"
|
||||
and select the appropriate target host device that is running the
|
||||
companion application.
|
||||
|
||||
[play]: https://play.google.com/store/search?q=barcode%20scanner&c=apps
|
||||
[zxing_cpp]: https://github.com/zxing-cpp/zxing-cpp
|
||||
[kotlin]: http://kotlinlang.org/
|
||||
@@ -144,6 +201,7 @@ class YourActivity : Activity() {
|
||||
[code_93]: https://en.wikipedia.org/wiki/Code_93
|
||||
[code_128]: https://en.wikipedia.org/wiki/Code_128
|
||||
[data_matrix]: https://en.wikipedia.org/wiki/Data_Matrix
|
||||
[dx_film_edge]: https://en.wikipedia.org/wiki/DX_encoding
|
||||
[ean_8]: https://en.wikipedia.org/wiki/EAN-8
|
||||
[ean_13]: https://en.wikipedia.org/wiki/International_Article_Number
|
||||
[itf]: https://en.wikipedia.org/wiki/Interleaved_2_of_5
|
||||
@@ -156,6 +214,9 @@ class YourActivity : Activity() {
|
||||
[upc_a]: https://en.wikipedia.org/wiki/Universal_Product_Code
|
||||
[upc_e]: https://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E
|
||||
[upc_ean]: https://en.wikipedia.org/wiki/Universal_Product_Code#EAN-13
|
||||
[intent]: https://developer.android.com/reference/android/content/Intent
|
||||
[barcode_scanner_view]: https://github.com/markusfisch/BarcodeScannerView
|
||||
[mlkit_barcode_scanning]: https://developers.google.com/ml-kit/vision/barcode-scanning/android
|
||||
[start_activity]: https://developer.android.com/reference/android/app/Activity#startActivityForResult(android.content.Intent,%20int)
|
||||
[on_activity_result]: https://developer.android.com/reference/android/app/Activity#onActivityResult(int,%20int,%20android.content.Intent)
|
||||
[intent_result]: https://developer.android.com/training/basics/intents/result
|
||||
|
||||
+28
-18
@@ -1,15 +1,15 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
namespace 'de.markusfisch.android.binaryeye'
|
||||
compileSdkVersion sdk_version
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion sdk_version
|
||||
namespace "de.markusfisch.android.binaryeye"
|
||||
compileSdk sdk_version
|
||||
|
||||
versionCode 125
|
||||
versionName '1.62.1'
|
||||
defaultConfig {
|
||||
minSdk 9
|
||||
targetSdk sdk_version
|
||||
|
||||
versionCode 154
|
||||
versionName '1.68.0'
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -34,13 +34,17 @@ android {
|
||||
}
|
||||
|
||||
release {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
bundle {
|
||||
language {
|
||||
// To make the app bundle contain all language resources
|
||||
@@ -53,24 +57,30 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Required since Gradle 8.
|
||||
compileOptions {
|
||||
// Required for Gradle 8.
|
||||
sourceCompatibility = 17
|
||||
targetCompatibility = 17
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
|
||||
// Kotlin
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2"
|
||||
|
||||
// Support
|
||||
implementation "com.android.support:appcompat-v7:$support_version"
|
||||
implementation "com.android.support:design:$support_version"
|
||||
implementation "com.android.support:preference-v7:$support_version"
|
||||
implementation "com.android.support:preference-v14:$support_version"
|
||||
implementation 'com.github.markusfisch:CameraView:1.9.2'
|
||||
|
||||
// External
|
||||
implementation 'com.github.markusfisch:CameraView:1.10.0'
|
||||
implementation 'com.github.markusfisch:ScalingImageView:1.4.1'
|
||||
implementation 'com.github.markusfisch:zxing-cpp:v2.2.0.1'
|
||||
implementation 'com.github.markusfisch:zxing-cpp:v2.3.0.4'
|
||||
|
||||
// Testing
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.16.1'
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-feature android:name="android.hardware.camera"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
<uses-sdk tools:overrideLibrary="android.support.v14.preference"/>
|
||||
<queries>
|
||||
<intent>
|
||||
@@ -62,7 +63,7 @@
|
||||
<intent-filter android:label="@string/compose_barcode">
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
<data android:mimeType="text/*"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.google.zxing.client.android.SCAN"/>
|
||||
@@ -75,6 +76,15 @@
|
||||
<data android:host="details" android:scheme="market"
|
||||
android:pathSuffix="id=com.xiaomi.scanner"/>
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<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"/>
|
||||
@@ -99,14 +109,34 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:name=".activity.WelcomeActivity"
|
||||
android:exported="true"/>
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:host="markusfisch.de"/>
|
||||
<data android:pathPrefix="/encode"/>
|
||||
<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="encode"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<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">
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
tools:targetApi="n">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE"/>
|
||||
</intent-filter>
|
||||
|
||||
+11
-9
@@ -6,15 +6,17 @@ import de.markusfisch.android.binaryeye.content.execShareIntent
|
||||
import de.markusfisch.android.binaryeye.content.openUrl
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
|
||||
interface IAction {
|
||||
val iconResId: Int
|
||||
val titleResId: Int
|
||||
abstract class Action {
|
||||
abstract val iconResId: Int
|
||||
abstract val titleResId: Int
|
||||
|
||||
fun canExecuteOn(data: ByteArray): Boolean
|
||||
suspend fun execute(context: Context, data: ByteArray)
|
||||
var fired: Boolean = false
|
||||
|
||||
abstract fun canExecuteOn(data: ByteArray): Boolean
|
||||
abstract suspend fun execute(context: Context, data: ByteArray)
|
||||
}
|
||||
|
||||
abstract class IntentAction : IAction {
|
||||
abstract class IntentAction : Action() {
|
||||
abstract val errorMsg: Int
|
||||
|
||||
final override suspend fun execute(context: Context, data: ByteArray) {
|
||||
@@ -22,7 +24,7 @@ abstract class IntentAction : IAction {
|
||||
if (intent == null) {
|
||||
context.toast(errorMsg)
|
||||
} else {
|
||||
context.execShareIntent(intent)
|
||||
fired = context.execShareIntent(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +34,7 @@ abstract class IntentAction : IAction {
|
||||
): Intent?
|
||||
}
|
||||
|
||||
abstract class SchemeAction : IAction {
|
||||
abstract class SchemeAction : Action() {
|
||||
abstract val scheme: String
|
||||
open val buildRegex: Boolean = false
|
||||
|
||||
@@ -50,6 +52,6 @@ abstract class SchemeAction : IAction {
|
||||
}
|
||||
|
||||
final override suspend fun execute(context: Context, data: ByteArray) {
|
||||
context.openUrl(String(data))
|
||||
fired = context.openUrl(String(data))
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@ import de.markusfisch.android.binaryeye.actions.web.WebAction
|
||||
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
|
||||
|
||||
object ActionRegistry {
|
||||
val DEFAULT_ACTION: IAction = OpenOrSearchAction
|
||||
val DEFAULT_ACTION: Action = OpenOrSearchAction
|
||||
|
||||
private val REGISTRY: Set<IAction> = setOf(
|
||||
private val REGISTRY: Set<Action> = setOf(
|
||||
MailAction,
|
||||
MatMsgAction,
|
||||
OtpauthAction,
|
||||
@@ -28,7 +28,7 @@ object ActionRegistry {
|
||||
WebAction
|
||||
)
|
||||
|
||||
fun getAction(data: ByteArray): IAction = REGISTRY.find {
|
||||
fun getAction(data: ByteArray): Action = REGISTRY.find {
|
||||
it.canExecuteOn(data)
|
||||
} ?: DEFAULT_ACTION
|
||||
}
|
||||
|
||||
+49
-46
@@ -2,62 +2,65 @@ package de.markusfisch.android.binaryeye.actions.search
|
||||
|
||||
import android.content.Context
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IAction
|
||||
import de.markusfisch.android.binaryeye.actions.Action
|
||||
import de.markusfisch.android.binaryeye.app.alertDialog
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.content.openUrl
|
||||
import de.markusfisch.android.binaryeye.net.urlEncode
|
||||
|
||||
object OpenOrSearchAction : IAction {
|
||||
object OpenOrSearchAction : Action() {
|
||||
override val iconResId: Int = R.drawable.ic_action_search
|
||||
override val titleResId: Int = R.string.search_web
|
||||
|
||||
override fun canExecuteOn(data: ByteArray): Boolean = false
|
||||
|
||||
override suspend fun execute(context: Context, data: ByteArray) {
|
||||
view(context, String(data), true)
|
||||
}
|
||||
|
||||
private suspend fun view(
|
||||
context: Context,
|
||||
url: String,
|
||||
search: Boolean
|
||||
) {
|
||||
if (!context.openUrl(url, silent = true) && search) {
|
||||
openSearch(context, url)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
val queryUri = alertDialog<String>(context) { resume ->
|
||||
setTitle(R.string.pick_search_engine)
|
||||
setItems(names.toTypedArray()) { _, which ->
|
||||
resume(urls[which] + query.urlEncode())
|
||||
}
|
||||
} ?: return
|
||||
view(context, queryUri, false)
|
||||
fired = openUrlOrSearch(context, String(data))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun openUrlOrSearch(
|
||||
context: Context,
|
||||
url: String,
|
||||
search: Boolean = true
|
||||
): Boolean {
|
||||
if (context.openUrl(url, silent = true)) {
|
||||
return true
|
||||
}
|
||||
if (search) {
|
||||
return prependSearchUrl(context, url)?.let {
|
||||
openUrlOrSearch(context, it, false)
|
||||
} ?: false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private suspend fun prependSearchUrl(
|
||||
context: Context,
|
||||
query: String
|
||||
): String? {
|
||||
val defaultSearchUrl = prefs.defaultSearchUrl
|
||||
if (defaultSearchUrl.isNotEmpty()) {
|
||||
return defaultSearchUrl + query.urlEncode()
|
||||
}
|
||||
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.removeAt(0)
|
||||
urls.removeAt(0)
|
||||
if (prefs.openWithUrl.isNotEmpty()) {
|
||||
names.add(prefs.openWithUrl)
|
||||
urls.add(prefs.openWithUrl)
|
||||
}
|
||||
return alertDialog(context) { resume ->
|
||||
setTitle(R.string.pick_search_engine)
|
||||
setItems(names.toTypedArray()) { _, which ->
|
||||
resume(urls[which] + query.urlEncode())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -9,7 +9,8 @@ import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IntentAction
|
||||
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
object VEventAction : IntentAction() {
|
||||
override val iconResId: Int
|
||||
@@ -87,6 +88,6 @@ private fun List<String>.simpleFindParse(date: String): Date? {
|
||||
|
||||
private fun SimpleDateFormat.simpleParse(date: String): Date? = try {
|
||||
parse(date)
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package de.markusfisch.android.binaryeye.actions.web
|
||||
|
||||
import android.content.Context
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IAction
|
||||
import de.markusfisch.android.binaryeye.actions.Action
|
||||
import de.markusfisch.android.binaryeye.content.openUrl
|
||||
|
||||
object WebAction : IAction {
|
||||
object WebAction : Action() {
|
||||
private val colloquialRegex =
|
||||
"^(http[s]*://)*[A-Za-z0-9]{3,}\\.[A-Za-z]{2,}[^ \t\r\n]*$".toRegex()
|
||||
"^(URL:[ ]*)*(http[s]*://)*[A-Za-z0-9-]{3,}\\.[A-Za-z]{2,}[^ \t\r\n]*$".toRegex()
|
||||
|
||||
override val iconResId: Int = R.drawable.ic_action_open
|
||||
override val titleResId: Int = R.string.open_url
|
||||
@@ -18,9 +18,12 @@ object WebAction : IAction {
|
||||
|
||||
override suspend fun execute(context: Context, data: ByteArray) {
|
||||
var url = String(data).trim()
|
||||
if (url.startsWith("URL:")) {
|
||||
url = url.substring(4).trim()
|
||||
}
|
||||
if (!url.startsWith("http") && !url.startsWith("ftp")) {
|
||||
url = "http://${url}"
|
||||
}
|
||||
context.openUrl(url)
|
||||
fired = context.openUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,31 +2,39 @@ package de.markusfisch.android.binaryeye.actions.wifi
|
||||
|
||||
import android.content.Context
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IAction
|
||||
import de.markusfisch.android.binaryeye.actions.Action
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object WifiAction : IAction {
|
||||
object WifiAction : Action() {
|
||||
override val iconResId = R.drawable.ic_action_wifi
|
||||
override val titleResId = R.string.connect_to_wifi
|
||||
|
||||
var password: String? = null
|
||||
|
||||
override fun canExecuteOn(data: ByteArray): Boolean =
|
||||
WifiConnector.parse(String(data)) {
|
||||
password = it
|
||||
} != null
|
||||
override fun canExecuteOn(data: ByteArray): Boolean {
|
||||
// Just check if the data can be parsed as the app should also
|
||||
// show invalid configurations so users can still access them.
|
||||
val inputMap = WifiConnector.parseMap(String(data)) ?: return false
|
||||
password = inputMap["P"]
|
||||
return !inputMap["S"].isNullOrEmpty()
|
||||
}
|
||||
|
||||
override suspend fun execute(
|
||||
context: Context,
|
||||
data: ByteArray
|
||||
) = withContext(Dispatchers.IO) {
|
||||
val message = WifiConnector.parse(String(data))?.let {
|
||||
WifiConnector.addNetwork(context, it)
|
||||
} ?: R.string.wifi_config_failed
|
||||
withContext(Dispatchers.Main) {
|
||||
context.toast(message)
|
||||
override suspend fun execute(context: Context, data: ByteArray) {
|
||||
withContext(Dispatchers.IO) {
|
||||
fired = WifiConnector.parse(String(data))?.let {
|
||||
WifiConnector.addNetwork(context, it)
|
||||
} ?: false
|
||||
withContext(Dispatchers.Main) {
|
||||
context.toast(
|
||||
if (fired) {
|
||||
R.string.wifi_added
|
||||
} else {
|
||||
R.string.wifi_config_failed
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+44
-32
@@ -1,14 +1,15 @@
|
||||
package de.markusfisch.android.binaryeye.actions.wifi
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import android.net.wifi.WifiEnterpriseConfig
|
||||
import android.net.wifi.WifiManager
|
||||
import android.net.wifi.WifiNetworkSuggestion
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.support.annotation.RequiresApi
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Normal:
|
||||
@@ -35,15 +36,9 @@ import java.util.*
|
||||
* The fields can appear in any order. Only "S:" is required.
|
||||
*/
|
||||
object WifiConnector {
|
||||
fun parse(
|
||||
input: String,
|
||||
passwordBlock: ((password: String?) -> Unit)? = null
|
||||
): Any? {
|
||||
fun parse(input: String): Any? {
|
||||
val inputMap = parseMap(input) ?: return null
|
||||
val parsedData = SimpleDataAccessor.of(inputMap) ?: return null
|
||||
passwordBlock?.apply {
|
||||
invoke(parsedData.password)
|
||||
}
|
||||
return try {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
// WifiConfiguration is deprecated in Android Q but
|
||||
@@ -54,7 +49,7 @@ object WifiConnector {
|
||||
} else {
|
||||
WifiNetworkSuggestion.Builder().apply(parsedData)
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// Some arguments may be out of range, e.g. SSIDs cannot
|
||||
// be longer than 32 characters in some versions of Android.
|
||||
// See: https://github.com/markusfisch/BinaryEye/issues/402
|
||||
@@ -62,24 +57,26 @@ object WifiConnector {
|
||||
}
|
||||
}
|
||||
|
||||
fun addNetwork(context: Context, config: Any): Int {
|
||||
fun addNetwork(context: Context, config: Any): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
|
||||
config is WifiNetworkSuggestion.Builder
|
||||
) {
|
||||
context.suggestNetworkFromBuilder(config)
|
||||
return true
|
||||
}
|
||||
|
||||
val wifiManager = context.applicationContext.getSystemService(
|
||||
Context.WIFI_SERVICE
|
||||
) as WifiManager
|
||||
// WifiConfiguration is deprecated in Android Q.
|
||||
@Suppress("DEPRECATION")
|
||||
return if ((config is WifiConfiguration &&
|
||||
wifiManager.enableWifi() &&
|
||||
wifiManager.removeOldNetwork(config) &&
|
||||
wifiManager.enableNewNetwork(config)) ||
|
||||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
|
||||
config is WifiNetworkSuggestion.Builder &&
|
||||
wifiManager.addNetworkFromBuilder(config))
|
||||
) {
|
||||
R.string.wifi_added
|
||||
} else {
|
||||
R.string.wifi_config_failed
|
||||
}
|
||||
return (config is WifiConfiguration &&
|
||||
wifiManager.enableWifi() &&
|
||||
wifiManager.removeOldNetwork(config) &&
|
||||
wifiManager.enableNewNetwork(config)) ||
|
||||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
|
||||
config is WifiNetworkSuggestion.Builder &&
|
||||
wifiManager.addNetworkFromBuilder(config))
|
||||
}
|
||||
|
||||
internal fun parseMap(input: String): Map<String, String>? {
|
||||
@@ -93,7 +90,7 @@ object WifiConnector {
|
||||
input.trimAndTerminate()
|
||||
)?.groupValues?.get(1)?.let { pairs ->
|
||||
pairRegex.findAll(pairs).map { pair ->
|
||||
pair.groupValues[1].uppercase(Locale.US) to pair.groupValues[2]
|
||||
pair.groupValues[1].uppercase(Locale.US) to pair.groupValues[2].unescape
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
@@ -103,17 +100,17 @@ object WifiConnector {
|
||||
) {
|
||||
// This should be private but because of testing it isn't possible.
|
||||
internal val ssid: String
|
||||
get() = inputMap.getValue("S").unescape
|
||||
get() = inputMap.getValue("S")
|
||||
internal val securityType: String
|
||||
get() = inputMap["T"]?.unescape ?: ""
|
||||
get() = inputMap["T"] ?: ""
|
||||
internal val password: String?
|
||||
get() = inputMap["P"]?.unescape
|
||||
get() = inputMap["P"]
|
||||
internal val hidden: Boolean
|
||||
get() = inputMap["H"]?.unescape == "true"
|
||||
get() = inputMap["H"] == "true"
|
||||
internal val anonymousIdentity: String
|
||||
get() = inputMap["AI"]?.unescape ?: inputMap["A"]?.unescape ?: ""
|
||||
get() = inputMap["AI"] ?: inputMap["A"] ?: ""
|
||||
internal val identity: String
|
||||
get() = inputMap["I"]?.unescape ?: ""
|
||||
get() = inputMap["I"] ?: ""
|
||||
internal val eapMethod: Int?
|
||||
get() = if (inputMap["E"].isNullOrEmpty()) {
|
||||
requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
@@ -201,7 +198,7 @@ object WifiConnector {
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// Don't accept passphrases that aren't ASCII encodeable.
|
||||
return null
|
||||
}
|
||||
@@ -313,6 +310,21 @@ private fun WifiManager.addNetworkFromBuilder(
|
||||
return result == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
private fun Context.suggestNetworkFromBuilder(
|
||||
builder: WifiNetworkSuggestion.Builder
|
||||
) {
|
||||
val suggestion = builder.build()
|
||||
startActivity(
|
||||
Intent(Settings.ACTION_WIFI_ADD_NETWORKS).apply {
|
||||
putParcelableArrayListExtra(
|
||||
Settings.EXTRA_WIFI_NETWORK_LIST,
|
||||
arrayListOf(suggestion)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure the string ends with a semicolon.
|
||||
private fun String.trimAndTerminate(): String {
|
||||
var trimmed = trim()
|
||||
@@ -366,7 +378,7 @@ private fun WifiManager.removeOldNetwork(
|
||||
}?.networkId?.also {
|
||||
removeNetwork(it)
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
} catch (_: SecurityException) {
|
||||
// The user didn't allow ACCESS_FINE_LOCATION which is
|
||||
// required to access configuredNetworks and that's fine.
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.markusfisch.android.binaryeye.activity
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
@@ -9,43 +10,54 @@ import android.graphics.Matrix
|
||||
import android.graphics.Rect
|
||||
import android.hardware.Camera
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.design.widget.FloatingActionButton
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.SeekBar
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.adapter.prettifyFormatName
|
||||
import de.markusfisch.android.binaryeye.app.*
|
||||
import de.markusfisch.android.binaryeye.app.PERMISSION_CAMERA
|
||||
import de.markusfisch.android.binaryeye.app.applyLocale
|
||||
import de.markusfisch.android.binaryeye.app.db
|
||||
import de.markusfisch.android.binaryeye.app.hasBluetoothPermission
|
||||
import de.markusfisch.android.binaryeye.app.hasCameraPermission
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.automation.runAutomatedActions
|
||||
import de.markusfisch.android.binaryeye.bluetooth.sendBluetoothAsync
|
||||
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.content.toHexString
|
||||
import de.markusfisch.android.binaryeye.database.toScan
|
||||
import de.markusfisch.android.binaryeye.graphics.FrameMetrics
|
||||
import de.markusfisch.android.binaryeye.graphics.mapPosition
|
||||
import de.markusfisch.android.binaryeye.graphics.setFrameRoi
|
||||
import de.markusfisch.android.binaryeye.graphics.setFrameToView
|
||||
import de.markusfisch.android.binaryeye.media.releaseToneGenerators
|
||||
import de.markusfisch.android.binaryeye.net.isScanDeeplink
|
||||
import de.markusfisch.android.binaryeye.net.sendAsync
|
||||
import de.markusfisch.android.binaryeye.net.urlEncode
|
||||
import de.markusfisch.android.binaryeye.preference.Preferences
|
||||
import de.markusfisch.android.binaryeye.view.errorFeedback
|
||||
import de.markusfisch.android.binaryeye.view.initSystemBars
|
||||
import de.markusfisch.android.binaryeye.view.initBars
|
||||
import de.markusfisch.android.binaryeye.view.scanFeedback
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.widget.DetectorView
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import de.markusfisch.android.cameraview.widget.CameraView
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.Binarizer
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.ReaderOptions
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.Result
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.TextMode
|
||||
import java.io.FileInputStream
|
||||
import java.util.Scanner
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
@@ -59,6 +71,8 @@ class CameraActivity : AppCompatActivity() {
|
||||
private lateinit var zoomBar: SeekBar
|
||||
private lateinit var flashFab: FloatingActionButton
|
||||
|
||||
private var currentProfile = prefs.profile
|
||||
private var shouldStoreSettings = true
|
||||
private var formatsToRead = setOf<BarcodeFormat>()
|
||||
private var frameMetrics = FrameMetrics()
|
||||
private var decoding = true
|
||||
@@ -68,8 +82,10 @@ class CameraActivity : AppCompatActivity() {
|
||||
private var frontFacing = false
|
||||
private var bulkMode = prefs.bulkMode
|
||||
private var restrictFormat: String? = null
|
||||
private var searchTerm: Regex? = null
|
||||
private var ignoreNext: String? = null
|
||||
private var fallbackBuffer: IntArray? = null
|
||||
private var requestCameraPermission = true
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
@@ -92,7 +108,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
) {
|
||||
when (requestCode) {
|
||||
PICK_FILE_RESULT_CODE -> {
|
||||
if (resultCode == Activity.RESULT_OK && resultData != null) {
|
||||
if (resultCode == RESULT_OK && resultData != null) {
|
||||
val pick = Intent(this, PickActivity::class.java)
|
||||
pick.action = Intent.ACTION_VIEW
|
||||
pick.setDataAndType(resultData.data, "image/*")
|
||||
@@ -115,8 +131,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
// custom locale.
|
||||
setTitle(R.string.scan_code)
|
||||
|
||||
initSystemBars(this)
|
||||
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
|
||||
initBars()
|
||||
|
||||
cameraView = findViewById(R.id.camera_view) as CameraView
|
||||
detectorView = findViewById(R.id.detector_view) as DetectorView
|
||||
@@ -128,7 +143,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
initDetectorView()
|
||||
|
||||
if (intent?.action == Intent.ACTION_SEND &&
|
||||
intent.type == "text/plain"
|
||||
intent.type?.startsWith("text/") == true
|
||||
) {
|
||||
handleSendText(intent)
|
||||
}
|
||||
@@ -137,38 +152,90 @@ class CameraActivity : AppCompatActivity() {
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
fallbackBuffer = null
|
||||
saveZoom()
|
||||
detectorView.saveCropHandlePos()
|
||||
releaseToneGenerators()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (firstStart()) {
|
||||
startActivity(Intent(this, WelcomeActivity::class.java))
|
||||
return
|
||||
}
|
||||
loadPreferences()
|
||||
if (refreshIfProfileChanged()) {
|
||||
return
|
||||
}
|
||||
System.gc()
|
||||
updateHints()
|
||||
setReturnTarget(intent)
|
||||
// Avoid asking multiple times when the user has denied access
|
||||
// for this session. Otherwise ActivityCompat.requestPermissions()
|
||||
// will trigger onResume() again and again.
|
||||
if (hasCameraPermission(requestCameraPermission)) {
|
||||
openCamera()
|
||||
}
|
||||
requestCameraPermission = false
|
||||
}
|
||||
|
||||
private fun Context.firstStart(): Boolean {
|
||||
val welcomeShownName = "welcome_shown"
|
||||
if (prefs.defaultPreferences.getBoolean(welcomeShownName, false)) {
|
||||
return false
|
||||
}
|
||||
prefs.defaultPreferences.edit().putBoolean(welcomeShownName, true).apply()
|
||||
val packageInfo = packageManager.getPackageInfo(packageName, 0)
|
||||
val installedSince = System.currentTimeMillis() -
|
||||
packageInfo.firstInstallTime
|
||||
return installedSince < 86400000
|
||||
}
|
||||
|
||||
private fun refreshIfProfileChanged(): Boolean {
|
||||
if (currentProfile == prefs.profile) {
|
||||
return false
|
||||
}
|
||||
shouldStoreSettings = false
|
||||
zoomBar.max = 0 // Reset zoom when there are no preferences yet.
|
||||
currentProfile = prefs.profile
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
recreate()
|
||||
} else {
|
||||
finish()
|
||||
startActivity(Intent(this, CameraActivity::class.java))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun loadPreferences() {
|
||||
detectorView.updateCropHandlePos()
|
||||
updateHintsAndTitle()
|
||||
if (prefs.bulkMode && bulkMode != prefs.bulkMode) {
|
||||
bulkMode = prefs.bulkMode
|
||||
invalidateOptionsMenu()
|
||||
ignoreNext = null
|
||||
}
|
||||
setReturnTarget(intent)
|
||||
if (hasCameraPermission()) {
|
||||
openCamera()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateHints() {
|
||||
private fun updateHintsAndTitle() {
|
||||
val restriction = restrictFormat
|
||||
formatsToRead = if (restriction != null) {
|
||||
title = getString(
|
||||
R.string.scan_format,
|
||||
prettifyFormatName(restriction)
|
||||
)
|
||||
setOf(restriction)
|
||||
} else {
|
||||
setTitle(R.string.scan_code)
|
||||
prefs.barcodeFormats
|
||||
}.toFormatSet()
|
||||
updateTitle()
|
||||
}
|
||||
|
||||
private fun updateTitle() {
|
||||
if (searchTerm != null || restrictFormat != null) {
|
||||
title = getString(
|
||||
R.string.scan_for,
|
||||
listOfNotNull(
|
||||
searchTerm?.let { "\"$it\"" },
|
||||
restrictFormat?.prettifyFormatName(),
|
||||
).joinToString(" ")
|
||||
)
|
||||
} else {
|
||||
setTitle(R.string.scan_code)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setReturnTarget(intent: Intent?) {
|
||||
@@ -177,7 +244,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
returnResult = true
|
||||
}
|
||||
|
||||
intent?.dataString?.isReturnUrl() == true -> {
|
||||
intent?.dataString?.let(::isScanDeeplink) == true -> {
|
||||
finishAfterShowingResult = true
|
||||
returnUrlTemplate = intent.data?.getQueryParameter("ret")
|
||||
}
|
||||
@@ -200,12 +267,21 @@ class CameraActivity : AppCompatActivity() {
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
closeCamera()
|
||||
if (shouldStoreSettings) {
|
||||
storeSettings();
|
||||
}
|
||||
shouldStoreSettings = true
|
||||
}
|
||||
|
||||
private fun closeCamera() {
|
||||
cameraView.close()
|
||||
}
|
||||
|
||||
private fun storeSettings() {
|
||||
storeZoomBarSettings()
|
||||
detectorView.storeCropHandlePos()
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedState: Bundle) {
|
||||
super.onRestoreInstanceState(savedState)
|
||||
zoomBar.max = savedState.getInt(ZOOM_MAX)
|
||||
@@ -213,6 +289,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
frontFacing = savedState.getBoolean(FRONT_FACING)
|
||||
bulkMode = savedState.getBoolean(BULK_MODE)
|
||||
restrictFormat = savedState.getString(RESTRICT_FORMAT)
|
||||
searchTerm = savedState.getString(SEARCH_TERM)?.toRegex()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@@ -221,6 +298,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
outState.putBoolean(FRONT_FACING, frontFacing)
|
||||
outState.putBoolean(BULK_MODE, bulkMode)
|
||||
outState.putString(RESTRICT_FORMAT, restrictFormat)
|
||||
outState.putString(SEARCH_TERM, searchTerm?.toString())
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
@@ -236,6 +314,10 @@ class CameraActivity : AppCompatActivity() {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.activity_camera, menu)
|
||||
menu.findItem(R.id.bulk_mode).isChecked = bulkMode
|
||||
menu.findItem(R.id.profile).title = getString(
|
||||
R.string.current_profile,
|
||||
prefs.profile ?: getString(R.string.profile_default)
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -281,6 +363,11 @@ class CameraActivity : AppCompatActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.find_code -> {
|
||||
askForCode()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.preferences -> {
|
||||
startActivity(MainActivity.getPreferencesIntent(this))
|
||||
true
|
||||
@@ -291,6 +378,11 @@ class CameraActivity : AppCompatActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.profile -> {
|
||||
pickProfile()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
@@ -320,12 +412,38 @@ class CameraActivity : AppCompatActivity() {
|
||||
setTitle(R.string.restrict_format)
|
||||
setItems(names.toTypedArray()) { _, which ->
|
||||
restrictFormat = formats[which]
|
||||
updateHints()
|
||||
updateHintsAndTitle()
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
// Dialogs do not have a parent view.
|
||||
@SuppressLint("InflateParams")
|
||||
private fun askForCode() {
|
||||
val view = layoutInflater.inflate(R.layout.dialog_find_code, null)
|
||||
val editText = view.findViewById<EditText>(R.id.term)
|
||||
searchTerm?.let {
|
||||
editText.setText(it.toString())
|
||||
}
|
||||
AlertDialog.Builder(this)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val term = editText.text.toString().trim()
|
||||
if (!term.isEmpty()) {
|
||||
searchTerm = term.toRegex()
|
||||
ignoreNext = null
|
||||
}
|
||||
updateTitle()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
searchTerm = null
|
||||
ignoreNext = null
|
||||
updateTitle()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun openReadme() {
|
||||
val intent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
@@ -334,11 +452,57 @@ class CameraActivity : AppCompatActivity() {
|
||||
execShareIntent(intent)
|
||||
}
|
||||
|
||||
private fun pickProfile() {
|
||||
val profiles = arrayOf(
|
||||
getString(R.string.profile_default),
|
||||
) + prefs.profiles
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.profile)
|
||||
.setItems(profiles) { _, which ->
|
||||
val newProfile = when (which) {
|
||||
0 -> null
|
||||
else -> profiles[which]
|
||||
}
|
||||
if (currentProfile != newProfile) {
|
||||
storeSettings()
|
||||
prefs.load(this, newProfile)
|
||||
loadPreferences()
|
||||
refreshIfProfileChanged()
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun handleSendText(intent: Intent) {
|
||||
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
|
||||
if (text?.isEmpty() == false) {
|
||||
startActivity(MainActivity.getEncodeIntent(this, text, true))
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
// Read text from file.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM) as Uri?
|
||||
} else {
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
}?.let { textUri ->
|
||||
val file = contentResolver.openFileDescriptor(textUri, "r")
|
||||
if (file != null) {
|
||||
val fs = FileInputStream(file.fileDescriptor)
|
||||
val scn = Scanner(fs).useDelimiter("\\A")
|
||||
if (scn.hasNext()) {
|
||||
startActivity(
|
||||
MainActivity.getEncodeIntent(
|
||||
this, scn.next(), true
|
||||
)
|
||||
)
|
||||
finish()
|
||||
}
|
||||
file.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +529,8 @@ class CameraActivity : AppCompatActivity() {
|
||||
v ?: return false
|
||||
val dist = offset - pos
|
||||
val maxValue = zoomBar.max
|
||||
val change = maxValue / v.height.toFloat() * 2f * dist
|
||||
val change = maxValue /
|
||||
v.height.toFloat() * 2f * dist
|
||||
zoomBar.progress = min(
|
||||
maxValue,
|
||||
max(progress + change.roundToInt(), 0)
|
||||
@@ -395,11 +560,12 @@ class CameraActivity : AppCompatActivity() {
|
||||
parameters: Camera.Parameters
|
||||
) {
|
||||
zoomBar.visibility = if (parameters.isZoomSupported) {
|
||||
restoreZoomBarSettings()
|
||||
val max = parameters.maxZoom
|
||||
if (zoomBar.max != max) {
|
||||
zoomBar.max = max
|
||||
zoomBar.progress = max / 10
|
||||
saveZoom()
|
||||
storeZoomBarSettings()
|
||||
}
|
||||
parameters.zoom = zoomBar.progress
|
||||
View.VISIBLE
|
||||
@@ -438,7 +604,9 @@ class CameraActivity : AppCompatActivity() {
|
||||
tryRotate = prefs.autoRotate,
|
||||
tryInvert = true,
|
||||
tryDownscale = true,
|
||||
maxNumberOfSymbols = 1
|
||||
returnCodabarStartEnd = true,
|
||||
maxNumberOfSymbols = 1,
|
||||
textMode = TextMode.PLAIN
|
||||
)
|
||||
var useLocalAverage = false
|
||||
camera.setPreviewCallback { frameData, _ ->
|
||||
@@ -463,10 +631,22 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
)?.let { results ->
|
||||
val result = results.first()
|
||||
if (result.text != ignoreNext) {
|
||||
postResult(result)
|
||||
decoding = false
|
||||
val text = result.text
|
||||
if (text == ignoreNext) {
|
||||
return@let
|
||||
}
|
||||
val term = searchTerm
|
||||
if (term != null &&
|
||||
!text.matches(term) &&
|
||||
!text.contains(term)
|
||||
) {
|
||||
ignoreNext = text
|
||||
errorFeedback()
|
||||
toast(R.string.does_not_match_search_term)
|
||||
return@let
|
||||
}
|
||||
postResult(result)
|
||||
decoding = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,7 +676,6 @@ class CameraActivity : AppCompatActivity() {
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
restoreZoom()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -505,19 +684,19 @@ class CameraActivity : AppCompatActivity() {
|
||||
val params = parameters
|
||||
params.zoom = zoom
|
||||
parameters = params
|
||||
} catch (e: RuntimeException) {
|
||||
} catch (_: RuntimeException) {
|
||||
// Ignore. There's nothing we can do.
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveZoom() {
|
||||
private fun storeZoomBarSettings() {
|
||||
val editor = prefs.preferences.edit()
|
||||
editor.putInt(ZOOM_MAX, zoomBar.max)
|
||||
editor.putInt(ZOOM_LEVEL, zoomBar.progress)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun restoreZoom() {
|
||||
private fun restoreZoomBarSettings() {
|
||||
zoomBar.max = prefs.preferences.getInt(ZOOM_MAX, zoomBar.max)
|
||||
zoomBar.progress = prefs.preferences.getInt(
|
||||
ZOOM_LEVEL,
|
||||
@@ -534,12 +713,21 @@ class CameraActivity : AppCompatActivity() {
|
||||
updateFrameRoiAndMappingMatrix()
|
||||
}
|
||||
detectorView.setPaddingFromWindowInsets()
|
||||
detectorView.restoreCropHandlePos()
|
||||
detectorView.cropHandleName = "camera_crop_handle"
|
||||
}
|
||||
|
||||
private fun updateFrameRoiAndMappingMatrix() {
|
||||
val viewRect = cameraView.previewRect
|
||||
val viewRoi = if (detectorView.roi.width() < 1) {
|
||||
if (viewRect.width() < 1 ||
|
||||
viewRect.height() < 1 ||
|
||||
!frameMetrics.isValid()
|
||||
) {
|
||||
return
|
||||
}
|
||||
val viewRoi = if (
|
||||
detectorView.roi.width() < 1 ||
|
||||
detectorView.roi.height() < 1
|
||||
) {
|
||||
viewRect
|
||||
} else {
|
||||
detectorView.roi
|
||||
@@ -616,8 +804,12 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
if (bulkMode) {
|
||||
if (prefs.ignoreConsecutiveDuplicates) {
|
||||
ignoreNext = result.text
|
||||
when (prefs.ignoreDuplicates()) {
|
||||
Preferences.Companion.IgnoreDuplicates.Consecutive,
|
||||
Preferences.Companion.IgnoreDuplicates.Any ->
|
||||
ignoreNext = result.text
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
if (prefs.showToastInBulkMode) {
|
||||
toast(result.text)
|
||||
@@ -636,6 +828,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
private const val FRONT_FACING = "front_facing"
|
||||
private const val BULK_MODE = "bulk_mode"
|
||||
private const val RESTRICT_FORMAT = "restrict_format"
|
||||
private const val SEARCH_TERM = "search_term"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,7 +850,7 @@ fun Activity.showResult(
|
||||
if (prefs.sendScanActive && prefs.sendScanUrl.isNotEmpty()) {
|
||||
if (prefs.sendScanType == "4") {
|
||||
openUrl(
|
||||
prefs.sendScanUrl + scan.content.urlEncode()
|
||||
prefs.sendScanUrl + scan.text.urlEncode()
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -668,7 +861,7 @@ fun Activity.showResult(
|
||||
if (code == null || code < 200 || code > 299) {
|
||||
errorFeedback()
|
||||
}
|
||||
if (body != null && body.isNotEmpty()) {
|
||||
if (!body.isNullOrEmpty()) {
|
||||
toast(body)
|
||||
} else if (code == null || code > 299) {
|
||||
toast(R.string.background_request_failed)
|
||||
@@ -699,6 +892,9 @@ fun Activity.showResult(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (runAutomatedActions(scan)) {
|
||||
return
|
||||
}
|
||||
if (!bulkMode) {
|
||||
startActivity(
|
||||
MainActivity.getDecodeIntent(this, scan)
|
||||
@@ -708,7 +904,7 @@ fun Activity.showResult(
|
||||
|
||||
private fun getReturnIntent(result: Result) = Intent().apply {
|
||||
putExtra("SCAN_RESULT", result.text)
|
||||
putExtra("SCAN_RESULT_FORMAT", result.format)
|
||||
putExtra("SCAN_RESULT_FORMAT", result.format.toString())
|
||||
putExtra("SCAN_RESULT_ORIENTATION", result.orientation)
|
||||
putExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL", result.ecLevel)
|
||||
if (result.rawBytes.isNotEmpty()) {
|
||||
@@ -716,12 +912,6 @@ private fun getReturnIntent(result: Result) = Intent().apply {
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.isReturnUrl() = listOf(
|
||||
"binaryeye://scan",
|
||||
"http://markusfisch.de/BinaryEye",
|
||||
"https://markusfisch.de/BinaryEye"
|
||||
).firstOrNull { startsWith(it) } != null
|
||||
|
||||
private fun completeUrl(urlTemplate: String, result: Result) = Uri.parse(
|
||||
urlTemplate
|
||||
.replace("{RESULT}", result.text.urlEncode())
|
||||
|
||||
@@ -3,11 +3,11 @@ package de.markusfisch.android.binaryeye.activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
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.PERMISSION_LOCATION
|
||||
import de.markusfisch.android.binaryeye.app.PERMISSION_WRITE
|
||||
@@ -20,9 +20,9 @@ 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.net.isEncodeDeeplink
|
||||
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.initBars
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
override fun onRequestPermissionsResult(
|
||||
@@ -31,7 +31,8 @@ class MainActivity : AppCompatActivity() {
|
||||
grantResults: IntArray
|
||||
) {
|
||||
when (requestCode) {
|
||||
PERMISSION_LOCATION, PERMISSION_WRITE -> if (grantResults.isNotEmpty() &&
|
||||
PERMISSION_LOCATION, PERMISSION_WRITE -> if (
|
||||
grantResults.isNotEmpty() &&
|
||||
grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
permissionGrantedCallback?.invoke()
|
||||
@@ -59,10 +60,7 @@ class MainActivity : AppCompatActivity() {
|
||||
super.onCreate(state)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
initSystemBars(this)
|
||||
val toolbar = findViewById(R.id.toolbar) as Toolbar
|
||||
recordToolbarHeight(toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
initBars()
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
supportFragmentManager.addOnBackStackChangedListener {
|
||||
@@ -85,13 +83,24 @@ class MainActivity : AppCompatActivity() {
|
||||
return when {
|
||||
intent.hasExtra(PREFERENCES) -> PreferencesFragment()
|
||||
intent.hasExtra(HISTORY) -> HistoryFragment()
|
||||
|
||||
intent.dataString?.let(::isEncodeDeeplink) == true -> {
|
||||
val uri = Uri.parse(intent.dataString)
|
||||
EncodeFragment.newInstance(
|
||||
content = uri.getQueryParameter("content"),
|
||||
format = uri.getQueryParameter("format")?.uppercase(),
|
||||
execute = uri.getQueryParameter("execute")
|
||||
.let { it == "" || it.toBoolean() }
|
||||
)
|
||||
}
|
||||
|
||||
intent.hasExtra(ENCODE) -> EncodeFragment.newInstance(
|
||||
intent.getStringExtra(ENCODE)
|
||||
)
|
||||
|
||||
intent.hasExtra(DECODED) -> DecodeFragment.newInstance(
|
||||
intent.getParcelableExtra(DECODED)!!
|
||||
)
|
||||
intent.hasExtra(DECODED) -> intent.getScanExtra(DECODED)?.let {
|
||||
DecodeFragment.newInstance(it)
|
||||
} ?: DecodeFragment()
|
||||
|
||||
else -> PreferencesFragment()
|
||||
}
|
||||
@@ -117,10 +126,11 @@ class MainActivity : AppCompatActivity() {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.putExtra(ENCODE, text)
|
||||
if (isExternal) {
|
||||
val flagActivityClearTask =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
} else 0
|
||||
val flagActivityClearTask = if (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
|
||||
) {
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
} else 0
|
||||
intent.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NO_HISTORY or
|
||||
flagActivityClearTask or
|
||||
@@ -137,3 +147,12 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Intent.getScanExtra(name: String): Scan? = if (
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
|
||||
) {
|
||||
@Suppress("DEPRECATION")
|
||||
getParcelableExtra(name)
|
||||
} else {
|
||||
getParcelableExtra(name, Scan::class.java)
|
||||
}
|
||||
|
||||
@@ -7,10 +7,9 @@ import android.graphics.Matrix
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
@@ -22,8 +21,7 @@ import de.markusfisch.android.binaryeye.graphics.loadImageUri
|
||||
import de.markusfisch.android.binaryeye.graphics.mapPosition
|
||||
import de.markusfisch.android.binaryeye.media.releaseToneGenerators
|
||||
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.initBars
|
||||
import de.markusfisch.android.binaryeye.view.scanFeedback
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.widget.CropImageView
|
||||
@@ -51,14 +49,14 @@ class PickActivity : AppCompatActivity() {
|
||||
tryRotate = true,
|
||||
tryInvert = true,
|
||||
tryDownscale = true,
|
||||
returnCodabarStartEnd = true,
|
||||
maxNumberOfSymbols = 1,
|
||||
formats = prefs.barcodeFormats.toFormatSet()
|
||||
)
|
||||
|
||||
private lateinit var cropImageView: CropImageView
|
||||
private lateinit var detectorView: DetectorView
|
||||
private lateinit var freeRotationItem: MenuItem
|
||||
|
||||
private var cropImageView: CropImageView? = null
|
||||
private var detectorView: DetectorView? = null
|
||||
private var freeRotationItem: MenuItem? = null
|
||||
private var result: Result? = null
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
@@ -74,11 +72,7 @@ class PickActivity : AppCompatActivity() {
|
||||
// locale.
|
||||
setTitle(R.string.pick_code_to_scan)
|
||||
|
||||
initSystemBars(this)
|
||||
val toolbar = findViewById(R.id.toolbar) as Toolbar
|
||||
recordToolbarHeight(toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
initBars()
|
||||
supportFragmentManager.addOnBackStackChangedListener {
|
||||
colorSystemAndToolBars(this@PickActivity)
|
||||
}
|
||||
@@ -87,23 +81,34 @@ class PickActivity : AppCompatActivity() {
|
||||
if (bitmap == null) {
|
||||
applicationContext.toast(R.string.error_no_content)
|
||||
finish()
|
||||
// This is why cropImageView and detectorView aren't lateinit.
|
||||
// If they were, a kotlin.UninitializedPropertyAccessException
|
||||
// would happen here.
|
||||
return
|
||||
}
|
||||
|
||||
cropImageView = findViewById(R.id.image) as CropImageView
|
||||
cropImageView.restrictTranslation = false
|
||||
cropImageView.freeRotation = prefs.freeRotation
|
||||
cropImageView.setImageBitmap(bitmap)
|
||||
cropImageView.onScan = {
|
||||
scanWithinBounds(bitmap)
|
||||
cropImageView = (findViewById(R.id.image) as CropImageView).apply {
|
||||
restrictTranslation = false
|
||||
freeRotation = prefs.freeRotation
|
||||
setImageBitmap(bitmap)
|
||||
onScan = {
|
||||
scanWithinBounds(bitmap)
|
||||
}
|
||||
runAfterLayout = {
|
||||
minWidth = min(
|
||||
minWidth / 2f,
|
||||
max(bitmap.width, bitmap.height).toFloat()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
detectorView = findViewById(R.id.detector_view) as DetectorView
|
||||
detectorView.onRoiChanged = {
|
||||
scanWithinBounds(bitmap)
|
||||
detectorView = (findViewById(R.id.detector_view) as DetectorView).apply {
|
||||
onRoiChanged = {
|
||||
scanWithinBounds(bitmap)
|
||||
}
|
||||
setPaddingFromWindowInsets()
|
||||
cropHandleName = "picker_crop_handle"
|
||||
}
|
||||
detectorView.setPaddingFromWindowInsets()
|
||||
detectorView.restoreCropHandlePos()
|
||||
|
||||
findViewById(R.id.scan).setOnClickListener {
|
||||
showResult()
|
||||
@@ -125,35 +130,42 @@ class PickActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun scanWithinBounds(bitmap: Bitmap) {
|
||||
val viewRoi = if (detectorView.roi.width() < 1) {
|
||||
cropImageView.getBoundsRect()
|
||||
} else {
|
||||
detectorView.roi
|
||||
}
|
||||
val mappedRect = cropImageView.mappedRect
|
||||
val cropped = bitmap.crop(
|
||||
getNormalizedRoi(mappedRect, viewRoi),
|
||||
cropImageView.imageRotation,
|
||||
cropImageView.pivotX,
|
||||
cropImageView.pivotY
|
||||
) ?: return
|
||||
val croppedInView = Rect(
|
||||
max(viewRoi.left, mappedRect.left.roundToInt()),
|
||||
max(viewRoi.top, mappedRect.top.roundToInt()),
|
||||
min(viewRoi.right, mappedRect.right.roundToInt()),
|
||||
min(viewRoi.bottom, mappedRect.bottom.roundToInt())
|
||||
val dv = detectorView ?: return
|
||||
val civ = cropImageView ?: return
|
||||
val viewRoi = Rect(
|
||||
if (dv.roi.width() < 1) {
|
||||
civ.getBoundsRect()
|
||||
} else {
|
||||
dv.roi
|
||||
}
|
||||
)
|
||||
matrix.apply {
|
||||
setScale(
|
||||
croppedInView.width().toFloat() / cropped.width,
|
||||
croppedInView.height().toFloat() / cropped.height
|
||||
)
|
||||
postTranslate(
|
||||
croppedInView.left.toFloat(),
|
||||
croppedInView.top.toFloat()
|
||||
)
|
||||
}
|
||||
val mappedRect = RectF(civ.mappedRect)
|
||||
val imageRotation = civ.imageRotation
|
||||
val pivotX = civ.pivotX
|
||||
val pivotY = civ.pivotY
|
||||
scope.launch {
|
||||
val cropped = bitmap.crop(
|
||||
getNormalizedRoi(mappedRect, viewRoi),
|
||||
imageRotation,
|
||||
pivotX,
|
||||
pivotY
|
||||
) ?: return@launch
|
||||
val croppedInView = Rect(
|
||||
max(viewRoi.left, mappedRect.left.roundToInt()),
|
||||
max(viewRoi.top, mappedRect.top.roundToInt()),
|
||||
min(viewRoi.right, mappedRect.right.roundToInt()),
|
||||
min(viewRoi.bottom, mappedRect.bottom.roundToInt())
|
||||
)
|
||||
matrix.apply {
|
||||
setScale(
|
||||
croppedInView.width().toFloat() / cropped.width,
|
||||
croppedInView.height().toFloat() / cropped.height
|
||||
)
|
||||
postTranslate(
|
||||
croppedInView.left.toFloat(),
|
||||
croppedInView.top.toFloat()
|
||||
)
|
||||
}
|
||||
cropped.decode()?.first()?.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (isFinishing) {
|
||||
@@ -161,10 +173,10 @@ class PickActivity : AppCompatActivity() {
|
||||
}
|
||||
result = it
|
||||
scanFeedback()
|
||||
detectorView.update(
|
||||
dv.update(
|
||||
matrix.mapPosition(
|
||||
it.position,
|
||||
detectorView.coordinates
|
||||
dv.coordinates
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -194,7 +206,7 @@ class PickActivity : AppCompatActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
detectorView.saveCropHandlePos()
|
||||
detectorView?.storeCropHandlePos()
|
||||
parentJob.cancel()
|
||||
releaseToneGenerators()
|
||||
}
|
||||
@@ -216,8 +228,8 @@ class PickActivity : AppCompatActivity() {
|
||||
|
||||
R.id.toggle_free -> {
|
||||
prefs.freeRotation = prefs.freeRotation xor true
|
||||
cropImageView.freeRotation = prefs.freeRotation
|
||||
freeRotationItem.updateFreeRotationIcon()
|
||||
cropImageView?.freeRotation = prefs.freeRotation
|
||||
freeRotationItem?.updateFreeRotationIcon()
|
||||
true
|
||||
}
|
||||
|
||||
@@ -236,13 +248,12 @@ class PickActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun rotateClockwise() {
|
||||
cropImageView.imageRotation = cropImageView.imageRotation + 90 % 360
|
||||
val civ = cropImageView ?: return
|
||||
civ.imageRotation = (civ.imageRotation + 90) % 360
|
||||
}
|
||||
|
||||
private fun loadSentImage(intent: Intent): Bitmap? {
|
||||
val uri = intent.getParcelableExtra<Parcelable>(
|
||||
Intent.EXTRA_STREAM
|
||||
) as? Uri ?: return null
|
||||
val uri = intent.getUriExtra() ?: return null
|
||||
return contentResolver.loadImageUri(uri)
|
||||
}
|
||||
|
||||
@@ -272,3 +283,12 @@ private fun getNormalizedRoi(imageRect: RectF, roi: Rect): RectF {
|
||||
(roi.bottom - imageRect.top) / h
|
||||
)
|
||||
}
|
||||
|
||||
private fun Intent.getUriExtra(): Uri? = if (
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
|
||||
) {
|
||||
@Suppress("DEPRECATION")
|
||||
getParcelableExtra(Intent.EXTRA_STREAM)
|
||||
} else {
|
||||
getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package de.markusfisch.android.binaryeye.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
|
||||
class WelcomeActivity : AppCompatActivity() {
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setContentView(R.layout.activity_welcome)
|
||||
findViewById(R.id.expert).setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
findViewById(R.id.simple).setOnClickListener {
|
||||
prefs.apply {
|
||||
showCropHandle = false
|
||||
zoomBySwiping = false
|
||||
beep = true
|
||||
copyImmediately = true
|
||||
openImmediately = true
|
||||
showMetaData = false
|
||||
showHexDump = false
|
||||
defaultSearchUrl = "https://www.google.com/search?q="
|
||||
openWithUrl = defaultSearchUrl
|
||||
expandEscapeSequences = false
|
||||
brightenScreen = true
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
private val idIndex = cursor.getColumnIndex(Database.SCANS_ID)
|
||||
private val timeIndex = cursor.getColumnIndex(Database.SCANS_DATETIME)
|
||||
private val nameIndex = cursor.getColumnIndex(Database.SCANS_NAME)
|
||||
private val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
|
||||
private val textIndex = cursor.getColumnIndex(Database.SCANS_TEXT)
|
||||
private val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
|
||||
private val selections = mutableMapOf<Long, Int>()
|
||||
private val selectedColor = ContextCompat.getColor(
|
||||
@@ -74,7 +74,7 @@ class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
|
||||
fun getContent(
|
||||
position: Int
|
||||
) = (getItem(position) as Cursor?)?.getString(contentIndex)
|
||||
) = (getItem(position) as Cursor?)?.getString(textIndex)
|
||||
|
||||
override fun newView(
|
||||
context: Context,
|
||||
@@ -92,7 +92,7 @@ class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
getViewHolder(view).apply {
|
||||
timeView.text = formatDateTime(cursor.getString(timeIndex))
|
||||
val name = cursor.getString(nameIndex)
|
||||
val content = cursor.getString(contentIndex)
|
||||
val text = cursor.getString(textIndex)
|
||||
var icon = 0
|
||||
contentView.text = when {
|
||||
name?.isNotEmpty() == true -> {
|
||||
@@ -100,16 +100,16 @@ class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
name
|
||||
}
|
||||
|
||||
content.isNullOrEmpty() -> context.getString(
|
||||
text.isNullOrEmpty() -> context.getString(
|
||||
R.string.binary_data
|
||||
)
|
||||
|
||||
else -> content
|
||||
else -> text
|
||||
}
|
||||
contentView.setCompoundDrawablesWithIntrinsicBounds(
|
||||
icon, 0, 0, 0
|
||||
)
|
||||
formatView.text = prettifyFormatName(cursor.getString(formatIndex))
|
||||
formatView.text = cursor.getString(formatIndex).prettifyFormatName()
|
||||
}
|
||||
val selected = selections[cursor.getLong(idIndex)] != null
|
||||
view.post {
|
||||
@@ -139,7 +139,7 @@ class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
)
|
||||
}
|
||||
|
||||
fun prettifyFormatName(format: String) = format.replace("_", " ")
|
||||
fun String.prettifyFormatName() = replace("_", " ")
|
||||
|
||||
private fun formatDateTime(rfc: String): String {
|
||||
try {
|
||||
@@ -149,7 +149,7 @@ private fun formatDateTime(rfc: String): String {
|
||||
DateFormat.SHORT
|
||||
).format(it)
|
||||
}
|
||||
} catch (e: ParseException) {
|
||||
} catch (_: ParseException) {
|
||||
// Ignore and fall through.
|
||||
}
|
||||
return rfc
|
||||
|
||||
@@ -2,7 +2,7 @@ package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
fun Context.applyLocale(localeName: String) {
|
||||
if (localeName.isEmpty()) {
|
||||
|
||||
@@ -10,9 +10,9 @@ import android.support.v4.content.ContextCompat
|
||||
var permissionGrantedCallback: (() -> Any)? = null
|
||||
|
||||
const val PERMISSION_CAMERA = 1
|
||||
fun Activity.hasCameraPermission() = hasPermission(
|
||||
fun Activity.hasCameraPermission(request: Boolean = true) = hasPermission(
|
||||
Manifest.permission.CAMERA,
|
||||
PERMISSION_CAMERA
|
||||
if (request) PERMISSION_CAMERA else 0
|
||||
)
|
||||
|
||||
const val PERMISSION_WRITE = 2
|
||||
@@ -53,11 +53,13 @@ private fun Activity.hasPermission(
|
||||
permission
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
arrayOf(permission),
|
||||
requestCode
|
||||
)
|
||||
if (requestCode > 0) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
arrayOf(permission),
|
||||
requestCode
|
||||
)
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package de.markusfisch.android.binaryeye.automation
|
||||
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
import de.markusfisch.android.binaryeye.net.urlEncode
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.util.Locale
|
||||
|
||||
data class AutomatedAction(
|
||||
val pattern: String,
|
||||
val type: Type,
|
||||
val urlTemplate: String? = null,
|
||||
val intentUriTemplate: String? = null
|
||||
) {
|
||||
enum class Type {
|
||||
Intent, CustomIntent
|
||||
}
|
||||
|
||||
fun matches(content: String): Boolean = try {
|
||||
Regex(pattern).matches(content)
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
|
||||
fun toJson(): JSONObject = JSONObject().apply {
|
||||
put(KEY_PATTERN, pattern)
|
||||
put(KEY_TYPE, type.name.lowercase(Locale.ROOT))
|
||||
put(KEY_URL_TEMPLATE, urlTemplate ?: "")
|
||||
put(KEY_INTENT_URI_TEMPLATE, intentUriTemplate ?: "")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_PATTERN = "pattern"
|
||||
private const val KEY_TYPE = "type"
|
||||
private const val KEY_URL_TEMPLATE = "url_template"
|
||||
private const val KEY_INTENT_URI_TEMPLATE = "intent_uri_template"
|
||||
|
||||
fun fromJson(obj: JSONObject): AutomatedAction? {
|
||||
val pattern = obj.optString(KEY_PATTERN, "").trim()
|
||||
if (pattern.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
val type = when (obj.optString(KEY_TYPE, "")) {
|
||||
"custom_intent" -> Type.CustomIntent
|
||||
"intent" -> Type.Intent
|
||||
"html" -> return null
|
||||
else -> Type.Intent
|
||||
}
|
||||
val urlTemplate = obj.optString(KEY_URL_TEMPLATE, "").trim()
|
||||
val intentUriTemplate = obj.optString(
|
||||
KEY_INTENT_URI_TEMPLATE,
|
||||
""
|
||||
).trim()
|
||||
return AutomatedAction(
|
||||
pattern = pattern,
|
||||
type = type,
|
||||
urlTemplate = urlTemplate.ifEmpty { null },
|
||||
intentUriTemplate = intentUriTemplate.ifEmpty { null }
|
||||
)
|
||||
}
|
||||
|
||||
fun fromJsonArray(json: String): MutableList<AutomatedAction> {
|
||||
val actions = ArrayList<AutomatedAction>()
|
||||
val array = try {
|
||||
JSONArray(json)
|
||||
} catch (_: Exception) {
|
||||
JSONArray()
|
||||
}
|
||||
for (i in 0 until array.length()) {
|
||||
val obj = array.optJSONObject(i) ?: continue
|
||||
fromJson(obj)?.let { actions.add(it) }
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
fun toJsonArray(actions: List<AutomatedAction>): String {
|
||||
val array = JSONArray()
|
||||
actions.forEach { array.put(it.toJson()) }
|
||||
return array.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun String.buildUrl(scan: Scan): String {
|
||||
return buildTemplate(scan, encodeResult = true)
|
||||
}
|
||||
|
||||
fun String.buildTemplate(scan: Scan, encodeResult: Boolean): String {
|
||||
val text = scan.text
|
||||
val raw = scan.raw ?: text.toByteArray()
|
||||
val result = if (encodeResult) {
|
||||
text.urlEncode()
|
||||
} else {
|
||||
text
|
||||
}
|
||||
val format = if (encodeResult) {
|
||||
scan.format.name.urlEncode()
|
||||
} else {
|
||||
scan.format.name
|
||||
}
|
||||
return this
|
||||
.replace("{RESULT}", result)
|
||||
.replace("{RESULT_BYTES}", raw.toHexString())
|
||||
.replace("{FORMAT}", format)
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package de.markusfisch.android.binaryeye.automation
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.content.execShareIntent
|
||||
import de.markusfisch.android.binaryeye.content.openUrl
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
|
||||
fun Context.runAutomatedActions(scan: Scan): Boolean {
|
||||
val content = scan.text
|
||||
if (content.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
val actions = prefs.automatedActions
|
||||
if (actions.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
for (action in actions) {
|
||||
if (action.matches(content)) {
|
||||
return when (action.type) {
|
||||
AutomatedAction.Type.Intent -> {
|
||||
val url = action.urlTemplate?.buildUrl(scan)
|
||||
if (url.isNullOrEmpty()) {
|
||||
toast(R.string.automated_action_invalid_url)
|
||||
false
|
||||
} else {
|
||||
openUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
AutomatedAction.Type.CustomIntent ->
|
||||
execCustomIntent(action, scan)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun Context.execCustomIntent(
|
||||
action: AutomatedAction,
|
||||
scan: Scan
|
||||
): Boolean {
|
||||
val template = action.intentUriTemplate
|
||||
if (template.isNullOrEmpty()) {
|
||||
toast(R.string.automated_action_intent_uri_required)
|
||||
return false
|
||||
}
|
||||
val intentUri = template.buildTemplate(scan, encodeResult = false)
|
||||
val intent = try {
|
||||
Intent.parseUri(intentUri, Intent.URI_INTENT_SCHEME)
|
||||
} catch (_: Exception) {
|
||||
toast(R.string.automated_action_intent_uri_invalid)
|
||||
return false
|
||||
}
|
||||
return execShareIntent(intent)
|
||||
}
|
||||
@@ -21,10 +21,10 @@ fun Scan.sendBluetoothAsync(
|
||||
val connected = if (isConnected) {
|
||||
true
|
||||
} else {
|
||||
connect(host)
|
||||
connect(host, true)
|
||||
}
|
||||
val sent = if (connected) {
|
||||
send(content)
|
||||
send(text, host)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -35,21 +35,20 @@ fun Scan.sendBluetoothAsync(
|
||||
}
|
||||
|
||||
fun setBluetoothHosts(listPref: ListPreference) {
|
||||
val devices = try {
|
||||
BluetoothAdapter.getDefaultAdapter().bondedDevices
|
||||
} catch (e: SecurityException) {
|
||||
try {
|
||||
val devices = blue.bondedDevices ?: return
|
||||
listPref.entries = devices.map {
|
||||
it.name
|
||||
}.toTypedArray()
|
||||
listPref.entryValues = devices.map {
|
||||
it.address
|
||||
}.toTypedArray()
|
||||
listPref.callChangeListener(listPref.value)
|
||||
} catch (_: SecurityException) {
|
||||
// Do nothing, either the user has denied Bluetooth access
|
||||
// or the permission was removed by the system. We're catching
|
||||
// the exception to keep the app from crashing.
|
||||
null
|
||||
} ?: return
|
||||
listPref.entries = devices.map {
|
||||
it.name
|
||||
}.toTypedArray()
|
||||
listPref.entryValues = devices.map {
|
||||
it.address
|
||||
}.toTypedArray()
|
||||
listPref.callChangeListener(listPref.value)
|
||||
}
|
||||
}
|
||||
|
||||
private var socket: BluetoothSocket? = null
|
||||
@@ -62,7 +61,7 @@ private val uuid = UUID.fromString(
|
||||
|
||||
private var isConnected = false
|
||||
|
||||
private fun connect(deviceName: String): Boolean = try {
|
||||
private fun connect(deviceName: String, onceMore: Boolean): Boolean = try {
|
||||
val device = findByName(deviceName) ?: throw RuntimeException(
|
||||
"Bluetooth device not found"
|
||||
)
|
||||
@@ -71,32 +70,41 @@ private fun connect(deviceName: String): Boolean = try {
|
||||
writer = socket?.outputStream?.writer()
|
||||
isConnected = true
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
close()
|
||||
} catch (_: Exception) {
|
||||
if (onceMore) {
|
||||
connect(deviceName, false)
|
||||
} else {
|
||||
close()
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
private fun send(message: String): Boolean = try {
|
||||
private fun send(message: String, host: String): Boolean = try {
|
||||
writer?.apply {
|
||||
write(message)
|
||||
write("\n")
|
||||
flush()
|
||||
}
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
close()
|
||||
false
|
||||
|
||||
if (connect(host, false)) {
|
||||
send(message, host)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun close() {
|
||||
try {
|
||||
writer?.close()
|
||||
} catch (e: IOException) {
|
||||
} catch (_: IOException) {
|
||||
// Catch exception if writer wasn't initialized.
|
||||
}
|
||||
try {
|
||||
socket?.close()
|
||||
} catch (e: IOException) {
|
||||
} catch (_: IOException) {
|
||||
// Nothing we can do about this but keep
|
||||
// the app from crashing.
|
||||
}
|
||||
@@ -106,11 +114,15 @@ private fun close() {
|
||||
}
|
||||
|
||||
private fun findByName(findableName: String): BluetoothDevice? {
|
||||
val deviceList = blue.bondedDevices
|
||||
for (device in deviceList) {
|
||||
if (device.address == findableName) {
|
||||
return device
|
||||
try {
|
||||
val deviceList = blue.bondedDevices
|
||||
for (device in deviceList) {
|
||||
if (device.address == findableName) {
|
||||
return device
|
||||
}
|
||||
}
|
||||
} catch (_: SecurityException) {
|
||||
// Fall through.
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
package de.markusfisch.android.binaryeye.content
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
import de.markusfisch.android.binaryeye.graphics.COLOR_BLACK
|
||||
import de.markusfisch.android.binaryeye.graphics.COLOR_WHITE
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BitMatrix
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.toBitmap
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.toSvg
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.toText
|
||||
import java.util.Arrays
|
||||
|
||||
enum class BarcodeColors {
|
||||
BLACK_ON_WHITE,
|
||||
WHITE_ON_BLACK,
|
||||
BLACK_ON_TRANSPARENT,
|
||||
WHITE_ON_TRANSPARENT;
|
||||
|
||||
fun foregroundColor(): Int = when (this) {
|
||||
BLACK_ON_WHITE,
|
||||
BLACK_ON_TRANSPARENT -> COLOR_BLACK
|
||||
|
||||
WHITE_ON_BLACK,
|
||||
WHITE_ON_TRANSPARENT -> COLOR_WHITE
|
||||
}
|
||||
|
||||
fun backgroundColor(): Int = when (this) {
|
||||
BLACK_ON_WHITE -> COLOR_WHITE
|
||||
WHITE_ON_BLACK -> COLOR_BLACK
|
||||
BLACK_ON_TRANSPARENT,
|
||||
WHITE_ON_TRANSPARENT -> 0
|
||||
}
|
||||
}
|
||||
|
||||
fun Scan.toBarcode(): Barcode<*> {
|
||||
val content = text.ifEmpty {
|
||||
raw
|
||||
}
|
||||
return if (symbol != null) {
|
||||
BitMatrixBarcode(
|
||||
symbol,
|
||||
content,
|
||||
format
|
||||
)
|
||||
} else {
|
||||
ContentBarcode(
|
||||
content,
|
||||
format,
|
||||
errorCorrectionLevel.toErrorCorrectionInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun String?.toErrorCorrectionInt() = when (this) {
|
||||
"L" -> 0
|
||||
"M" -> 4
|
||||
"Q" -> 6
|
||||
"H" -> 8
|
||||
else -> -1
|
||||
}
|
||||
|
||||
sealed class Barcode<T>(
|
||||
val content: T,
|
||||
val format: BarcodeFormat,
|
||||
val colors: BarcodeColors
|
||||
) {
|
||||
private var _bitmap: Bitmap? = null
|
||||
fun bitmap(): Bitmap {
|
||||
val b = _bitmap ?: bitmap(512)
|
||||
_bitmap = b
|
||||
return b
|
||||
}
|
||||
|
||||
fun bitmap(size: Int): Bitmap {
|
||||
return toBitmap(size)
|
||||
}
|
||||
|
||||
protected abstract fun toBitmap(size: Int): Bitmap
|
||||
|
||||
private var _svg: String? = null
|
||||
fun svg(): String {
|
||||
val s = _svg ?: toSvg()
|
||||
_svg = s
|
||||
return s
|
||||
}
|
||||
|
||||
protected abstract fun toSvg(): String
|
||||
|
||||
private var _text: String? = null
|
||||
fun text(): String {
|
||||
val t = _text ?: toText()
|
||||
_text = t
|
||||
return t
|
||||
}
|
||||
|
||||
protected abstract fun toText(): String
|
||||
|
||||
fun textOrHex(): String = when (content) {
|
||||
is String -> content
|
||||
is ByteArray -> content.toHexString()
|
||||
else -> throw IllegalArgumentException("Illegal arguments")
|
||||
}
|
||||
}
|
||||
|
||||
class ContentBarcode<T>(
|
||||
content: T,
|
||||
format: BarcodeFormat,
|
||||
val ecLevel: Int = -1,
|
||||
val margin: Int = -1,
|
||||
colors: BarcodeColors = BarcodeColors.BLACK_ON_WHITE
|
||||
) : Barcode<T>(content, format, colors) {
|
||||
override fun toBitmap(size: Int) = ZxingCpp.encodeAsBitmap(
|
||||
content, format, size, format.height(size), margin, ecLevel,
|
||||
setColor = colors.foregroundColor(),
|
||||
unsetColor = colors.backgroundColor()
|
||||
)
|
||||
|
||||
override fun toSvg() = ZxingCpp.encodeAsSvg(
|
||||
content, format, margin, ecLevel
|
||||
)
|
||||
|
||||
override fun toText() = ZxingCpp.encodeAsText(
|
||||
content, format, margin, ecLevel,
|
||||
inverted = colors == BarcodeColors.BLACK_ON_WHITE
|
||||
)
|
||||
}
|
||||
|
||||
class BitMatrixBarcode<T>(
|
||||
val bitMatrix: BitMatrix,
|
||||
content: T,
|
||||
format: BarcodeFormat,
|
||||
colors: BarcodeColors = BarcodeColors.BLACK_ON_WHITE
|
||||
) : Barcode<T>(content, format, colors) {
|
||||
override fun toBitmap(size: Int) = bitMatrix.inflate(size).toBitmap(
|
||||
setColor = colors.foregroundColor(),
|
||||
unsetColor = colors.backgroundColor()
|
||||
)
|
||||
|
||||
override fun toSvg() = bitMatrix.inflate().toSvg()
|
||||
|
||||
override fun toText() = bitMatrix.inflate().toText(
|
||||
inverted = colors == BarcodeColors.BLACK_ON_WHITE
|
||||
)
|
||||
}
|
||||
|
||||
private fun BitMatrix.inflate(
|
||||
size: Int = 0,
|
||||
quietZone: Boolean = true
|
||||
): BitMatrix {
|
||||
val extraModules = if (quietZone) 2 else 0
|
||||
val codeWidth = width + extraModules
|
||||
val codeHeight = height + extraModules
|
||||
var scale = size / codeWidth
|
||||
if (scale < 1) {
|
||||
if (size > 0 && !quietZone) {
|
||||
return this
|
||||
}
|
||||
scale = 1
|
||||
}
|
||||
val outWidth = codeWidth * scale
|
||||
val outHeight = codeHeight * scale
|
||||
val scaled = BitMatrix(
|
||||
outWidth,
|
||||
outHeight,
|
||||
ByteArray(outWidth * outHeight)
|
||||
)
|
||||
Arrays.fill(scaled.data, 1)
|
||||
val padding = extraModules / 2
|
||||
val left = padding * scale
|
||||
val top = padding * scale
|
||||
val right = outWidth - left
|
||||
val bottom = outHeight - top
|
||||
for (y in top until bottom) {
|
||||
var dst = y * outWidth + left
|
||||
val offset = (y - top) / scale * width
|
||||
for (x in left until right) {
|
||||
scaled.data[dst++] = data[
|
||||
offset + (x - left) / scale
|
||||
]
|
||||
}
|
||||
}
|
||||
return scaled
|
||||
}
|
||||
|
||||
private fun BarcodeFormat.height(size: Int) = when (this) {
|
||||
// 1D barcodes don't need as much vertical space.
|
||||
BarcodeFormat.CODABAR,
|
||||
BarcodeFormat.CODE_39,
|
||||
BarcodeFormat.CODE_93,
|
||||
BarcodeFormat.CODE_128,
|
||||
BarcodeFormat.DATA_BAR,
|
||||
BarcodeFormat.DATA_BAR_EXPANDED,
|
||||
BarcodeFormat.DX_FILM_EDGE,
|
||||
BarcodeFormat.EAN_8,
|
||||
BarcodeFormat.EAN_13,
|
||||
BarcodeFormat.ITF,
|
||||
BarcodeFormat.UPC_A,
|
||||
BarcodeFormat.UPC_E -> size / 3
|
||||
|
||||
else -> size
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.content
|
||||
|
||||
fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }
|
||||
@@ -0,0 +1,67 @@
|
||||
package de.markusfisch.android.binaryeye.content
|
||||
|
||||
import android.content.Context
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
private val idToLabel = hashMapOf(
|
||||
"DCA" to R.string.idl_vehicle_class,
|
||||
"DCB" to R.string.idl_restriction_codes,
|
||||
"DCD" to R.string.idl_endorsement_codes,
|
||||
"DBA" to R.string.idl_date_of_expiry,
|
||||
"DCS" to R.string.idl_customer_family_name,
|
||||
"DAC" to R.string.idl_customer_first_name,
|
||||
"DCT" to R.string.idl_customer_first_name_alt,
|
||||
"DAD" to R.string.idl_customer_middle_name,
|
||||
"DBD" to R.string.idl_date_of_issue,
|
||||
"DBB" to R.string.idl_date_of_birth,
|
||||
"DBC" to R.string.idl_sex,
|
||||
"DAY" to R.string.idl_eye_color,
|
||||
"DAU" to R.string.idl_body_height,
|
||||
"DAG" to R.string.idl_address_street,
|
||||
"DAI" to R.string.idl_address_city,
|
||||
"DAJ" to R.string.idl_address_jurisdiction_code,
|
||||
"DAK" to R.string.idl_address_postal_code,
|
||||
"DAQ" to R.string.idl_customer_id_number,
|
||||
"DCF" to R.string.idl_document_discriminator,
|
||||
"DCG" to R.string.idl_country,
|
||||
"DDE" to R.string.idl_family_name_truncation,
|
||||
"DDF" to R.string.idl_first_name_truncation,
|
||||
"DDG" to R.string.idl_middle_name_truncation,
|
||||
"DAH" to R.string.idl_address_street_2,
|
||||
"DAZ" to R.string.idl_hair_color,
|
||||
"DCI" to R.string.idl_place_of_birth,
|
||||
"DCJ" to R.string.idl_audit_information,
|
||||
"DCK" to R.string.idl_inventory_control_number,
|
||||
"DBN" to R.string.idl_alias_family_name,
|
||||
"DBG" to R.string.idl_alias_given_name,
|
||||
"DBS" to R.string.idl_alias_suffix_name,
|
||||
"DCU" to R.string.idl_name_suffix,
|
||||
"DCE" to R.string.idl_weight_range,
|
||||
"DCL" to R.string.idl_ethnicity,
|
||||
"DCM" to R.string.idl_standard_vehicle_classification,
|
||||
"DCN" to R.string.idl_standard_endorsement_code,
|
||||
"DCO" to R.string.idl_standard_restriction_code,
|
||||
"DCP" to R.string.idl_vehicle_classification_description,
|
||||
"DCQ" to R.string.idl_endorsement_code_description,
|
||||
"DCR" to R.string.idl_restriction_code_description,
|
||||
"DDA" to R.string.idl_compliance_type,
|
||||
"DDB" to R.string.idl_card_revision_date,
|
||||
"DDC" to R.string.idl_hazmat_endorsement_expiration_date,
|
||||
"DDD" to R.string.idl_limited_duration_document_indicator,
|
||||
"DAW" to R.string.idl_weight_pounds,
|
||||
"DAX" to R.string.idl_weight_kilograms,
|
||||
"DDH" to R.string.idl_under_18_until_date,
|
||||
"DDI" to R.string.idl_under_19_until_date,
|
||||
"DDJ" to R.string.idl_under_21_until_date,
|
||||
"DDK" to R.string.idl_organ_donor_indicator,
|
||||
"DDL" to R.string.idl_veteran_indicator
|
||||
)
|
||||
|
||||
fun Context.idlToRes(id: String): String {
|
||||
val resId = idToLabel[id]
|
||||
return if (resId != null) {
|
||||
getString(resId)
|
||||
} else {
|
||||
id
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package de.markusfisch.android.binaryeye.content
|
||||
|
||||
data class IdlInfo(
|
||||
var iin: String? = null,
|
||||
val elements: MutableMap<String, String> = linkedMapOf()
|
||||
)
|
||||
|
||||
object IdlParser {
|
||||
private fun resolveSex(value: String): String = when (value.firstOrNull()) {
|
||||
'1' -> "M"
|
||||
'2' -> "F"
|
||||
'9' -> "X"
|
||||
else -> value
|
||||
}
|
||||
|
||||
private fun skipWhitespace(s: String, p: Int): Int {
|
||||
var i = p
|
||||
while (i < s.length && s[i].isWhitespace()) {
|
||||
++i;
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
private fun findSubType(s: String, idlInfo: IdlInfo): String {
|
||||
val len = s.length
|
||||
var p = skipWhitespace(s, 0)
|
||||
|
||||
if (p < len && s[p] == '@') {
|
||||
while (p < len) {
|
||||
if (!s[p].isDigit()) {
|
||||
++p
|
||||
continue
|
||||
}
|
||||
var d = p
|
||||
while (d < len && s[d].isDigit()) {
|
||||
++d
|
||||
}
|
||||
if (d - p < 8 || len - d < 3) {
|
||||
p = d
|
||||
continue
|
||||
}
|
||||
|
||||
val code = s.substring(d, d + 2)
|
||||
if (code != "DL" && code != "ID") {
|
||||
p = d + 2
|
||||
continue
|
||||
}
|
||||
|
||||
idlInfo.elements["DL"] = code
|
||||
d += 2
|
||||
|
||||
while (d < len) {
|
||||
while (d < len && s[d] !in "DI") {
|
||||
++d
|
||||
}
|
||||
if (len - d < 2) break
|
||||
|
||||
val sec = s.substring(d, d + 2)
|
||||
if (sec == "DL" || sec == "ID") {
|
||||
return s.substring(d + 2)
|
||||
}
|
||||
++d
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
private fun findIIN(s: String): String? {
|
||||
val ansi = s.indexOf("ANSI").takeIf { it >= 0 } ?: return null
|
||||
var p = ansi + 4
|
||||
val len = s.length
|
||||
val whitespaceSkipped = skipWhitespace(s, p)
|
||||
if (whitespaceSkipped == p) {
|
||||
return null
|
||||
}
|
||||
p = whitespaceSkipped
|
||||
val iinStart = p
|
||||
var digits = 0
|
||||
while (digits < 6 && p < len && s[p].isDigit()) {
|
||||
++p
|
||||
++digits
|
||||
}
|
||||
return s.substring(iinStart, p)
|
||||
}
|
||||
|
||||
fun parse(s: String?): IdlInfo? {
|
||||
if (s.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val idlInfo = IdlInfo().apply {
|
||||
iin = findIIN(s)
|
||||
}
|
||||
val data = findSubType(s, idlInfo)
|
||||
val len = data.length
|
||||
var start = 0
|
||||
|
||||
for (p in 0..len) {
|
||||
if (p < len && data[p] > '\u001f') {
|
||||
continue
|
||||
}
|
||||
|
||||
if (p - start > 3) {
|
||||
val vp = start + 3
|
||||
val key = data.substring(start, vp)
|
||||
|
||||
if (key.matches(Regex("[DZ][A-Z]{2}"))) {
|
||||
var value = data.substring(vp, p)
|
||||
if (key == "DBC") {
|
||||
value = resolveSex(value)
|
||||
}
|
||||
idlInfo.elements[key] = value.trim()
|
||||
}
|
||||
}
|
||||
start = p + 1
|
||||
}
|
||||
|
||||
return if (idlInfo.elements.isEmpty()) null else idlInfo
|
||||
}
|
||||
}
|
||||
@@ -33,12 +33,20 @@ fun Context.startIntent(intent: Intent): Boolean = try {
|
||||
// necessary.
|
||||
startActivity(intent)
|
||||
true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
false
|
||||
}
|
||||
|
||||
fun Context.openUrl(url: String, silent: Boolean = false): Boolean {
|
||||
val intent = Intent(Intent.ACTION_VIEW, url.parseAndNormalizeUri())
|
||||
return openUri(url.parseAndNormalizeUri(), silent)
|
||||
}
|
||||
|
||||
fun Context.openUri(uri: Uri, silent: Boolean = false): Boolean {
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri).apply {
|
||||
if (uri.scheme == "content") {
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
}
|
||||
return if (silent) {
|
||||
startIntent(intent)
|
||||
} else {
|
||||
@@ -91,7 +99,7 @@ fun <T> Context.shareAsFile(content: T, fileName: String) {
|
||||
FileOutputStream(file).use {
|
||||
it.write(bytes)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
} catch (_: IOException) {
|
||||
toast(R.string.error_saving_file)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package de.markusfisch.android.binaryeye.database
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import de.markusfisch.android.binaryeye.content.toHexString
|
||||
import de.markusfisch.android.binaryeye.io.writeExternalFile
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.OutputStream
|
||||
@@ -32,7 +31,8 @@ private fun exportCsv(
|
||||
val columns = arrayOf(
|
||||
Database.SCANS_DATETIME,
|
||||
Database.SCANS_FORMAT,
|
||||
Database.SCANS_CONTENT,
|
||||
Database.SCANS_NAME,
|
||||
Database.SCANS_TEXT,
|
||||
Database.SCANS_ERROR_CORRECTION_LEVEL,
|
||||
Database.SCANS_VERSION,
|
||||
Database.SCANS_SEQUENCE_SIZE,
|
||||
@@ -46,7 +46,7 @@ private fun exportCsv(
|
||||
val indices = columns.map {
|
||||
cursor.getColumnIndex(it)
|
||||
}
|
||||
val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
|
||||
val contentIndex = cursor.getColumnIndex(Database.SCANS_TEXT)
|
||||
val rawIndex = cursor.getColumnIndex(Database.SCANS_RAW)
|
||||
outputStream.write(
|
||||
columns.joinToString(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.markusfisch.android.binaryeye.database
|
||||
|
||||
import android.database.Cursor
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp
|
||||
|
||||
// Overwrite Kotlin's ".use" function for Cursor because Cursor cannot
|
||||
// be cast to Closeable below API level 16. This should be removed when
|
||||
@@ -30,3 +31,18 @@ fun Cursor.getBlob(name: String): ByteArray? {
|
||||
val idx = getColumnIndex(name)
|
||||
return if (idx < 0) null else getBlob(idx)
|
||||
}
|
||||
|
||||
fun Cursor.getBitMatrix(
|
||||
widthName: String,
|
||||
heightName: String,
|
||||
dataName: String
|
||||
): ZxingCpp.BitMatrix? {
|
||||
val width = getInt(widthName)
|
||||
val height = getInt(heightName)
|
||||
val data = getBlob(dataName)
|
||||
return if (width > 0 && height > 0 && data != null) {
|
||||
ZxingCpp.BitMatrix(width, height, data)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.preference.Preferences
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
|
||||
|
||||
class Database {
|
||||
@@ -15,12 +16,12 @@ class Database {
|
||||
db = OpenHelper(context).writableDatabase
|
||||
}
|
||||
|
||||
fun getScans(query: String? = null): Cursor? = db.rawQuery(
|
||||
fun getScans(query: String? = null): Cursor = db.rawQuery(
|
||||
"""SELECT
|
||||
$SCANS_ID,
|
||||
$SCANS_DATETIME,
|
||||
$SCANS_NAME,
|
||||
$SCANS_CONTENT,
|
||||
$SCANS_TEXT,
|
||||
$SCANS_FORMAT
|
||||
FROM $SCANS
|
||||
${getWhereClause(query)}
|
||||
@@ -28,12 +29,12 @@ class Database {
|
||||
""".trimMargin(), getWhereArguments(query)
|
||||
)
|
||||
|
||||
fun getScansDetailed(query: String? = null): Cursor? = db.rawQuery(
|
||||
fun getScansDetailed(query: String? = null): Cursor = db.rawQuery(
|
||||
"""SELECT
|
||||
$SCANS_ID,
|
||||
$SCANS_DATETIME,
|
||||
$SCANS_NAME,
|
||||
$SCANS_CONTENT,
|
||||
$SCANS_TEXT,
|
||||
$SCANS_RAW,
|
||||
$SCANS_FORMAT,
|
||||
$SCANS_ERROR_CORRECTION_LEVEL,
|
||||
@@ -55,7 +56,7 @@ class Database {
|
||||
query: String?,
|
||||
prefix: String = "WHERE"
|
||||
) = if (query?.isNotEmpty() == true) {
|
||||
"""$prefix $SCANS_CONTENT LIKE ?
|
||||
"""$prefix $SCANS_TEXT LIKE ?
|
||||
OR $SCANS_NAME LIKE ?"""
|
||||
} else {
|
||||
""
|
||||
@@ -70,12 +71,12 @@ class Database {
|
||||
null
|
||||
}
|
||||
|
||||
fun getScansDetailed(ids: LongArray): Cursor? = db.rawQuery(
|
||||
fun getScansDetailed(ids: LongArray): Cursor = db.rawQuery(
|
||||
"""SELECT
|
||||
$SCANS_ID,
|
||||
$SCANS_DATETIME,
|
||||
$SCANS_NAME,
|
||||
$SCANS_CONTENT,
|
||||
$SCANS_TEXT,
|
||||
$SCANS_RAW,
|
||||
$SCANS_FORMAT,
|
||||
$SCANS_ERROR_CORRECTION_LEVEL,
|
||||
@@ -88,9 +89,9 @@ class Database {
|
||||
$SCANS_GTIN_PRICE,
|
||||
$SCANS_GTIN_ISSUE_NUMBER
|
||||
FROM $SCANS
|
||||
WHERE $SCANS_ID IN (?)
|
||||
WHERE $SCANS_ID IN (${ids.joinToString(",")})
|
||||
ORDER BY $SCANS_DATETIME DESC
|
||||
""".trimMargin(), arrayOf(ids.joinToString(","))
|
||||
""".trimMargin(), null
|
||||
)
|
||||
|
||||
fun getScan(id: Long): Scan? = db.rawQuery(
|
||||
@@ -98,11 +99,15 @@ class Database {
|
||||
$SCANS_ID,
|
||||
$SCANS_DATETIME,
|
||||
$SCANS_NAME,
|
||||
$SCANS_CONTENT,
|
||||
$SCANS_TEXT,
|
||||
$SCANS_RAW,
|
||||
$SCANS_FORMAT,
|
||||
$SCANS_ERROR_CORRECTION_LEVEL,
|
||||
$SCANS_VERSION,
|
||||
$SCANS_DATA_MASK,
|
||||
$SCANS_SYMBOL_WIDTH,
|
||||
$SCANS_SYMBOL_HEIGHT,
|
||||
$SCANS_SYMBOL_DATA,
|
||||
$SCANS_SEQUENCE_SIZE,
|
||||
$SCANS_SEQUENCE_INDEX,
|
||||
$SCANS_SEQUENCE_ID,
|
||||
@@ -113,14 +118,20 @@ class Database {
|
||||
FROM $SCANS
|
||||
WHERE $SCANS_ID = ?
|
||||
""".trimMargin(), arrayOf("$id")
|
||||
)?.use {
|
||||
).use {
|
||||
if (it.moveToFirst()) {
|
||||
Scan(
|
||||
it.getString(SCANS_CONTENT),
|
||||
it.getString(SCANS_TEXT),
|
||||
it.getBlob(SCANS_RAW),
|
||||
BarcodeFormat.valueOf(it.getString(SCANS_FORMAT)),
|
||||
it.getString(SCANS_ERROR_CORRECTION_LEVEL),
|
||||
it.getString(SCANS_VERSION),
|
||||
it.getInt(SCANS_DATA_MASK),
|
||||
it.getBitMatrix(
|
||||
SCANS_SYMBOL_WIDTH,
|
||||
SCANS_SYMBOL_HEIGHT,
|
||||
SCANS_SYMBOL_DATA
|
||||
),
|
||||
it.getInt(SCANS_SEQUENCE_SIZE),
|
||||
it.getInt(SCANS_SEQUENCE_INDEX),
|
||||
it.getString(SCANS_SEQUENCE_ID),
|
||||
@@ -129,46 +140,66 @@ class Database {
|
||||
it.getString(SCANS_GTIN_PRICE),
|
||||
it.getString(SCANS_GTIN_ISSUE_NUMBER),
|
||||
it.getString(SCANS_DATETIME),
|
||||
it.getLong(SCANS_ID)
|
||||
it.getLong(SCANS_ID),
|
||||
it.getString(SCANS_NAME),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun insertScan(scan: Scan): Long = db.insert(
|
||||
SCANS,
|
||||
null,
|
||||
ContentValues().apply {
|
||||
put(SCANS_DATETIME, scan.dateTime)
|
||||
put(SCANS_CONTENT, scan.content)
|
||||
if (scan.raw != null) {
|
||||
put(SCANS_RAW, scan.raw)
|
||||
}
|
||||
put(SCANS_FORMAT, scan.format.name)
|
||||
scan.errorCorrectionLevel?.let {
|
||||
put(SCANS_ERROR_CORRECTION_LEVEL, it)
|
||||
}
|
||||
put(SCANS_VERSION, scan.version)
|
||||
put(SCANS_SEQUENCE_SIZE, scan.sequenceSize)
|
||||
put(SCANS_SEQUENCE_INDEX, scan.sequenceIndex)
|
||||
put(SCANS_SEQUENCE_ID, scan.sequenceId)
|
||||
scan.country?.let { put(SCANS_GTIN_COUNTRY, it) }
|
||||
scan.addOn?.let { put(SCANS_GTIN_ADD_ON, it) }
|
||||
scan.price?.let { put(SCANS_GTIN_PRICE, it) }
|
||||
scan.issueNumber?.let { put(SCANS_GTIN_ISSUE_NUMBER, it) }
|
||||
if (prefs.ignoreConsecutiveDuplicates) {
|
||||
fun insertScan(scan: Scan): Long {
|
||||
when (prefs.ignoreDuplicates()) {
|
||||
Preferences.Companion.IgnoreDuplicates.Consecutive -> {
|
||||
val id = getIdOfLastScan(
|
||||
get(SCANS_CONTENT) as String,
|
||||
get(SCANS_RAW) as ByteArray?,
|
||||
scan.text,
|
||||
scan.raw,
|
||||
scan.format
|
||||
)
|
||||
if (id > 0L) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
Preferences.Companion.IgnoreDuplicates.Any -> {
|
||||
val id = getIdOfScanByContent(scan.text, scan.format)
|
||||
if (id > 0L) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
)
|
||||
return db.insert(
|
||||
SCANS,
|
||||
null,
|
||||
ContentValues().apply {
|
||||
put(SCANS_DATETIME, scan.dateTime)
|
||||
put(SCANS_TEXT, scan.text)
|
||||
if (scan.raw != null) {
|
||||
put(SCANS_RAW, scan.raw)
|
||||
}
|
||||
put(SCANS_FORMAT, scan.format.name)
|
||||
scan.errorCorrectionLevel?.let {
|
||||
put(SCANS_ERROR_CORRECTION_LEVEL, it)
|
||||
}
|
||||
put(SCANS_VERSION, scan.version)
|
||||
put(SCANS_DATA_MASK, scan.dataMask)
|
||||
if (scan.symbol != null) {
|
||||
put(SCANS_SYMBOL_WIDTH, scan.symbol.width)
|
||||
put(SCANS_SYMBOL_HEIGHT, scan.symbol.height)
|
||||
put(SCANS_SYMBOL_DATA, scan.symbol.data)
|
||||
}
|
||||
put(SCANS_SEQUENCE_SIZE, scan.sequenceSize)
|
||||
put(SCANS_SEQUENCE_INDEX, scan.sequenceIndex)
|
||||
put(SCANS_SEQUENCE_ID, scan.sequenceId)
|
||||
scan.country?.let { put(SCANS_GTIN_COUNTRY, it) }
|
||||
scan.addOn?.let { put(SCANS_GTIN_ADD_ON, it) }
|
||||
scan.price?.let { put(SCANS_GTIN_PRICE, it) }
|
||||
scan.issueNumber?.let { put(SCANS_GTIN_ISSUE_NUMBER, it) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun getIdOfLastScan(
|
||||
content: String,
|
||||
@@ -177,17 +208,17 @@ class Database {
|
||||
): Long = db.rawQuery(
|
||||
"""SELECT
|
||||
$SCANS_ID,
|
||||
$SCANS_CONTENT,
|
||||
$SCANS_TEXT,
|
||||
$SCANS_RAW,
|
||||
$SCANS_FORMAT
|
||||
FROM $SCANS
|
||||
ORDER BY $SCANS_ID DESC
|
||||
LIMIT 1
|
||||
""".trimMargin(), null
|
||||
)?.use {
|
||||
).use {
|
||||
if (it.count > 0 &&
|
||||
it.moveToFirst() &&
|
||||
it.getString(SCANS_CONTENT) == content &&
|
||||
it.getString(SCANS_TEXT) == content &&
|
||||
(raw == null || it.getBlob(SCANS_RAW)
|
||||
?.contentEquals(raw) == true) &&
|
||||
it.getString(SCANS_FORMAT) == format.name
|
||||
@@ -196,7 +227,26 @@ class Database {
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
} ?: 0L
|
||||
}
|
||||
|
||||
private fun getIdOfScanByContent(
|
||||
content: String,
|
||||
format: BarcodeFormat
|
||||
): Long = db.rawQuery(
|
||||
"""SELECT
|
||||
$SCANS_ID
|
||||
FROM $SCANS
|
||||
WHERE $SCANS_TEXT = ?
|
||||
AND $SCANS_FORMAT = ?
|
||||
LIMIT 1
|
||||
""".trimMargin(), arrayOf(content, format.name)
|
||||
).use {
|
||||
if (it.moveToFirst()) {
|
||||
it.getLong(SCANS_ID)
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
}
|
||||
|
||||
fun removeScan(id: Long) {
|
||||
db.delete(SCANS, "$SCANS_ID = ?", arrayOf("$id"))
|
||||
@@ -213,7 +263,7 @@ class Database {
|
||||
}
|
||||
|
||||
private class OpenHelper(context: Context) :
|
||||
SQLiteOpenHelper(context, FILE_NAME, null, 6) {
|
||||
SQLiteOpenHelper(context, FILE_NAME, null, 8) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
db.createScans()
|
||||
}
|
||||
@@ -238,6 +288,12 @@ class Database {
|
||||
if (oldVersion < 6) {
|
||||
db.migrateToVersionString()
|
||||
}
|
||||
if (oldVersion < 7) {
|
||||
db.addDataMaskColumn()
|
||||
}
|
||||
if (oldVersion < 8) {
|
||||
db.addSymbolColumns()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,12 +303,16 @@ class Database {
|
||||
const val SCANS_ID = "_id"
|
||||
const val SCANS_DATETIME = "_datetime"
|
||||
const val SCANS_NAME = "name"
|
||||
const val SCANS_CONTENT = "content"
|
||||
const val SCANS_TEXT = "content"
|
||||
const val SCANS_RAW = "raw"
|
||||
const val SCANS_FORMAT = "format"
|
||||
const val SCANS_ERROR_CORRECTION_LEVEL = "error_correction_level"
|
||||
const val SCANS_VERSION_NUMBER = "version_number"
|
||||
const val SCANS_VERSION = "version"
|
||||
const val SCANS_DATA_MASK = "data_mask"
|
||||
const val SCANS_SYMBOL_WIDTH = "symbol_width"
|
||||
const val SCANS_SYMBOL_HEIGHT = "symbol_height"
|
||||
const val SCANS_SYMBOL_DATA = "symbol_data"
|
||||
const val SCANS_ISSUE_NUMBER = "issue_number"
|
||||
const val SCANS_ORIENTATION = "orientation"
|
||||
const val SCANS_OTHER_META_DATA = "other_meta_data"
|
||||
@@ -275,11 +335,15 @@ class Database {
|
||||
$SCANS_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
$SCANS_DATETIME DATETIME NOT NULL,
|
||||
$SCANS_NAME TEXT,
|
||||
$SCANS_CONTENT TEXT NOT NULL,
|
||||
$SCANS_TEXT TEXT NOT NULL,
|
||||
$SCANS_RAW BLOB,
|
||||
$SCANS_FORMAT TEXT NOT NULL,
|
||||
$SCANS_ERROR_CORRECTION_LEVEL TEXT,
|
||||
$SCANS_VERSION TEXT,
|
||||
$SCANS_DATA_MASK INTEGER,
|
||||
$SCANS_SYMBOL_WIDTH INTEGER,
|
||||
$SCANS_SYMBOL_HEIGHT INTEGER,
|
||||
$SCANS_SYMBOL_DATA BLOB,
|
||||
$SCANS_SEQUENCE_SIZE INTEGER,
|
||||
$SCANS_SEQUENCE_INDEX INTEGER,
|
||||
$SCANS_SEQUENCE_ID TEXT,
|
||||
@@ -307,7 +371,7 @@ class Database {
|
||||
}
|
||||
|
||||
private fun SQLiteDatabase.addNameColumn() {
|
||||
execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_NAME TEXT".trimMargin())
|
||||
execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_NAME TEXT")
|
||||
}
|
||||
|
||||
private fun SQLiteDatabase.migrateToZxingCpp() {
|
||||
@@ -331,5 +395,16 @@ class Database {
|
||||
execSQL("ALTER TABLE $SCANS ADD $SCANS_VERSION TEXT")
|
||||
execSQL("UPDATE $SCANS SET $SCANS_VERSION = $SCANS_VERSION_NUMBER")
|
||||
}
|
||||
|
||||
private fun SQLiteDatabase.addDataMaskColumn() {
|
||||
execSQL("ALTER TABLE $SCANS ADD $SCANS_DATA_MASK INTEGER")
|
||||
execSQL("UPDATE $SCANS SET $SCANS_DATA_MASK = -1")
|
||||
}
|
||||
|
||||
private fun SQLiteDatabase.addSymbolColumns() {
|
||||
execSQL("ALTER TABLE $SCANS ADD $SCANS_SYMBOL_WIDTH INTEGER")
|
||||
execSQL("ALTER TABLE $SCANS ADD $SCANS_SYMBOL_HEIGHT INTEGER")
|
||||
execSQL("ALTER TABLE $SCANS ADD $SCANS_SYMBOL_DATA BLOB")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package de.markusfisch.android.binaryeye.database
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import de.markusfisch.android.binaryeye.content.toHexString
|
||||
import de.markusfisch.android.binaryeye.io.writeExternalFile
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
@@ -24,7 +23,8 @@ fun Cursor.exportJson(): String? {
|
||||
val columns = arrayOf(
|
||||
Database.SCANS_DATETIME,
|
||||
Database.SCANS_FORMAT,
|
||||
Database.SCANS_CONTENT,
|
||||
Database.SCANS_NAME,
|
||||
Database.SCANS_TEXT,
|
||||
Database.SCANS_ERROR_CORRECTION_LEVEL,
|
||||
Database.SCANS_VERSION,
|
||||
Database.SCANS_SEQUENCE_SIZE,
|
||||
@@ -38,7 +38,7 @@ fun Cursor.exportJson(): String? {
|
||||
val indices = columns.map {
|
||||
Pair(getColumnIndex(it), it)
|
||||
}
|
||||
val contentIndex = getColumnIndex(Database.SCANS_CONTENT)
|
||||
val contentIndex = getColumnIndex(Database.SCANS_TEXT)
|
||||
val rawIndex = getColumnIndex(Database.SCANS_RAW)
|
||||
val root = JSONArray()
|
||||
do {
|
||||
@@ -69,7 +69,7 @@ private fun Cursor.toJsonObject(
|
||||
obj.put(it.second, value ?: "")
|
||||
}
|
||||
obj
|
||||
} catch (e: JSONException) {
|
||||
} catch (_: JSONException) {
|
||||
obj
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
package de.markusfisch.android.binaryeye.database
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.text.format.DateFormat
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.ContentType
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BitMatrix
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.ContentType
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.Result
|
||||
import java.util.Locale
|
||||
|
||||
data class Scan(
|
||||
val content: String,
|
||||
val text: String,
|
||||
val raw: ByteArray?,
|
||||
val format: BarcodeFormat,
|
||||
val errorCorrectionLevel: String? = null,
|
||||
val version: String? = null,
|
||||
val dataMask: Int = -1,
|
||||
val symbol: BitMatrix? = null,
|
||||
val sequenceSize: Int = -1,
|
||||
val sequenceIndex: Int = -1,
|
||||
val sequenceId: String = "",
|
||||
@@ -24,7 +26,8 @@ data class Scan(
|
||||
val price: String? = null,
|
||||
val issueNumber: String? = null,
|
||||
val dateTime: String = getDateTime(),
|
||||
var id: Long = 0L
|
||||
var id: Long = 0L,
|
||||
val label: String? = null
|
||||
) : Parcelable {
|
||||
// Needs to be overwritten manually, as ByteArray is an array and
|
||||
// this isn't handled well by Kotlin.
|
||||
@@ -40,12 +43,14 @@ data class Scan(
|
||||
|
||||
return id == other.id &&
|
||||
dateTime == other.dateTime &&
|
||||
content == other.content &&
|
||||
text == other.text &&
|
||||
((raw == null && other.raw == null) ||
|
||||
(raw != null && other.raw != null && raw.contentEquals(other.raw))) &&
|
||||
(raw != null && other.raw != null &&
|
||||
raw.contentEquals(other.raw))) &&
|
||||
format == other.format &&
|
||||
errorCorrectionLevel == other.errorCorrectionLevel &&
|
||||
version == other.version &&
|
||||
dataMask == other.dataMask &&
|
||||
sequenceSize == other.sequenceSize &&
|
||||
sequenceIndex == other.sequenceIndex &&
|
||||
sequenceId == other.sequenceId &&
|
||||
@@ -60,11 +65,12 @@ data class Scan(
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + dateTime.hashCode()
|
||||
result = 31 * result + content.hashCode()
|
||||
result = 31 * result + text.hashCode()
|
||||
result = 31 * result + (raw?.contentHashCode() ?: 0)
|
||||
result = 31 * result + format.hashCode()
|
||||
result = 31 * result + (errorCorrectionLevel?.hashCode() ?: 0)
|
||||
result = 31 * result + version.hashCode()
|
||||
result = 31 * result + dataMask
|
||||
result = 31 * result + sequenceSize
|
||||
result = 31 * result + sequenceIndex
|
||||
result = 31 * result + sequenceId.hashCode()
|
||||
@@ -76,11 +82,13 @@ data class Scan(
|
||||
}
|
||||
|
||||
private constructor(parcel: Parcel) : this(
|
||||
content = parcel.readString() ?: "",
|
||||
text = parcel.readString() ?: "",
|
||||
raw = parcel.readSizedByteArray(),
|
||||
format = BarcodeFormat.valueOf(parcel.readString() ?: ""),
|
||||
errorCorrectionLevel = parcel.readString(),
|
||||
version = parcel.readString(),
|
||||
dataMask = parcel.readInt(),
|
||||
symbol = parcel.readBitMatrix(),
|
||||
sequenceSize = parcel.readInt(),
|
||||
sequenceIndex = parcel.readInt(),
|
||||
sequenceId = parcel.readString() ?: "",
|
||||
@@ -89,16 +97,19 @@ data class Scan(
|
||||
price = parcel.readString(),
|
||||
issueNumber = parcel.readString(),
|
||||
dateTime = parcel.readString() ?: "",
|
||||
id = parcel.readLong()
|
||||
id = parcel.readLong(),
|
||||
label = parcel.readString()
|
||||
)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.apply {
|
||||
writeString(content)
|
||||
writeString(text)
|
||||
writeSizedByteArray(raw)
|
||||
writeString(format.name)
|
||||
writeString(errorCorrectionLevel)
|
||||
writeString(version)
|
||||
writeInt(dataMask)
|
||||
writeBitMatrix(symbol)
|
||||
writeInt(sequenceSize)
|
||||
writeInt(sequenceIndex)
|
||||
writeString(sequenceId)
|
||||
@@ -108,6 +119,7 @@ data class Scan(
|
||||
writeString(issueNumber)
|
||||
writeString(dateTime)
|
||||
writeLong(id)
|
||||
writeString(label)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,12 +141,14 @@ fun Result.toScan(): Scan {
|
||||
ContentType.ISO15434,
|
||||
ContentType.TEXT -> text
|
||||
|
||||
else -> ""
|
||||
else -> "" // Show as binary.
|
||||
},
|
||||
rawBytes,
|
||||
format,
|
||||
ecLevel,
|
||||
version,
|
||||
dataMask,
|
||||
symbol,
|
||||
sequenceSize,
|
||||
sequenceIndex,
|
||||
sequenceId,
|
||||
@@ -145,37 +159,6 @@ fun Result.toScan(): Scan {
|
||||
)
|
||||
}
|
||||
|
||||
data class Recreation(
|
||||
val format: BarcodeFormat,
|
||||
val ecLevel: Int,
|
||||
val size: Int,
|
||||
val margin: Int
|
||||
) {
|
||||
fun <T> encode(content: T): Bitmap? = try {
|
||||
ZxingCpp.encodeAsBitmap(
|
||||
content, format, size, size, margin, ecLevel
|
||||
)
|
||||
} catch (_: RuntimeException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun Scan.toRecreation(
|
||||
size: Int = 128,
|
||||
margin: Int = -1
|
||||
) = Recreation(
|
||||
format,
|
||||
when (errorCorrectionLevel) {
|
||||
"L" -> 0
|
||||
"M" -> 4
|
||||
"Q" -> 6
|
||||
"H" -> 8
|
||||
else -> -1
|
||||
},
|
||||
size,
|
||||
margin
|
||||
)
|
||||
|
||||
private fun getDateTime(
|
||||
time: Long = System.currentTimeMillis()
|
||||
) = DateFormat.format(
|
||||
@@ -187,7 +170,11 @@ private fun getDateTime(
|
||||
"yyyy-MM-dd HH:mm:ss"
|
||||
},
|
||||
time
|
||||
).toString()
|
||||
).toString() + String.format(
|
||||
Locale.getDefault(),
|
||||
":%03d",
|
||||
time % 1000
|
||||
)
|
||||
|
||||
private fun Parcel.writeSizedByteArray(array: ByteArray?) {
|
||||
val size = array?.size ?: 0
|
||||
@@ -207,3 +194,21 @@ private fun Parcel.readSizedByteArray(): ByteArray? {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Parcel.writeBitMatrix(bm: BitMatrix?) {
|
||||
writeInt(bm?.width ?: 0)
|
||||
bm?.let {
|
||||
writeInt(bm.height)
|
||||
writeSizedByteArray(bm.data)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Parcel.readBitMatrix(): BitMatrix? {
|
||||
val width = readInt()
|
||||
if (width < 1) {
|
||||
return null
|
||||
}
|
||||
val height = readInt()
|
||||
val data = readSizedByteArray() ?: return null
|
||||
return BitMatrix(width, height, data)
|
||||
}
|
||||
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.ListView
|
||||
import android.widget.RadioGroup
|
||||
import android.widget.TextView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.automation.AutomatedAction
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.view.systemBarListViewScrollListener
|
||||
|
||||
class AutomatedActionsFragment : Fragment() {
|
||||
private val actions = ArrayList<AutomatedAction>()
|
||||
|
||||
private lateinit var adapter: ActionsAdapter
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View? {
|
||||
val ac = activity ?: return null
|
||||
ac.setTitle(R.string.automated_actions)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_automated_actions,
|
||||
container,
|
||||
false
|
||||
)
|
||||
|
||||
actions.clear()
|
||||
actions.addAll(prefs.automatedActions)
|
||||
|
||||
val listView = view.findViewById<ListView>(R.id.actions)
|
||||
listView.emptyView = view.findViewById(R.id.no_actions)
|
||||
adapter = ActionsAdapter(ac, actions)
|
||||
listView.adapter = adapter
|
||||
listView.setOnScrollListener(systemBarListViewScrollListener)
|
||||
listView.setOnItemClickListener { _, _, position, _ ->
|
||||
editAction(position)
|
||||
}
|
||||
listView.setOnItemLongClickListener { _, _, position, _ ->
|
||||
confirmRemoveAction(position)
|
||||
true
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.add).setOnClickListener {
|
||||
editAction(null)
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
|
||||
listView.setPaddingFromWindowInsets()
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun editAction(position: Int?) {
|
||||
val ac = activity ?: return
|
||||
val action = position?.let { actions[it] }
|
||||
val view = ac.layoutInflater.inflate(
|
||||
R.layout.dialog_automated_action,
|
||||
null
|
||||
)
|
||||
val regexView = view.findViewById<EditText>(R.id.regex)
|
||||
val urlView = view.findViewById<EditText>(R.id.url_template)
|
||||
val typeGroup = view.findViewById<RadioGroup>(R.id.action_type)
|
||||
val urlSection = view.findViewById<View>(R.id.url_section)
|
||||
val intentSection = view.findViewById<View>(R.id.intent_section)
|
||||
val intentUriView = view.findViewById<EditText>(
|
||||
R.id.intent_uri_template
|
||||
)
|
||||
|
||||
if (action != null) {
|
||||
regexView.setText(action.pattern)
|
||||
when (action.type) {
|
||||
AutomatedAction.Type.Intent -> {
|
||||
urlView.setText(action.urlTemplate ?: "")
|
||||
}
|
||||
|
||||
AutomatedAction.Type.CustomIntent -> {
|
||||
intentUriView.setText(action.intentUriTemplate ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTypeSections(checkedId: Int) {
|
||||
val isIntent = checkedId == R.id.action_type_intent
|
||||
urlSection.visibility = if (isIntent) View.VISIBLE else View.GONE
|
||||
intentSection.visibility = if (isIntent) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
typeGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||
updateTypeSections(checkedId)
|
||||
}
|
||||
val initialTypeId = if (
|
||||
action?.type == AutomatedAction.Type.CustomIntent
|
||||
) {
|
||||
R.id.action_type_custom_intent
|
||||
} else {
|
||||
R.id.action_type_intent
|
||||
}
|
||||
typeGroup.check(initialTypeId)
|
||||
updateTypeSections(initialTypeId)
|
||||
|
||||
val dialog = AlertDialog.Builder(ac)
|
||||
.setTitle(
|
||||
if (action == null) {
|
||||
R.string.automated_action_add
|
||||
} else {
|
||||
R.string.automated_action_edit
|
||||
}
|
||||
)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.create()
|
||||
|
||||
dialog.setOnShowListener {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
addOrUpdateAction(
|
||||
position,
|
||||
regexView,
|
||||
urlView,
|
||||
typeGroup,
|
||||
intentUriView,
|
||||
dialog
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun addOrUpdateAction(
|
||||
position: Int?,
|
||||
regexView: EditText,
|
||||
urlView: EditText,
|
||||
typeGroup: RadioGroup,
|
||||
intentUriView: EditText,
|
||||
dialog: Dialog,
|
||||
) {
|
||||
val pattern = regexView.text.toString().trim()
|
||||
if (pattern.isEmpty()) {
|
||||
regexView.error = getString(R.string.automated_action_regex_required)
|
||||
return
|
||||
}
|
||||
try {
|
||||
Regex(pattern)
|
||||
} catch (_: Exception) {
|
||||
regexView.error = getString(R.string.automated_action_regex_invalid)
|
||||
return
|
||||
}
|
||||
val isIntent = typeGroup.checkedRadioButtonId == R.id.action_type_intent
|
||||
val newAction = if (isIntent) {
|
||||
val urlTemplate = urlView.text.toString().trim()
|
||||
if (urlTemplate.isEmpty()) {
|
||||
urlView.error = getString(R.string.automated_action_url_required)
|
||||
return
|
||||
}
|
||||
AutomatedAction(
|
||||
pattern = pattern,
|
||||
type = AutomatedAction.Type.Intent,
|
||||
urlTemplate = urlTemplate
|
||||
)
|
||||
} else {
|
||||
val intentUriTemplate = intentUriView.text.toString().trim()
|
||||
if (intentUriTemplate.isEmpty()) {
|
||||
intentUriView.error = getString(
|
||||
R.string.automated_action_intent_uri_required
|
||||
)
|
||||
return
|
||||
}
|
||||
if (!intentUriTemplate.startsWith(
|
||||
"intent:",
|
||||
ignoreCase = true
|
||||
)
|
||||
) {
|
||||
intentUriView.error = getString(
|
||||
R.string.automated_action_intent_uri_invalid
|
||||
)
|
||||
return
|
||||
}
|
||||
AutomatedAction(
|
||||
pattern = pattern,
|
||||
type = AutomatedAction.Type.CustomIntent,
|
||||
intentUriTemplate = intentUriTemplate
|
||||
)
|
||||
}
|
||||
if (position == null) {
|
||||
actions.add(newAction)
|
||||
} else {
|
||||
actions[position] = newAction
|
||||
}
|
||||
prefs.setAutomatedActions(actions)
|
||||
adapter.notifyDataSetChanged()
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
private fun confirmRemoveAction(position: Int) {
|
||||
val ac = activity ?: return
|
||||
AlertDialog.Builder(ac)
|
||||
.setMessage(R.string.automated_action_remove_confirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
actions.removeAt(position)
|
||||
prefs.setAutomatedActions(actions)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
private class ActionsAdapter(
|
||||
context: Context,
|
||||
private val items: List<AutomatedAction>
|
||||
) : ArrayAdapter<AutomatedAction>(
|
||||
context,
|
||||
android.R.layout.simple_list_item_2,
|
||||
android.R.id.text1,
|
||||
items
|
||||
) {
|
||||
override fun getView(
|
||||
position: Int,
|
||||
convertView: View?,
|
||||
parent: ViewGroup
|
||||
): View {
|
||||
val view = super.getView(position, convertView, parent)
|
||||
val action = items[position]
|
||||
getViewHolder(view).apply {
|
||||
title.text = action.pattern
|
||||
subtitle.text = when (action.type) {
|
||||
AutomatedAction.Type.Intent -> if (!action.urlTemplate.isNullOrEmpty()) {
|
||||
context.getString(
|
||||
R.string.automated_action_list_intent,
|
||||
action.urlTemplate
|
||||
)
|
||||
} else {
|
||||
context.getString(R.string.automated_action_list_intent_empty)
|
||||
}
|
||||
|
||||
AutomatedAction.Type.CustomIntent ->
|
||||
if (!action.intentUriTemplate.isNullOrEmpty()) {
|
||||
context.getString(
|
||||
R.string.automated_action_list_custom_intent,
|
||||
action.intentUriTemplate
|
||||
)
|
||||
} else {
|
||||
context.getString(
|
||||
R.string.automated_action_list_custom_intent_empty
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
private fun getViewHolder(
|
||||
view: View
|
||||
): ViewHolder = view.tag as ViewHolder? ?: ViewHolder(
|
||||
view.findViewById(android.R.id.text1),
|
||||
view.findViewById(android.R.id.text2),
|
||||
).also {
|
||||
view.tag = it
|
||||
}
|
||||
|
||||
private data class ViewHolder(
|
||||
val title: TextView,
|
||||
val subtitle: TextView,
|
||||
)
|
||||
}
|
||||
}
|
||||
+355
-119
@@ -4,44 +4,71 @@ import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Matrix
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.db
|
||||
import de.markusfisch.android.binaryeye.app.hasWritePermission
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.content.Barcode
|
||||
import de.markusfisch.android.binaryeye.content.BarcodeColors
|
||||
import de.markusfisch.android.binaryeye.content.BitMatrixBarcode
|
||||
import de.markusfisch.android.binaryeye.content.ContentBarcode
|
||||
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.graphics.COLOR_BLACK
|
||||
import de.markusfisch.android.binaryeye.graphics.COLOR_WHITE
|
||||
import de.markusfisch.android.binaryeye.database.toScan
|
||||
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.net.createEncodeDeeplink
|
||||
import de.markusfisch.android.binaryeye.os.getScreenBrightness
|
||||
import de.markusfisch.android.binaryeye.os.setScreenBrightness
|
||||
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.zxingcpp.ZxingCpp
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class BarcodeFragment : Fragment() {
|
||||
private enum class FileType {
|
||||
PNG, SVG, TXT
|
||||
PNG, JPG, SVG, TXT
|
||||
}
|
||||
|
||||
private val parentJob = Job()
|
||||
private val scope = CoroutineScope(Dispatchers.IO + parentJob)
|
||||
|
||||
private lateinit var imageView: ConfinedScalingImageView
|
||||
private lateinit var barcode: Barcode<*>
|
||||
private lateinit var addToHistoryItem: MenuItem
|
||||
private lateinit var brightenScreenItem: MenuItem
|
||||
|
||||
private var currentBrightness = -1f
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
@@ -62,6 +89,9 @@ class BarcodeFragment : Fragment() {
|
||||
false
|
||||
)
|
||||
|
||||
// Make `imageView` available for `onPause()`.
|
||||
imageView = view.findViewById(R.id.barcode)
|
||||
|
||||
val bitmap: Bitmap
|
||||
try {
|
||||
barcode = arguments?.toBarcode() ?: throw IllegalArgumentException(
|
||||
@@ -81,15 +111,20 @@ class BarcodeFragment : Fragment() {
|
||||
return null
|
||||
}
|
||||
|
||||
val imageView = view.findViewById<ConfinedScalingImageView>(
|
||||
R.id.barcode
|
||||
)
|
||||
imageView.setImageBitmap(bitmap)
|
||||
imageView.post {
|
||||
// Make sure to invoke this after ScalingImageView.onLayout().
|
||||
imageView.runAfterLayout = {
|
||||
val scale = prefs.previewScale
|
||||
if (scale > 0) {
|
||||
imageView.setScale(
|
||||
scale,
|
||||
bitmap.width / 2f,
|
||||
bitmap.height / 2f
|
||||
)
|
||||
}
|
||||
|
||||
imageView.minWidth = min(
|
||||
imageView.minWidth / 2f,
|
||||
barcode.size.toFloat()
|
||||
max(bitmap.width, bitmap.height).toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -101,28 +136,67 @@ class BarcodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
(view.findViewById(R.id.inset_layout) as View).setPaddingFromWindowInsets()
|
||||
imageView.doOnApplyWindowInsets { v, insets ->
|
||||
(v as ConfinedScalingImageView).insets.set(insets)
|
||||
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
|
||||
imageView.doOnApplyWindowInsets { _, insets ->
|
||||
imageView.insets.set(insets)
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun Bundle.toBarcode() = Barcode(
|
||||
getString(CONTENT_TEXT) ?: getByteArray(CONTENT_RAW) ?: throw IllegalArgumentException(
|
||||
"content cannot be null"
|
||||
),
|
||||
BarcodeFormat.valueOf(
|
||||
private fun Bundle.toBarcode(): Barcode<*> {
|
||||
val content =
|
||||
getString(CONTENT_TEXT) ?: getByteArray(CONTENT_RAW) ?: throw IllegalArgumentException(
|
||||
"content cannot be null"
|
||||
)
|
||||
val barcodeFormat = BarcodeFormat.valueOf(
|
||||
getString(FORMAT) ?: throw IllegalArgumentException(
|
||||
"format cannot be null"
|
||||
)
|
||||
),
|
||||
getInt(SIZE),
|
||||
getInt(MARGIN),
|
||||
getInt(EC_LEVEL),
|
||||
Colors.values()[getInt(COLORS)]
|
||||
)
|
||||
)
|
||||
val colors = BarcodeColors.entries[getInt(COLORS)]
|
||||
val data = getByteArray(BIT_MATRIX_DATA)
|
||||
return if (data != null) {
|
||||
BitMatrixBarcode(
|
||||
ZxingCpp.BitMatrix(
|
||||
getInt(BIT_MATRIX_WIDTH),
|
||||
getInt(BIT_MATRIX_HEIGHT),
|
||||
data
|
||||
),
|
||||
content,
|
||||
barcodeFormat,
|
||||
colors
|
||||
)
|
||||
} else {
|
||||
ContentBarcode(
|
||||
content,
|
||||
barcodeFormat,
|
||||
getInt(EC_LEVEL),
|
||||
getInt(MARGIN),
|
||||
colors
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (prefs.brightenScreen) {
|
||||
// Post to make sure brightenScreenItem is initialized.
|
||||
view?.post {
|
||||
brightenScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (currentBrightness > -1f) {
|
||||
restoreScreenBrightness()
|
||||
}
|
||||
if (imageView.isInBounds()) {
|
||||
prefs.previewScale = imageView.getScale()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
@@ -131,21 +205,53 @@ class BarcodeFragment : Fragment() {
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.fragment_barcode, menu)
|
||||
addToHistoryItem = menu.findItem(R.id.add_to_history)
|
||||
brightenScreenItem = menu.findItem(R.id.brighten_screen)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.add_to_history -> {
|
||||
readAndAddToHistory(
|
||||
barcode.bitmap(),
|
||||
barcode.format
|
||||
)
|
||||
addToHistoryItem.isVisible = false
|
||||
context.toast(R.string.added_to_history)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.copy_to_clipboard -> {
|
||||
context.apply {
|
||||
copyToClipboard(barcode.text())
|
||||
toast(R.string.copied_to_clipboard)
|
||||
}
|
||||
copyToClipboard(barcode.text())
|
||||
true
|
||||
}
|
||||
|
||||
R.id.copy_as_deeplink -> {
|
||||
copyToClipboard(deeplinkToCopy())
|
||||
true
|
||||
}
|
||||
|
||||
R.id.export_to_file -> {
|
||||
context.pickFileType(R.string.export_as) {
|
||||
askForFileNameAndSave(it)
|
||||
context.pickFileType(R.string.export_as) { fileType ->
|
||||
when (fileType) {
|
||||
FileType.PNG,
|
||||
FileType.JPG -> askForSize { size ->
|
||||
askForFileNameAndSave(fileType, size)
|
||||
}
|
||||
|
||||
else -> askForFileNameAndSave(fileType, 0)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
R.id.brighten_screen -> {
|
||||
prefs.brightenScreen = if (currentBrightness > -1f) {
|
||||
restoreScreenBrightness()
|
||||
false
|
||||
} else {
|
||||
brightenScreen()
|
||||
true
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -154,11 +260,23 @@ class BarcodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyToClipboard(text: String, isSensitive: Boolean = false) {
|
||||
activity?.apply {
|
||||
copyToClipboard(text, isSensitive)
|
||||
toast(R.string.copied_to_clipboard)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deeplinkToCopy() = createEncodeDeeplink(
|
||||
format = barcode.format.name,
|
||||
content = barcode.textOrHex(),
|
||||
)
|
||||
|
||||
private fun Context.pickFileType(
|
||||
title: Int,
|
||||
action: (FileType) -> Unit
|
||||
) {
|
||||
val fileTypes = FileType.values()
|
||||
val fileTypes = FileType.entries.toTypedArray()
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(title)
|
||||
.setItems(fileTypes.map { it.name }.toTypedArray()) { _, which ->
|
||||
@@ -169,11 +287,13 @@ class BarcodeFragment : Fragment() {
|
||||
|
||||
// Dialogs do not have a parent view.
|
||||
@SuppressLint("InflateParams")
|
||||
private fun askForFileNameAndSave(fileType: FileType) {
|
||||
private fun askForFileNameAndSave(fileType: FileType, bitmapSize: Int) {
|
||||
val ac = activity ?: return
|
||||
// Write permission is only required before Android Q.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
|
||||
!ac.hasWritePermission { askForFileNameAndSave(fileType) }
|
||||
!ac.hasWritePermission {
|
||||
askForFileNameAndSave(fileType, bitmapSize)
|
||||
}
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -191,21 +311,28 @@ class BarcodeFragment : Fragment() {
|
||||
addSuffixIfNotGiven(fileName, ".png"),
|
||||
MIME_PNG
|
||||
) {
|
||||
barcode.bitmap().saveAsPng(it)
|
||||
barcode.bitmap(bitmapSize).saveAsPng(it)
|
||||
}
|
||||
|
||||
FileType.JPG -> saveAs(
|
||||
addSuffixIfNotGiven(fileName, ".jpg"),
|
||||
MIME_JPG
|
||||
) {
|
||||
barcode.bitmap(bitmapSize).saveAsJpg(it)
|
||||
}
|
||||
|
||||
FileType.SVG -> saveAs(
|
||||
addSuffixIfNotGiven(fileName, ".svg"),
|
||||
MIME_SVG
|
||||
) { outputStream ->
|
||||
outputStream.write(barcode.svg().toByteArray())
|
||||
) {
|
||||
it.write(barcode.svg().toByteArray())
|
||||
}
|
||||
|
||||
FileType.TXT -> saveAs(
|
||||
addSuffixIfNotGiven(fileName, ".txt"),
|
||||
MIME_TXT
|
||||
) { outputStream ->
|
||||
outputStream.write(barcode.text().toByteArray())
|
||||
) {
|
||||
it.write(barcode.text().toByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,6 +341,52 @@ class BarcodeFragment : Fragment() {
|
||||
.show()
|
||||
}
|
||||
|
||||
// Dialogs do not have a parent view.
|
||||
@SuppressLint("InflateParams")
|
||||
private fun askForSize(write: (size: Int) -> Unit) {
|
||||
val ac = activity ?: return
|
||||
val view = ac.layoutInflater.inflate(R.layout.dialog_size, null)
|
||||
val sizeView = view.findViewById<TextView>(R.id.size_display)
|
||||
val sizeBarView = view.findViewById<SeekBar>(R.id.size_bar)
|
||||
sizeBarView.initSizeBar(sizeView)
|
||||
AlertDialog.Builder(ac)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
write(getSize(sizeBarView.progress))
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun SeekBar.initSizeBar(sizeView: TextView) {
|
||||
sizeView.updateSize(progress)
|
||||
setOnSeekBarChangeListener(
|
||||
object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(
|
||||
seekBar: SeekBar,
|
||||
progressValue: Int,
|
||||
fromUser: Boolean
|
||||
) {
|
||||
sizeView.updateSize(progressValue)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun TextView.updateSize(power: Int) {
|
||||
val size = getSize(power)
|
||||
text = if (size > 0) {
|
||||
getString(R.string.size_width_by_height, size, size)
|
||||
} else {
|
||||
getString(R.string.size_no_magnification)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveAs(
|
||||
fileName: String,
|
||||
mimeType: String,
|
||||
@@ -221,7 +394,9 @@ class BarcodeFragment : Fragment() {
|
||||
) {
|
||||
val ac = activity ?: return
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val message = ac.writeExternalFile(fileName, mimeType, write).toSaveResult()
|
||||
val message = ac.writeExternalFile(
|
||||
fileName, mimeType, write
|
||||
).toSaveResult()
|
||||
withContext(Dispatchers.Main) {
|
||||
ac.toast(message)
|
||||
}
|
||||
@@ -230,29 +405,42 @@ class BarcodeFragment : Fragment() {
|
||||
|
||||
private fun Context.shareAs(fileType: FileType) {
|
||||
when (fileType) {
|
||||
FileType.PNG -> share(barcode.bitmap())
|
||||
FileType.PNG -> askForSize {
|
||||
share(barcode.bitmap(it), MIME_PNG, "png")
|
||||
}
|
||||
|
||||
FileType.JPG -> askForSize {
|
||||
share(barcode.bitmap(it), MIME_JPG, "jpg")
|
||||
}
|
||||
|
||||
FileType.SVG -> shareText(barcode.svg(), MIME_SVG)
|
||||
FileType.TXT -> shareText(barcode.text())
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.share(bitmap: Bitmap) {
|
||||
private fun Context.share(bitmap: Bitmap, mimeType: String, ext: String) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val file = File(
|
||||
externalCacheDir,
|
||||
"shared_barcode.png"
|
||||
"shared_barcode.$ext"
|
||||
)
|
||||
val success = try {
|
||||
FileOutputStream(file).use {
|
||||
bitmap.saveAsPng(it)
|
||||
when (mimeType) {
|
||||
MIME_PNG -> bitmap.saveAsPng(it)
|
||||
MIME_JPG -> bitmap.saveAsJpg(it)
|
||||
else -> throw IllegalArgumentException(
|
||||
"Invalid mime type"
|
||||
)
|
||||
}
|
||||
}
|
||||
true
|
||||
} catch (e: IOException) {
|
||||
} catch (_: IOException) {
|
||||
false
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
if (success) {
|
||||
shareFile(file, "image/png")
|
||||
shareFile(file, mimeType)
|
||||
} else {
|
||||
toast(R.string.error_saving_file)
|
||||
}
|
||||
@@ -260,34 +448,93 @@ class BarcodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun brightenScreen() {
|
||||
activity?.let {
|
||||
currentBrightness = it.getScreenBrightness()
|
||||
it.setScreenBrightness(1f)
|
||||
brightenScreenItem.isChecked = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreScreenBrightness() {
|
||||
if (currentBrightness > -1f) {
|
||||
activity?.setScreenBrightness(currentBrightness)
|
||||
currentBrightness = -1f
|
||||
brightenScreenItem.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BIT_MATRIX_WIDTH = "bit_matrix_width"
|
||||
private const val BIT_MATRIX_HEIGHT = "bit_matrix_height"
|
||||
private const val BIT_MATRIX_DATA = "bit_matrix_data"
|
||||
private const val CONTENT_TEXT = "content_text"
|
||||
private const val CONTENT_RAW = "content_raw"
|
||||
private const val FORMAT = "format"
|
||||
private const val SIZE = "size"
|
||||
private const val MARGIN = "margin"
|
||||
private const val EC_LEVEL = "ec_level"
|
||||
private const val MARGIN = "margin"
|
||||
private const val COLORS = "colors"
|
||||
private const val MIME_PNG = "image/png"
|
||||
private const val MIME_JPG = "image/jpeg"
|
||||
private const val MIME_SVG = "image/svg+xmg"
|
||||
private const val MIME_TXT = "text/plain"
|
||||
|
||||
fun newInstance(
|
||||
barcode: Barcode<*>,
|
||||
colors: Int = 0
|
||||
): Fragment {
|
||||
val args = Bundle()
|
||||
when (barcode) {
|
||||
is ContentBarcode -> args.putEcLevelMargin(
|
||||
barcode.ecLevel,
|
||||
barcode.margin
|
||||
)
|
||||
|
||||
is BitMatrixBarcode -> {
|
||||
args.putInt(BIT_MATRIX_WIDTH, barcode.bitMatrix.width)
|
||||
args.putInt(BIT_MATRIX_HEIGHT, barcode.bitMatrix.height)
|
||||
args.putByteArray(BIT_MATRIX_DATA, barcode.bitMatrix.data)
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("unknown barcode")
|
||||
}
|
||||
args.putContentFormatColors(
|
||||
barcode.content,
|
||||
barcode.format,
|
||||
colors
|
||||
)
|
||||
val fragment = BarcodeFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
|
||||
fun <T> newInstance(
|
||||
content: T,
|
||||
format: BarcodeFormat,
|
||||
size: Int,
|
||||
margin: Int,
|
||||
ecLevel: Int = -1,
|
||||
margin: Int = -1,
|
||||
colors: Int = 0
|
||||
): Fragment {
|
||||
val args = Bundle()
|
||||
args.putContentFormatColors(content, format, colors)
|
||||
args.putEcLevelMargin(ecLevel, margin)
|
||||
val fragment = BarcodeFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
|
||||
private fun <T> Bundle.putContentFormatColors(
|
||||
content: T,
|
||||
format: BarcodeFormat,
|
||||
colors: Int
|
||||
) {
|
||||
when (content) {
|
||||
is String -> {
|
||||
args.putString(CONTENT_TEXT, content)
|
||||
putString(CONTENT_TEXT, content)
|
||||
}
|
||||
|
||||
is ByteArray -> {
|
||||
args.putByteArray(CONTENT_RAW, content)
|
||||
putByteArray(CONTENT_RAW, content)
|
||||
}
|
||||
|
||||
else -> {
|
||||
@@ -296,82 +543,71 @@ class BarcodeFragment : Fragment() {
|
||||
)
|
||||
}
|
||||
}
|
||||
args.putString(FORMAT, format.name)
|
||||
args.putInt(SIZE, size)
|
||||
args.putInt(MARGIN, margin)
|
||||
args.putInt(EC_LEVEL, ecLevel)
|
||||
args.putInt(COLORS, colors)
|
||||
val fragment = BarcodeFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
putString(FORMAT, format.name)
|
||||
putInt(COLORS, colors)
|
||||
}
|
||||
|
||||
private fun Bundle.putEcLevelMargin(
|
||||
ecLevel: Int,
|
||||
margin: Int
|
||||
) {
|
||||
putInt(EC_LEVEL, ecLevel)
|
||||
putInt(MARGIN, margin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class Barcode<T>(
|
||||
val content: T,
|
||||
val format: BarcodeFormat,
|
||||
val size: Int,
|
||||
val margin: Int,
|
||||
val ecLevel: Int,
|
||||
val colors: Colors
|
||||
) {
|
||||
private var _bitmap: Bitmap? = null
|
||||
fun bitmap(): Bitmap {
|
||||
val b = _bitmap ?: ZxingCpp.encodeAsBitmap(
|
||||
content, format, size, size, margin, ecLevel,
|
||||
setColor = colors.foregroundColor(),
|
||||
unsetColor = colors.backgroundColor()
|
||||
)
|
||||
_bitmap = b
|
||||
return b
|
||||
}
|
||||
|
||||
private var _svg: String? = null
|
||||
fun svg(): String {
|
||||
val s = _svg ?: ZxingCpp.encodeAsSvg(
|
||||
content, format, margin, ecLevel
|
||||
)
|
||||
_svg = s
|
||||
return s
|
||||
}
|
||||
|
||||
private var _text: String? = null
|
||||
fun text(): String {
|
||||
val t = _text ?: ZxingCpp.encodeAsText(
|
||||
content, format, margin, ecLevel,
|
||||
inverted = colors == Colors.BLACK_ON_WHITE
|
||||
)
|
||||
_text = t
|
||||
return t
|
||||
private fun ImageView.setScale(scale: Float, x: Float, y: Float) {
|
||||
imageMatrix = Matrix().apply {
|
||||
setTranslate(-x, -y)
|
||||
postScale(scale, scale)
|
||||
postTranslate(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
private enum class Colors {
|
||||
BLACK_ON_WHITE,
|
||||
WHITE_ON_BLACK,
|
||||
BLACK_ON_TRANSPARENT,
|
||||
WHITE_ON_TRANSPARENT;
|
||||
|
||||
fun foregroundColor(): Int = when (this) {
|
||||
BLACK_ON_WHITE,
|
||||
BLACK_ON_TRANSPARENT -> COLOR_BLACK
|
||||
|
||||
WHITE_ON_BLACK,
|
||||
WHITE_ON_TRANSPARENT -> COLOR_WHITE
|
||||
}
|
||||
|
||||
fun backgroundColor(): Int = when (this) {
|
||||
BLACK_ON_WHITE -> COLOR_WHITE
|
||||
WHITE_ON_BLACK -> COLOR_BLACK
|
||||
BLACK_ON_TRANSPARENT,
|
||||
WHITE_ON_TRANSPARENT -> 0
|
||||
}
|
||||
private fun ImageView.getScale(): Float {
|
||||
val values = FloatArray(9)
|
||||
imageMatrix.getValues(values)
|
||||
return values[Matrix.MSCALE_X]
|
||||
}
|
||||
|
||||
private fun Bitmap.saveAsPng(outputStream: OutputStream, quality: Int = 90) =
|
||||
compress(Bitmap.CompressFormat.PNG, quality, outputStream)
|
||||
|
||||
private fun Bitmap.saveAsJpg(outputStream: OutputStream, quality: Int = 90) =
|
||||
compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
|
||||
|
||||
private val fileNameCharacters = "[^A-Za-z0-9]".toRegex()
|
||||
private fun encodeFileName(name: String): String =
|
||||
fileNameCharacters.replace(name, "_").take(16).trim('_').lowercase(Locale.getDefault())
|
||||
private fun encodeFileName(name: String): String = fileNameCharacters
|
||||
.replace(name, "_")
|
||||
.take(16)
|
||||
.trim('_')
|
||||
.lowercase(Locale.getDefault())
|
||||
|
||||
private fun getSize(step: Int) = when (step) {
|
||||
0 -> 0
|
||||
else -> 128 shl (step - 1)
|
||||
}
|
||||
|
||||
private fun readAndAddToHistory(
|
||||
bitmap: Bitmap,
|
||||
format: BarcodeFormat
|
||||
) {
|
||||
ZxingCpp.readBitmap(
|
||||
bitmap,
|
||||
0, 0,
|
||||
bitmap.width, bitmap.height,
|
||||
0,
|
||||
ZxingCpp.ReaderOptions(
|
||||
tryHarder = true,
|
||||
tryRotate = true,
|
||||
tryInvert = true,
|
||||
tryDownscale = true,
|
||||
maxNumberOfSymbols = 1,
|
||||
formats = setOf(format)
|
||||
)
|
||||
)?.first {
|
||||
db.insertScan(it.toScan())
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,74 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.DocumentsContract
|
||||
import android.support.design.widget.FloatingActionButton
|
||||
import android.support.v4.app.Fragment
|
||||
import android.text.Editable
|
||||
import android.text.Html
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.TextWatcher
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.*
|
||||
import android.widget.*
|
||||
import android.text.style.TypefaceSpan
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TableLayout
|
||||
import android.widget.TableRow
|
||||
import android.widget.TextView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.ActionRegistry
|
||||
import de.markusfisch.android.binaryeye.actions.IAction
|
||||
import de.markusfisch.android.binaryeye.actions.mail.MatMsg
|
||||
import de.markusfisch.android.binaryeye.actions.mail.MatMsgAction
|
||||
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
|
||||
import de.markusfisch.android.binaryeye.actions.vtype.vcard.VCardAction
|
||||
import de.markusfisch.android.binaryeye.actions.vtype.vevent.VEventAction
|
||||
import de.markusfisch.android.binaryeye.actions.web.WebAction
|
||||
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
|
||||
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.app.addFragment
|
||||
import de.markusfisch.android.binaryeye.app.db
|
||||
import de.markusfisch.android.binaryeye.app.hasLocationPermission
|
||||
import de.markusfisch.android.binaryeye.app.hasWritePermission
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.content.ContentBarcode
|
||||
import de.markusfisch.android.binaryeye.content.IdlParser
|
||||
import de.markusfisch.android.binaryeye.content.copyToClipboard
|
||||
import de.markusfisch.android.binaryeye.content.idlToRes
|
||||
import de.markusfisch.android.binaryeye.content.shareAsFile
|
||||
import de.markusfisch.android.binaryeye.content.shareText
|
||||
import de.markusfisch.android.binaryeye.content.toHexString
|
||||
import de.markusfisch.android.binaryeye.content.toBarcode
|
||||
import de.markusfisch.android.binaryeye.content.toErrorCorrectionInt
|
||||
import de.markusfisch.android.binaryeye.content.wipeLastShareFile
|
||||
import de.markusfisch.android.binaryeye.database.Recreation
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
import de.markusfisch.android.binaryeye.database.toRecreation
|
||||
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.net.createEncodeDeeplink
|
||||
import de.markusfisch.android.binaryeye.view.hideSoftKeyboard
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import kotlinx.coroutines.*
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class DecodeFragment : Fragment() {
|
||||
@@ -42,11 +79,15 @@ class DecodeFragment : Fragment() {
|
||||
private lateinit var hexView: TextView
|
||||
private lateinit var stampView: TextView
|
||||
private lateinit var recreationView: ImageView
|
||||
private lateinit var labelView: EditText
|
||||
private lateinit var fab: FloatingActionButton
|
||||
private lateinit var scan: Scan
|
||||
private lateinit var format: String
|
||||
|
||||
private val parentJob = Job()
|
||||
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main + parentJob)
|
||||
private val scope: CoroutineScope = CoroutineScope(
|
||||
Dispatchers.Main + parentJob
|
||||
)
|
||||
private val dp: Float
|
||||
get() = resources.displayMetrics.density
|
||||
private val content: String
|
||||
@@ -56,8 +97,23 @@ class DecodeFragment : Fragment() {
|
||||
private var action = ActionRegistry.DEFAULT_ACTION
|
||||
private var isBinary = false
|
||||
private var originalBytes: ByteArray = ByteArray(0)
|
||||
private var id = 0L
|
||||
private var recreation: Recreation? = null
|
||||
private var label: String? = null
|
||||
private var recreationSize = 0
|
||||
|
||||
override fun onActivityResult(
|
||||
requestCode: Int,
|
||||
resultCode: Int,
|
||||
resultData: Intent?
|
||||
) {
|
||||
when (requestCode) {
|
||||
OPEN_DOCUMENT -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val uri = resultData?.data ?: return
|
||||
activity?.openPickedFile(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
@@ -77,21 +133,22 @@ class DecodeFragment : Fragment() {
|
||||
false
|
||||
)
|
||||
|
||||
closeAutomatically = prefs.closeAutomatically &&
|
||||
activity?.intent?.hasExtra(MainActivity.DECODED) == true
|
||||
val justScanned = activity?.intent?.hasExtra(
|
||||
MainActivity.DECODED
|
||||
) == true
|
||||
closeAutomatically = prefs.closeAutomatically && justScanned
|
||||
|
||||
val scan = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
scan = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
arguments?.getParcelable(SCAN, Scan::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
arguments?.getParcelable(SCAN) as Scan?
|
||||
arguments?.getParcelable(SCAN)
|
||||
} ?: throw IllegalArgumentException("DecodeFragment needs a Scan")
|
||||
|
||||
val originalContent = scan.content
|
||||
isBinary = scan.content.isEmpty()
|
||||
originalBytes = scan.raw ?: originalContent.toByteArray()
|
||||
isBinary = scan.text.isEmpty()
|
||||
originalBytes = scan.raw ?: scan.text.toByteArray()
|
||||
format = scan.format.name
|
||||
id = scan.id
|
||||
recreationSize = (200f * dp).roundToInt()
|
||||
|
||||
contentView = view.findViewById(R.id.content)
|
||||
formatView = view.findViewById(R.id.format)
|
||||
@@ -100,36 +157,55 @@ class DecodeFragment : Fragment() {
|
||||
hexView = view.findViewById(R.id.hex)
|
||||
stampView = view.findViewById(R.id.stamp)
|
||||
recreationView = view.findViewById(R.id.recreation)
|
||||
labelView = view.findViewById(R.id.label)
|
||||
fab = view.findViewById(R.id.open)
|
||||
|
||||
initContentAndFab(originalContent)
|
||||
|
||||
if (prefs.showMetaData) {
|
||||
metaView.fillMetaView(scan)
|
||||
} else {
|
||||
metaView.visibility = View.GONE
|
||||
}
|
||||
if (prefs.showRecreation) {
|
||||
recreation = scan.toRecreation(
|
||||
(200f * dp).roundToInt()
|
||||
)
|
||||
|
||||
if (scan.id > 0) {
|
||||
scan.label?.let {
|
||||
labelView.setText(it)
|
||||
label = it
|
||||
}
|
||||
} else {
|
||||
labelView.visibility = View.GONE
|
||||
}
|
||||
|
||||
updateViewsAndFab(originalContent, originalBytes)
|
||||
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
|
||||
view.findViewById<View>(R.id.scroll_view).setPaddingFromWindowInsets()
|
||||
|
||||
(view.findViewById(R.id.inset_layout) as View).setPaddingFromWindowInsets()
|
||||
(view.findViewById(R.id.scroll_view) as View).setPaddingFromWindowInsets()
|
||||
initContentAndFab(justScanned)
|
||||
updateViewsAndFab(scan.text, originalBytes)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
val id = scan.id
|
||||
if (id > 0) {
|
||||
val newLabel = labelView.text.toString().trim()
|
||||
if (newLabel != (label ?: "")) {
|
||||
db.renameScan(id, newLabel)
|
||||
label = newLabel
|
||||
}
|
||||
}
|
||||
if (action.fired) {
|
||||
maybeBackOrFinish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
parentJob.cancel()
|
||||
wipeLastShareFile()
|
||||
}
|
||||
|
||||
private fun initContentAndFab(originalContent: String) {
|
||||
private fun initContentAndFab(justScanned: Boolean) {
|
||||
if (isBinary) {
|
||||
contentView.setText(String(originalBytes).foldNonAlNum())
|
||||
contentView.isEnabled = false
|
||||
@@ -138,7 +214,7 @@ class DecodeFragment : Fragment() {
|
||||
askForFileNameAndSave(originalBytes)
|
||||
}
|
||||
} else {
|
||||
contentView.setText(originalContent)
|
||||
contentView.setText(scan.text)
|
||||
contentView.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
updateViewsAndFab(s?.toString() ?: "")
|
||||
@@ -161,7 +237,8 @@ class DecodeFragment : Fragment() {
|
||||
fab.setOnClickListener {
|
||||
executeAction(content)
|
||||
}
|
||||
if (prefs.openImmediately) {
|
||||
if (justScanned && prefs.openImmediately) {
|
||||
closeAutomatically = true
|
||||
executeAction(content)
|
||||
}
|
||||
}
|
||||
@@ -174,17 +251,16 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun resolveActionAndUpdateFab(bytes: ByteArray) {
|
||||
val prevAction = action
|
||||
if (!prevAction.canExecuteOn(bytes)) {
|
||||
action = ActionRegistry.getAction(bytes)
|
||||
if (!isBinary) {
|
||||
val prevAction = action
|
||||
if (!prevAction.canExecuteOn(bytes)) {
|
||||
action = ActionRegistry.getAction(bytes)
|
||||
}
|
||||
if (prevAction !== action) {
|
||||
fab.setImageResource(action.iconResId)
|
||||
}
|
||||
}
|
||||
if (prevAction !== action) {
|
||||
updateFab(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFab(action: IAction) {
|
||||
fab.setImageResource(action.iconResId)
|
||||
// Run this for the initial action too.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
fab.setOnLongClickListener { v ->
|
||||
v.context.toast(action.titleResId)
|
||||
@@ -199,35 +275,119 @@ class DecodeFragment : Fragment() {
|
||||
formatView.text = resources.getQuantityString(
|
||||
R.plurals.barcode_info,
|
||||
bytes.size,
|
||||
prettifyFormatName(format),
|
||||
format.prettifyFormatName(),
|
||||
bytes.size
|
||||
)
|
||||
hexView.showIf(prefs.showHexDump) { v ->
|
||||
v.text = hexDump(bytes)
|
||||
}
|
||||
recreationView.showIf(prefs.showRecreation) { v ->
|
||||
val r = recreation ?: return@showIf
|
||||
val content = if (isBinary) bytes else text
|
||||
v.setImageBitmap(r.encode(content))
|
||||
v.setOnClickListener {
|
||||
fragmentManager?.addFragment(
|
||||
BarcodeFragment.newInstance(
|
||||
content,
|
||||
r.format,
|
||||
r.size,
|
||||
r.ecLevel
|
||||
)
|
||||
val unmodified: Boolean
|
||||
val content = if (isBinary) {
|
||||
if (bytes.isEmpty()) {
|
||||
clearRecreation()
|
||||
return@showIf
|
||||
}
|
||||
unmodified = bytes.contentEquals(originalBytes)
|
||||
bytes
|
||||
} else {
|
||||
if (text.isEmpty()) {
|
||||
clearRecreation()
|
||||
return@showIf
|
||||
}
|
||||
unmodified = text == scan.text
|
||||
text
|
||||
}
|
||||
val barcode = if (unmodified) {
|
||||
scan.toBarcode()
|
||||
} else {
|
||||
ContentBarcode(
|
||||
content,
|
||||
scan.format,
|
||||
scan.errorCorrectionLevel.toErrorCorrectionInt()
|
||||
)
|
||||
}
|
||||
try {
|
||||
v.setImageBitmap(barcode.bitmap(recreationSize))
|
||||
v.setOnClickListener {
|
||||
v.context.hideSoftKeyboard(contentView)
|
||||
v.post {
|
||||
fragmentManager?.addFragment(
|
||||
BarcodeFragment.newInstance(barcode)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (_: RuntimeException) {
|
||||
clearRecreation()
|
||||
}
|
||||
}
|
||||
dataView.fillDataView(text)
|
||||
dataView.fillDataView(text, bytes)
|
||||
stampView.setTrackingLink(bytes, format)
|
||||
}
|
||||
|
||||
private fun TableLayout.fillDataView(text: String) {
|
||||
val items = LinkedHashMap<Int, String?>()
|
||||
if (action is WifiAction) {
|
||||
WifiConnector.parseMap(text)?.let { wifiData ->
|
||||
private fun clearRecreation() {
|
||||
recreationView.apply {
|
||||
setImageBitmap(null)
|
||||
setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableLayout.fillDataView(text: String, bytes: ByteArray) {
|
||||
val items = LinkedHashMap<Any, CharSequence?>()
|
||||
when (prefs.showChecksum) {
|
||||
"CRC4" -> items[R.string.crc4] = String.format("%X", crc4(bytes))
|
||||
"MD5" -> items[R.string.md5] = bytes.md5().toHexString().fold()
|
||||
"SHA1" -> items[R.string.sha1] = bytes.sha1().toHexString().fold()
|
||||
"SHA256" -> items[R.string.sha256] =
|
||||
bytes.sha256().toHexString().fold()
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
IdlParser.parse(String(bytes))?.let {
|
||||
items.putAll(mapOf("IIN" to it.iin))
|
||||
items.putAll(
|
||||
it.elements.entries.associate { (id, value) ->
|
||||
context.idlToRes(id) to value
|
||||
}
|
||||
)
|
||||
}
|
||||
when (action) {
|
||||
is MatMsgAction -> items.putAll(
|
||||
MatMsg(text).run {
|
||||
mapOf(
|
||||
R.string.email_to to to,
|
||||
R.string.email_subject to sub,
|
||||
R.string.email_body to body
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
is VCardAction,
|
||||
is VEventAction -> VTypeParser.parseMap(text).let { vData ->
|
||||
items.putAll(
|
||||
vData.map { item ->
|
||||
item.key to item.value.joinToString("\n") {
|
||||
it.value
|
||||
}
|
||||
}.toMap()
|
||||
)
|
||||
}
|
||||
|
||||
is WebAction -> try {
|
||||
items.putAll(
|
||||
Uri.parse(text).run {
|
||||
mapOf(
|
||||
R.string.scheme to scheme,
|
||||
R.string.host to host,
|
||||
R.string.query to query
|
||||
)
|
||||
}
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
is WifiAction -> WifiConnector.parseMap(text)?.let { wifiData ->
|
||||
items.putAll(
|
||||
linkedMapOf(
|
||||
R.string.entry_type to getString(R.string.wifi_network),
|
||||
@@ -247,7 +407,7 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun TableLayout.fillMetaView(scan: Scan) {
|
||||
val items = linkedMapOf(
|
||||
val items = linkedMapOf<Any, CharSequence?>(
|
||||
R.string.error_correction_level to scan.errorCorrectionLevel,
|
||||
R.string.sequence_size to scan.sequenceSize.positiveToString(),
|
||||
R.string.sequence_index to scan.sequenceIndex.positiveToString(),
|
||||
@@ -273,11 +433,20 @@ class DecodeFragment : Fragment() {
|
||||
)
|
||||
)
|
||||
}
|
||||
if (scan.format == ZxingCpp.BarcodeFormat.QR_CODE &&
|
||||
scan.dataMask > -1
|
||||
) {
|
||||
items.putAll(
|
||||
linkedMapOf(
|
||||
R.string.qr_data_mask to scan.dataMask.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
fill(items)
|
||||
}
|
||||
|
||||
private fun TableLayout.fill(
|
||||
items: LinkedHashMap<Int, String?>
|
||||
items: LinkedHashMap<Any, CharSequence?>
|
||||
) {
|
||||
removeAllViews()
|
||||
visibility = if (items.isEmpty()) View.GONE else {
|
||||
@@ -288,10 +457,18 @@ class DecodeFragment : Fragment() {
|
||||
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 = text
|
||||
when (val key = item.key) {
|
||||
is Int -> keyView.setText(key)
|
||||
is String -> keyView.text = key
|
||||
else -> keyView.text = key.toString()
|
||||
}
|
||||
val valueView = TextView(ctx).apply {
|
||||
setPadding(spaceBetween, 0, 0, 0)
|
||||
this.text = text
|
||||
setOnClickListener {
|
||||
copyToClipboard(text.toString())
|
||||
}
|
||||
}
|
||||
tr.addView(keyView)
|
||||
tr.addView(valueView)
|
||||
addView(tr)
|
||||
@@ -325,6 +502,12 @@ class DecodeFragment : Fragment() {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.copy_as_deeplink -> {
|
||||
copyToClipboard(deeplinkToCopy())
|
||||
maybeBackOrFinish()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.share -> {
|
||||
context?.apply {
|
||||
shareText(textOrHex())
|
||||
@@ -357,7 +540,7 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
|
||||
R.id.remove -> {
|
||||
db.removeScan(id)
|
||||
db.removeScan(scan.id)
|
||||
backOrFinish()
|
||||
true
|
||||
}
|
||||
@@ -372,14 +555,16 @@ class DecodeFragment : Fragment() {
|
||||
content
|
||||
}
|
||||
|
||||
private fun deeplinkToCopy() = createEncodeDeeplink(
|
||||
format = format,
|
||||
content = textOrHex(),
|
||||
)
|
||||
|
||||
private fun copyPasswordToClipboard() {
|
||||
val ac = action
|
||||
if (ac is WifiAction) {
|
||||
ac.password?.let { password ->
|
||||
activity?.apply {
|
||||
copyToClipboard(password, isSensitive = true)
|
||||
toast(R.string.copied_password_to_clipboard)
|
||||
}
|
||||
copyToClipboard(password, isSensitive = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -387,7 +572,10 @@ class DecodeFragment : Fragment() {
|
||||
private fun copyToClipboard(text: String, isSensitive: Boolean = false) {
|
||||
activity?.apply {
|
||||
copyToClipboard(text, isSensitive)
|
||||
toast(R.string.copied_to_clipboard)
|
||||
// There's a clipboard popup from Android 13 on.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
toast(R.string.copied_to_clipboard)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,20 +594,51 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeAction(content: String) {
|
||||
private fun executeAction(str: String) {
|
||||
val ac = activity ?: return
|
||||
if (content.isNotEmpty()) {
|
||||
if (action is WifiAction &&
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
|
||||
!ac.hasLocationPermission { executeAction(content) }
|
||||
) {
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
action.execute(ac, content.toByteArray())
|
||||
maybeBackOrFinish()
|
||||
}
|
||||
if (str.isEmpty() || openLocalDocument(str)) {
|
||||
return
|
||||
}
|
||||
if (action is WifiAction &&
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
|
||||
!ac.hasLocationPermission { executeAction(str) }
|
||||
) {
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
action.execute(ac, str.toByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
private fun openLocalDocument(str: String): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N ||
|
||||
!str.startsWith(SCHEME_FILE)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
// Needs to be disabled so we get the result.
|
||||
closeAutomatically = false
|
||||
// file:// can't be used with Scoped Storage anymore.
|
||||
startActivityForResult(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/*"
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val primary = str.substring(
|
||||
SCHEME_FILE.length
|
||||
).localDirName() ?: Environment.getExternalStorageDirectory().path
|
||||
putExtra(
|
||||
DocumentsContract.EXTRA_INITIAL_URI,
|
||||
DocumentsContract.buildDocumentUri(
|
||||
"com.android.externalstorage.documents",
|
||||
"primary:$primary"
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
OPEN_DOCUMENT
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun askForFileNameAndSave(raw: ByteArray) {
|
||||
@@ -448,6 +667,8 @@ class DecodeFragment : Fragment() {
|
||||
|
||||
companion object {
|
||||
private const val SCAN = "scan"
|
||||
private const val OPEN_DOCUMENT = 1
|
||||
private const val SCHEME_FILE = "file://"
|
||||
|
||||
fun newInstance(scan: Scan): Fragment {
|
||||
val args = Bundle()
|
||||
@@ -472,7 +693,7 @@ private inline fun <T : View> T.showIf(
|
||||
}
|
||||
|
||||
private val nonAlNum = "[^a-zA-Z0-9]".toRegex()
|
||||
private val multipleDots = "[…]+".toRegex()
|
||||
private val multipleDots = "…+".toRegex()
|
||||
private fun String.foldNonAlNum() = replace(nonAlNum, "…")
|
||||
.replace(multipleDots, "…")
|
||||
|
||||
@@ -568,7 +789,7 @@ private fun generateDpTrackingLink(bytes: ByteArray, format: String): String? {
|
||||
"%X",
|
||||
crc4(hexString.toByteArray(Charsets.ISO_8859_1))
|
||||
)
|
||||
return "<a href=\"https://www.deutschepost.de/de/s/sendungsverfolgung/verfolgen.html?piececode=$trackingNumber\">Deutsche Post: $trackingNumber</a>"
|
||||
return "<a href=\"https://www.deutschepost.de/de/s/sendungsverfolgung.html?piececode=$trackingNumber\">Deutsche Post: $trackingNumber</a>"
|
||||
}
|
||||
|
||||
// CRC-4 with polynomial x^4 + x + 1.
|
||||
@@ -603,3 +824,60 @@ private fun String.fromHtml() = if (
|
||||
@Suppress("DEPRECATION")
|
||||
Html.fromHtml(this)
|
||||
}
|
||||
|
||||
private fun ByteArray.md5(): ByteArray = MessageDigest.getInstance(
|
||||
"MD5"
|
||||
).run {
|
||||
update(this@md5)
|
||||
digest()
|
||||
}
|
||||
|
||||
private fun ByteArray.sha1(): ByteArray = MessageDigest.getInstance(
|
||||
"SHA-1"
|
||||
).run {
|
||||
update(this@sha1)
|
||||
digest()
|
||||
}
|
||||
|
||||
private fun ByteArray.sha256(): ByteArray = MessageDigest.getInstance(
|
||||
"SHA-256"
|
||||
).run {
|
||||
update(this@sha256)
|
||||
digest()
|
||||
}
|
||||
|
||||
private fun String.fold(): CharSequence {
|
||||
val l = length
|
||||
return if (l > 10) {
|
||||
val half = l / 2
|
||||
val s = substring(0, half) + "\n" + substring(half)
|
||||
SpannableString(s).apply {
|
||||
setSpan(
|
||||
TypefaceSpan("monospace"),
|
||||
0, s.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.localDirName(): String? = when {
|
||||
endsWith("/") -> this
|
||||
else -> File(this).parentFile?.absolutePath
|
||||
}?.replaceFirst(Regex("^/storage/emulated/0/"), "")
|
||||
?.replaceFirst(Regex("^/sdcard/"), "")
|
||||
?.trim('/')
|
||||
|
||||
private fun Activity.openPickedFile(uri: Uri) {
|
||||
startActivity(
|
||||
Intent.createChooser(
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(uri, contentResolver.getType(uri) ?: "*/*")
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
},
|
||||
getString(R.string.open_url)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package de.markusfisch.android.binaryeye.fragment
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
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.Menu
|
||||
import android.view.MenuInflater
|
||||
@@ -16,20 +16,19 @@ import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.CheckBox
|
||||
import android.widget.EditText
|
||||
import android.widget.SeekBar
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
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.prefs
|
||||
import de.markusfisch.android.binaryeye.app.setFragment
|
||||
import de.markusfisch.android.binaryeye.text.unescape
|
||||
import de.markusfisch.android.binaryeye.view.hideSoftKeyboard
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
|
||||
import java.io.InputStream
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class EncodeFragment : Fragment() {
|
||||
@@ -38,11 +37,7 @@ class EncodeFragment : Fragment() {
|
||||
private lateinit var ecSpinner: Spinner
|
||||
private lateinit var colorsLabel: TextView
|
||||
private lateinit var colorsSpinner: Spinner
|
||||
private lateinit var sizeView: TextView
|
||||
private lateinit var sizeBarView: SeekBar
|
||||
private lateinit var marginLayout: View
|
||||
private lateinit var marginView: TextView
|
||||
private lateinit var marginBarView: SeekBar
|
||||
private lateinit var addQuietZoneSwitch: SwitchCompat
|
||||
private lateinit var contentView: EditText
|
||||
private lateinit var unescapeCheckBox: CheckBox
|
||||
|
||||
@@ -60,7 +55,6 @@ class EncodeFragment : Fragment() {
|
||||
BarcodeFormat.UPC_A
|
||||
)
|
||||
|
||||
private var minMargin = 0
|
||||
private var bytes: ByteArray? = null
|
||||
|
||||
override fun onActivityResult(
|
||||
@@ -109,7 +103,7 @@ class EncodeFragment : Fragment() {
|
||||
val formatAdapter = ArrayAdapter(
|
||||
ac,
|
||||
android.R.layout.simple_spinner_item,
|
||||
formats.map { prettifyFormatName(it.name) }
|
||||
formats.map { it.name.prettifyFormatName() }
|
||||
)
|
||||
formatAdapter.setDropDownViewResource(
|
||||
android.R.layout.simple_spinner_dropdown_item
|
||||
@@ -145,12 +139,13 @@ class EncodeFragment : Fragment() {
|
||||
colorsLabel, colorsSpinner
|
||||
)
|
||||
when (format) {
|
||||
BarcodeFormat.AZTEC -> setMarginBar(4, 4)
|
||||
BarcodeFormat.DATA_MATRIX -> setMarginBar(1, 1)
|
||||
BarcodeFormat.QR_CODE -> setMarginBar(4, 4)
|
||||
BarcodeFormat.PDF_417 -> setMarginBar(0, 30)
|
||||
BarcodeFormat.AZTEC,
|
||||
BarcodeFormat.DATA_MATRIX,
|
||||
BarcodeFormat.QR_CODE,
|
||||
BarcodeFormat.PDF_417 -> true
|
||||
|
||||
else -> false
|
||||
}.setVisibilityFor(marginLayout)
|
||||
}.setVisibilityFor(addQuietZoneSwitch)
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parentView: AdapterView<*>?) {}
|
||||
@@ -177,26 +172,22 @@ class EncodeFragment : Fragment() {
|
||||
|
||||
colorsLabel = view.findViewById(R.id.colors_label)
|
||||
colorsSpinner = view.findViewById(R.id.colors)
|
||||
|
||||
sizeView = view.findViewById(R.id.size_display)
|
||||
sizeBarView = view.findViewById(R.id.size_bar)
|
||||
initSizeBar()
|
||||
|
||||
marginLayout = view.findViewById(R.id.margin)
|
||||
marginView = view.findViewById(R.id.margin_display)
|
||||
marginBarView = view.findViewById(R.id.margin_bar)
|
||||
initMarginBar()
|
||||
addQuietZoneSwitch = view.findViewById(R.id.add_quiet_zone)
|
||||
addQuietZoneSwitch.isChecked = prefs.addQuietZone
|
||||
|
||||
contentView = view.findViewById(R.id.content)
|
||||
unescapeCheckBox = view.findViewById(R.id.unescape)
|
||||
unescapeCheckBox.isChecked = prefs.expandEscapeSequences
|
||||
|
||||
var complete = false
|
||||
val args = arguments
|
||||
args?.getString(CONTENT_TEXT)?.let {
|
||||
contentView.setText(it)
|
||||
complete = it.isNotEmpty()
|
||||
}
|
||||
args?.getByteArray(CONTENT_RAW)?.let {
|
||||
bytes = it
|
||||
complete = it.isNotEmpty()
|
||||
setEncodeByteArray()
|
||||
}
|
||||
|
||||
@@ -215,8 +206,17 @@ class EncodeFragment : Fragment() {
|
||||
it.context.encode()
|
||||
}
|
||||
|
||||
(view.findViewById(R.id.inset_layout) as View).setPaddingFromWindowInsets()
|
||||
(view.findViewById(R.id.scroll_view) as View).setPaddingFromWindowInsets()
|
||||
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
|
||||
view.findViewById<View>(R.id.scroll_view).setPaddingFromWindowInsets()
|
||||
|
||||
if (complete &&
|
||||
args?.getBoolean(EXECUTE, false) == true
|
||||
) {
|
||||
view.post {
|
||||
val content = view.context.getContent() ?: return@post
|
||||
fragmentManager?.setFragment(newEncodeFragment(content))
|
||||
}
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
@@ -248,113 +248,65 @@ class EncodeFragment : Fragment() {
|
||||
super.onPause()
|
||||
prefs.indexOfLastSelectedFormat = formatView.selectedItemPosition
|
||||
prefs.expandEscapeSequences = unescapeCheckBox.isChecked
|
||||
prefs.lastMargin = marginBarView.progress
|
||||
prefs.addQuietZone = addQuietZoneSwitch.isChecked
|
||||
}
|
||||
|
||||
private fun Context.encode() {
|
||||
hideSoftKeyboard(contentView)
|
||||
val content = if (bytes != null) {
|
||||
bytes
|
||||
} else {
|
||||
var text = contentView.text.toString()
|
||||
if (unescapeCheckBox.isChecked) {
|
||||
try {
|
||||
text = text.unescape()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
toast(e.message ?: "Invalid escape sequence")
|
||||
return
|
||||
}
|
||||
}
|
||||
if (text.isEmpty()) {
|
||||
toast(R.string.error_no_content)
|
||||
return
|
||||
}
|
||||
text
|
||||
val content = getContent() ?: return
|
||||
contentView.post {
|
||||
fragmentManager?.addFragment(newEncodeFragment(content))
|
||||
}
|
||||
val format = formats[formatView.selectedItemPosition]
|
||||
fragmentManager?.addFragment(
|
||||
BarcodeFragment.newInstance(
|
||||
content,
|
||||
format,
|
||||
getSize(sizeBarView.progress),
|
||||
when (format) {
|
||||
BarcodeFormat.AZTEC,
|
||||
BarcodeFormat.DATA_MATRIX,
|
||||
BarcodeFormat.QR_CODE,
|
||||
BarcodeFormat.PDF_417 -> marginBarView.progress
|
||||
}
|
||||
|
||||
else -> -1
|
||||
},
|
||||
format.getErrorCorrectionLevel(ecSpinner.selectedItemPosition),
|
||||
if (format.canBeInverted()) {
|
||||
colorsSpinner.selectedItemPosition
|
||||
} else 0
|
||||
)
|
||||
private fun newEncodeFragment(content: Any): Fragment {
|
||||
val format = formats[formatView.selectedItemPosition]
|
||||
return BarcodeFragment.newInstance(
|
||||
content,
|
||||
format,
|
||||
format.getErrorCorrectionLevel(ecSpinner.selectedItemPosition),
|
||||
when (format) {
|
||||
BarcodeFormat.AZTEC,
|
||||
BarcodeFormat.DATA_MATRIX,
|
||||
BarcodeFormat.QR_CODE,
|
||||
BarcodeFormat.PDF_417 -> if (
|
||||
addQuietZoneSwitch.isChecked
|
||||
) 1 else 0
|
||||
|
||||
else -> -1
|
||||
},
|
||||
if (format.canBeInverted()) {
|
||||
colorsSpinner.selectedItemPosition
|
||||
} else 0
|
||||
)
|
||||
}
|
||||
|
||||
private fun initSizeBar() {
|
||||
updateSize(sizeBarView.progress)
|
||||
sizeBarView.setOnSeekBarChangeListener(
|
||||
object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(
|
||||
seekBar: SeekBar,
|
||||
progressValue: Int,
|
||||
fromUser: Boolean
|
||||
) {
|
||||
updateSize(progressValue)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateSize(power: Int) {
|
||||
val size = getSize(power)
|
||||
sizeView.text = getString(R.string.size_width_by_height, size, size)
|
||||
}
|
||||
|
||||
private fun initMarginBar() {
|
||||
updateMargin(marginBarView.progress)
|
||||
marginBarView.setOnSeekBarChangeListener(
|
||||
object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(
|
||||
seekBar: SeekBar,
|
||||
progressValue: Int,
|
||||
fromUser: Boolean
|
||||
) {
|
||||
if (progressValue >= minMargin) {
|
||||
updateMargin(progressValue)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateMargin(margin: Int) {
|
||||
marginView.text = getString(R.string.margin_size, margin)
|
||||
}
|
||||
|
||||
private fun setMarginBar(minimum: Int, value: Int): Boolean {
|
||||
minMargin = minimum
|
||||
marginBarView.apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
min = minimum
|
||||
}
|
||||
progress = max(
|
||||
minimum, if (prefs.lastMargin > -1) {
|
||||
prefs.lastMargin
|
||||
} else {
|
||||
value
|
||||
}
|
||||
)
|
||||
private fun Context.getContent(): Any? {
|
||||
if (bytes != null) {
|
||||
return bytes
|
||||
}
|
||||
var text = contentView.text.toString()
|
||||
if (text.isEmpty()) {
|
||||
toast(R.string.error_no_content)
|
||||
return null
|
||||
}
|
||||
if (!unescapeCheckBox.isChecked) {
|
||||
return text
|
||||
}
|
||||
try {
|
||||
text = text.unescape()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
toast(e.message ?: "Invalid escape sequence")
|
||||
return null
|
||||
}
|
||||
// Return a ByteArray if there were escape sequences for
|
||||
// non-printable characters.
|
||||
return if (text.any { it.code < 32 && it.code !in setOf(9, 10, 13) }) {
|
||||
// Do not expand UTF-8 sequences in binary data.
|
||||
text.toByteArray(Charsets.ISO_8859_1)
|
||||
} else {
|
||||
text
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun setEncodeByteArray() {
|
||||
@@ -368,11 +320,13 @@ class EncodeFragment : Fragment() {
|
||||
private const val CONTENT_TEXT = "content_text"
|
||||
private const val CONTENT_RAW = "content_raw"
|
||||
private const val FORMAT = "format"
|
||||
private const val EXECUTE = "execute"
|
||||
private const val PICK_FILE_RESULT_CODE = 1
|
||||
|
||||
fun <T> newInstance(
|
||||
content: T? = null,
|
||||
format: String? = null
|
||||
format: String? = null,
|
||||
execute: Boolean = false
|
||||
): Fragment {
|
||||
val args = Bundle()
|
||||
content?.let {
|
||||
@@ -383,6 +337,7 @@ class EncodeFragment : Fragment() {
|
||||
"content must be a String or a ByteArray"
|
||||
)
|
||||
}
|
||||
args.putBoolean(EXECUTE, execute)
|
||||
}
|
||||
format?.let { args.putString(FORMAT, it) }
|
||||
val fragment = EncodeFragment()
|
||||
@@ -422,7 +377,9 @@ private fun BarcodeFormat.canBeInverted() = when (this) {
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun String.toFormat(default: BarcodeFormat = BarcodeFormat.QR_CODE): BarcodeFormat = try {
|
||||
private fun String.toFormat(
|
||||
default: BarcodeFormat = BarcodeFormat.QR_CODE
|
||||
): BarcodeFormat = try {
|
||||
BarcodeFormat.valueOf(this)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
default
|
||||
@@ -446,8 +403,6 @@ private fun Spinner.setEntries(resId: Int) = ArrayAdapter.createFromResource(
|
||||
adapter = aa
|
||||
}
|
||||
|
||||
private fun getSize(power: Int) = 128 * (power + 1)
|
||||
|
||||
private fun InputStream.readBytesMax(max: Int): ByteArray {
|
||||
var offset = 0
|
||||
var remaining = min(available(), max)
|
||||
|
||||
@@ -15,22 +15,43 @@ import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.view.ActionMode
|
||||
import android.support.v7.widget.SearchView
|
||||
import android.support.v7.widget.SwitchCompat
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.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.app.addFragment
|
||||
import de.markusfisch.android.binaryeye.app.alertDialog
|
||||
import de.markusfisch.android.binaryeye.app.db
|
||||
import de.markusfisch.android.binaryeye.app.hasWritePermission
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.content.copyToClipboard
|
||||
import de.markusfisch.android.binaryeye.content.shareAsFile
|
||||
import de.markusfisch.android.binaryeye.content.shareText
|
||||
import de.markusfisch.android.binaryeye.content.wipeLastShareFile
|
||||
import de.markusfisch.android.binaryeye.database.*
|
||||
import de.markusfisch.android.binaryeye.database.Database
|
||||
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.database.use
|
||||
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.view.lockStatusBarColor
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.view.systemBarListViewScrollListener
|
||||
import de.markusfisch.android.binaryeye.view.unlockStatusBarColor
|
||||
import de.markusfisch.android.binaryeye.view.useVisibility
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class HistoryFragment : Fragment() {
|
||||
private lateinit var useHistorySwitch: SwitchCompat
|
||||
@@ -49,9 +70,13 @@ class HistoryFragment : Fragment() {
|
||||
R.menu.fragment_history_edit,
|
||||
menu
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
||||
// SDK35+ doesn't draw a status bar background anymore.
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM
|
||||
) {
|
||||
lockStatusBarColor()
|
||||
val ac = activity ?: return false
|
||||
@Suppress("DEPRECATION")
|
||||
ac.window.statusBarColor = ContextCompat.getColor(
|
||||
ac,
|
||||
R.color.accent_dark
|
||||
@@ -82,7 +107,7 @@ class HistoryFragment : Fragment() {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.edit_scan -> {
|
||||
R.id.edit_label -> {
|
||||
scansAdapter?.forSelection { id, position ->
|
||||
ac.askForName(
|
||||
id,
|
||||
@@ -142,12 +167,16 @@ class HistoryFragment : Fragment() {
|
||||
|
||||
useHistorySwitch = view.findViewById(
|
||||
R.id.use_history
|
||||
) as SwitchCompat
|
||||
)
|
||||
initHistorySwitch(useHistorySwitch)
|
||||
|
||||
listView = view.findViewById(R.id.scans)
|
||||
listView.setOnItemClickListener { _, _, _, id ->
|
||||
showScan(id)
|
||||
listView.setOnItemClickListener { _, v, position, id ->
|
||||
if (actionMode != null) {
|
||||
scansAdapter?.select(v, id, position)
|
||||
} else {
|
||||
showScan(id)
|
||||
}
|
||||
}
|
||||
listView.setOnItemLongClickListener { _, v, position, id ->
|
||||
scansAdapter?.select(v, id, position)
|
||||
@@ -167,7 +196,7 @@ class HistoryFragment : Fragment() {
|
||||
|
||||
progressView = view.findViewById(R.id.progress_view)
|
||||
|
||||
(view.findViewById(R.id.inset_layout) as View).setPaddingFromWindowInsets()
|
||||
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
|
||||
listView.setPaddingFromWindowInsets()
|
||||
|
||||
update()
|
||||
@@ -272,7 +301,7 @@ class HistoryFragment : Fragment() {
|
||||
val cursor = db.getScans(filter)
|
||||
withContext(Dispatchers.Main) {
|
||||
val ac = activity ?: return@withContext
|
||||
val hasScans = cursor != null && cursor.count > 0
|
||||
val hasScans = cursor.count > 0
|
||||
if (filter == null) {
|
||||
if (!hasScans) {
|
||||
listView.emptyView = useHistorySwitch
|
||||
@@ -285,7 +314,7 @@ class HistoryFragment : Fragment() {
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
cursor?.let { cursor ->
|
||||
cursor.let { cursor ->
|
||||
// Close previous cursor.
|
||||
scansAdapter?.also { it.changeCursor(null) }
|
||||
scansAdapter = ScansAdapter(ac, cursor)
|
||||
@@ -316,7 +345,7 @@ class HistoryFragment : Fragment() {
|
||||
closeActionMode()
|
||||
try {
|
||||
fragmentManager?.addFragment(DecodeFragment.newInstance(scan))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// Ignore, can never happen.
|
||||
}
|
||||
}
|
||||
@@ -417,12 +446,12 @@ class HistoryFragment : Fragment() {
|
||||
} ?: return@useVisibility
|
||||
val message = when (delimiter) {
|
||||
"db" -> ac.exportDatabase(name)
|
||||
else -> db.getScansDetailed(filter)?.use {
|
||||
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)
|
||||
@@ -450,14 +479,14 @@ class HistoryFragment : Fragment() {
|
||||
db.getScansDetailed(selectedIds.toLongArray())
|
||||
} else {
|
||||
db.getScansDetailed(filter)
|
||||
}?.use { cursor ->
|
||||
}.use { cursor ->
|
||||
val details = format.split(":")
|
||||
when (details[0]) {
|
||||
"text" -> Pair(cursor.exportText(details[1]), "txt")
|
||||
"csv" -> Pair(cursor.exportCsv(details[1]), "csv")
|
||||
else -> Pair(cursor.exportJson(), "json")
|
||||
}
|
||||
}?.let { (text, ext) ->
|
||||
}.let { (text, ext) ->
|
||||
text?.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (asFile) {
|
||||
@@ -477,7 +506,7 @@ class HistoryFragment : Fragment() {
|
||||
|
||||
private fun Cursor.exportText(separator: String): String {
|
||||
val sb = StringBuilder()
|
||||
val contentIndex = getColumnIndex(Database.SCANS_CONTENT)
|
||||
val contentIndex = getColumnIndex(Database.SCANS_TEXT)
|
||||
if (contentIndex > -1 && moveToFirst()) {
|
||||
do {
|
||||
val content = getString(contentIndex)
|
||||
|
||||
+4
-2
@@ -7,7 +7,9 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v4.app.Fragment
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
@@ -49,7 +51,7 @@ class NetworkSuggestionsFragment : Fragment() {
|
||||
listView.emptyView = view.findViewById(R.id.no_suggestions)
|
||||
listView.adapter = suggestionArrayAdapter
|
||||
listView.setOnScrollListener(systemBarListViewScrollListener)
|
||||
(view.findViewById(R.id.inset_layout) as View).setPaddingFromWindowInsets()
|
||||
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
|
||||
listView.setPaddingFromWindowInsets()
|
||||
|
||||
view.findViewById<View>(R.id.remove).setOnClickListener {
|
||||
|
||||
+108
-29
@@ -28,6 +28,8 @@ import de.markusfisch.android.binaryeye.view.systemBarRecyclerViewScrollListener
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
|
||||
class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
private var lastProfile: String? = null
|
||||
|
||||
private val changeListener = object : OnSharedPreferenceChangeListener {
|
||||
override fun onSharedPreferenceChanged(
|
||||
sharedPreferences: SharedPreferences?,
|
||||
@@ -35,34 +37,78 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
) {
|
||||
key ?: return
|
||||
val preference = findPreference(key) ?: return
|
||||
prefs.update()
|
||||
when (preference.key) {
|
||||
"custom_locale" -> activity?.restartApp()
|
||||
"beep_tone_name" -> {
|
||||
beepConfirm()
|
||||
setSummary(preference)
|
||||
}
|
||||
|
||||
"send_scan_bluetooth" -> {
|
||||
if (prefs.sendScanBluetooth &&
|
||||
activity?.hasBluetoothPermission() == false
|
||||
) {
|
||||
prefs.sendScanBluetooth = false
|
||||
}
|
||||
setSummary(preference)
|
||||
}
|
||||
|
||||
else -> setSummary(preference)
|
||||
if (preference.key != PROFILE) {
|
||||
prefs.update()
|
||||
}
|
||||
when (preference.key) {
|
||||
"custom_locale" -> {
|
||||
activity?.restartApp()
|
||||
return
|
||||
}
|
||||
|
||||
"beep_tone_name" -> beepConfirm()
|
||||
|
||||
"send_scan_bluetooth" -> if (
|
||||
prefs.sendScanBluetooth &&
|
||||
activity?.hasBluetoothPermission() == false
|
||||
) {
|
||||
prefs.sendScanBluetooth = false
|
||||
}
|
||||
}
|
||||
setSummary(preference)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(state: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences)
|
||||
loadPreferences(rootKey)
|
||||
}
|
||||
|
||||
private fun loadPreferences(rootKey: String? = null) {
|
||||
preferenceScreen?.sharedPreferences
|
||||
?.unregisterOnSharedPreferenceChangeListener(changeListener)
|
||||
|
||||
preferenceManager.apply {
|
||||
sharedPreferencesName = prefs.profile ?: "${context.packageName}_preferences"
|
||||
sharedPreferencesMode = Context.MODE_PRIVATE
|
||||
}
|
||||
|
||||
// Refresh the preferences.
|
||||
preferenceScreen = null
|
||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||
preferenceScreen.sharedPreferences
|
||||
.registerOnSharedPreferenceChangeListener(changeListener)
|
||||
setSummaries(preferenceScreen)
|
||||
|
||||
wireProfiles()
|
||||
wireAutomatedActions()
|
||||
setBluetoothResources()
|
||||
wireClearNetworkPreferences()
|
||||
}
|
||||
|
||||
private fun wireProfiles() {
|
||||
findPreference(PROFILE).apply {
|
||||
updateProfileSummary(this)
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
activity?.supportFragmentManager?.addFragment(
|
||||
ProfilesFragment()
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun wireAutomatedActions() {
|
||||
findPreference(AUTOMATED_ACTIONS).apply {
|
||||
updateAutomatedActionsSummary(this)
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
activity?.supportFragmentManager?.addFragment(
|
||||
AutomatedActionsFragment()
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setBluetoothResources() {
|
||||
if (prefs.sendScanBluetooth &&
|
||||
activity?.hasBluetoothPermission() == true
|
||||
@@ -79,7 +125,9 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
// From R+ we can query past network suggestions and
|
||||
// make them editable.
|
||||
setOnPreferenceClickListener {
|
||||
fragmentManager.addFragment(NetworkSuggestionsFragment())
|
||||
activity?.supportFragmentManager?.addFragment(
|
||||
NetworkSuggestionsFragment()
|
||||
)
|
||||
true
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
|
||||
@@ -124,13 +172,19 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (lastProfile != prefs.profile) {
|
||||
loadPreferences()
|
||||
}
|
||||
activity?.setTitle(R.string.preferences)
|
||||
findPreference(AUTOMATED_ACTIONS)?.let {
|
||||
updateAutomatedActionsSummary(it)
|
||||
}
|
||||
findPreference(PROFILE)?.let {
|
||||
updateProfileSummary(it)
|
||||
}
|
||||
listView.setPaddingFromWindowInsets()
|
||||
listView.removeOnScrollListener(systemBarRecyclerViewScrollListener)
|
||||
listView.addOnScrollListener(systemBarRecyclerViewScrollListener)
|
||||
preferenceScreen.sharedPreferences
|
||||
.registerOnSharedPreferenceChangeListener(changeListener)
|
||||
setSummaries(preferenceScreen)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@@ -165,9 +219,13 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
|
||||
private fun setSummary(preference: Preference) {
|
||||
if (preference.key == AUTOMATED_ACTIONS) {
|
||||
updateAutomatedActionsSummary(preference)
|
||||
return
|
||||
}
|
||||
when (preference) {
|
||||
is UrlPreference -> {
|
||||
preference.setSummary(preference.getUrl())
|
||||
preference.summary = preference.getUrl()
|
||||
}
|
||||
|
||||
is ListPreference -> {
|
||||
@@ -175,11 +233,9 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
|
||||
is MultiSelectListPreference -> {
|
||||
preference.setSummary(
|
||||
preference.values.joinToString(", ") {
|
||||
it.replace(Regex("_"), " ")
|
||||
}
|
||||
)
|
||||
preference.summary = preference.values.joinToString(", ") {
|
||||
it.replace(Regex("_"), " ")
|
||||
}
|
||||
}
|
||||
|
||||
is PreferenceGroup -> {
|
||||
@@ -187,6 +243,29 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAutomatedActionsSummary(preference: Preference) {
|
||||
val count = prefs.automatedActions.size
|
||||
preference.summary = if (count == 0) {
|
||||
getString(R.string.automated_actions_none)
|
||||
} else {
|
||||
resources.getQuantityString(
|
||||
R.plurals.automated_actions_count,
|
||||
count,
|
||||
count
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProfileSummary(preference: Preference) {
|
||||
preference.summary = prefs.profile ?: getString(R.string.profile_default)
|
||||
lastProfile = prefs.profile
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PROFILE = "profile"
|
||||
private const val AUTOMATED_ACTIONS = "automated_actions"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Activity.restartApp() {
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package de.markusfisch.android.binaryeye.fragment
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.view.systemBarListViewScrollListener
|
||||
import de.markusfisch.android.binaryeye.widget.toast
|
||||
|
||||
class ProfilesFragment : Fragment() {
|
||||
private val profiles = ArrayList<ProfileItem>()
|
||||
|
||||
private lateinit var adapter: ProfilesAdapter
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
state: Bundle?
|
||||
): View? {
|
||||
val ac = activity ?: return null
|
||||
ac.setTitle(R.string.profile)
|
||||
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_profiles,
|
||||
container,
|
||||
false
|
||||
)
|
||||
|
||||
refreshProfiles()
|
||||
|
||||
val listView = view.findViewById<ListView>(R.id.profiles)
|
||||
listView.emptyView = view.findViewById(R.id.no_profiles)
|
||||
adapter = ProfilesAdapter(ac, profiles)
|
||||
listView.adapter = adapter
|
||||
listView.setOnScrollListener(systemBarListViewScrollListener)
|
||||
listView.setOnItemClickListener { _, _, position, _ ->
|
||||
selectProfile(position)
|
||||
}
|
||||
listView.setOnItemLongClickListener { _, _, position, _ ->
|
||||
confirmRemoveProfile(position)
|
||||
true
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.add).setOnClickListener {
|
||||
addProfile()
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
|
||||
listView.setPaddingFromWindowInsets()
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun refreshProfiles() {
|
||||
profiles.clear()
|
||||
profiles.add(
|
||||
ProfileItem(
|
||||
null,
|
||||
getString(R.string.profile_default)
|
||||
)
|
||||
)
|
||||
prefs.profiles.forEach { name ->
|
||||
profiles.add(ProfileItem(name, name))
|
||||
}
|
||||
if (this::adapter.isInitialized) {
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectProfile(position: Int) {
|
||||
val ac = activity ?: return
|
||||
val profile = profiles[position].name
|
||||
if (prefs.profile == profile) {
|
||||
return
|
||||
}
|
||||
prefs.load(ac, profile)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
// Dialogs do not have a parent view.
|
||||
@Suppress("InflateParams")
|
||||
private fun addProfile() {
|
||||
val ac = activity ?: return
|
||||
val view = ac.layoutInflater.inflate(
|
||||
R.layout.dialog_profile_name, null
|
||||
)
|
||||
val editText = view.findViewById<EditText>(R.id.name)
|
||||
AlertDialog.Builder(ac)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val name = editText.text.toString().trim()
|
||||
if (name.isNotEmpty() && prefs.addProfile(name)) {
|
||||
prefs.load(ac, name)
|
||||
refreshProfiles()
|
||||
} else {
|
||||
ac.toast(R.string.profile_invalid_name)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun confirmRemoveProfile(position: Int) {
|
||||
val ac = activity ?: return
|
||||
val profile = profiles[position]
|
||||
val profileName = profile.name ?: return
|
||||
AlertDialog.Builder(ac)
|
||||
.setMessage(R.string.profile_remove_confirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val isCurrent = prefs.profile == profileName
|
||||
prefs.removeProfile(profileName)
|
||||
if (isCurrent) {
|
||||
prefs.load(ac, null)
|
||||
}
|
||||
refreshProfiles()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
private class ProfilesAdapter(
|
||||
context: Context,
|
||||
private val items: List<ProfileItem>
|
||||
) : ArrayAdapter<ProfileItem>(
|
||||
context,
|
||||
android.R.layout.simple_list_item_2,
|
||||
android.R.id.text1,
|
||||
items
|
||||
) {
|
||||
override fun getView(
|
||||
position: Int,
|
||||
convertView: View?,
|
||||
parent: ViewGroup
|
||||
): View {
|
||||
val view = super.getView(position, convertView, parent)
|
||||
val item = items[position]
|
||||
val isCurrent = item.name == prefs.profile
|
||||
getViewHolder(view).apply {
|
||||
title.text = item.label
|
||||
if (isCurrent) {
|
||||
subtitle.text = context.getString(R.string.profile_current)
|
||||
subtitle.visibility = View.VISIBLE
|
||||
} else {
|
||||
subtitle.text = ""
|
||||
subtitle.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
private fun getViewHolder(
|
||||
view: View
|
||||
): ViewHolder = view.tag as ViewHolder? ?: ViewHolder(
|
||||
view.findViewById(android.R.id.text1),
|
||||
view.findViewById(android.R.id.text2),
|
||||
).also {
|
||||
view.tag = it
|
||||
}
|
||||
|
||||
private data class ViewHolder(
|
||||
val title: TextView,
|
||||
val subtitle: TextView,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class ProfileItem(
|
||||
val name: String?,
|
||||
val label: String
|
||||
)
|
||||
@@ -80,10 +80,5 @@ private fun completeUrl(template: String): String {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ fun ContentResolver.loadImageUri(uri: Uri): Bitmap? = try {
|
||||
openInputStream(uri)?.use {
|
||||
BitmapFactory.decodeStream(it, null, options)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
} catch (_: IOException) {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -55,17 +55,17 @@ fun Bitmap.crop(
|
||||
pivotX: Float,
|
||||
pivotY: Float
|
||||
) = try {
|
||||
val erected = erect(rotation, pivotX, pivotY)
|
||||
val w = erected.width
|
||||
val h = erected.height
|
||||
val b = rotate(rotation, pivotX, pivotY)
|
||||
val w = b.width
|
||||
val h = b.height
|
||||
val x = max(0, (rect.left * w).roundToInt())
|
||||
val y = max(0, (rect.top * h).roundToInt())
|
||||
Bitmap.createBitmap(
|
||||
erected,
|
||||
b,
|
||||
x,
|
||||
y,
|
||||
min(w - x, (rect.right * w).roundToInt() - x),
|
||||
min(h - y, (rect.bottom * h).roundToInt() - y)
|
||||
min(w, (rect.right * w).roundToInt()) - x,
|
||||
min(h, (rect.bottom * h).roundToInt()) - y
|
||||
)
|
||||
} catch (e: OutOfMemoryError) {
|
||||
null
|
||||
@@ -73,13 +73,11 @@ fun Bitmap.crop(
|
||||
null
|
||||
}
|
||||
|
||||
private fun Bitmap.erect(
|
||||
private fun Bitmap.rotate(
|
||||
rotation: Float,
|
||||
pivotX: Float,
|
||||
pivotY: Float
|
||||
): Bitmap = if (
|
||||
rotation % 360f != 0f
|
||||
) {
|
||||
): Bitmap = if (rotation % 360f != 0f) {
|
||||
Bitmap.createBitmap(
|
||||
this,
|
||||
0,
|
||||
|
||||
@@ -10,7 +10,9 @@ data class FrameMetrics(
|
||||
var width: Int = 0,
|
||||
var height: Int = 0,
|
||||
var orientation: Int = 0
|
||||
)
|
||||
) {
|
||||
fun isValid() = width > 0 && height > 0
|
||||
}
|
||||
|
||||
fun Rect.setFrameRoi(
|
||||
frameMetrics: FrameMetrics,
|
||||
|
||||
@@ -5,11 +5,12 @@ import android.graphics.Canvas
|
||||
|
||||
fun Bitmap.fixTransparency(): Bitmap {
|
||||
val result = analyzeTransparency()
|
||||
return if (result.hasTransparentPixels) {
|
||||
val cfg = config
|
||||
return if (cfg != null && result.hasTransparentPixels) {
|
||||
val copy = Bitmap.createBitmap(
|
||||
width,
|
||||
height,
|
||||
config
|
||||
cfg
|
||||
)
|
||||
Canvas(copy).apply {
|
||||
drawColor(result.backgroundColor)
|
||||
|
||||
@@ -53,7 +53,7 @@ fun Context.writeExternalFile(
|
||||
): Boolean = try {
|
||||
openExternalOutputStream(fileName, mimeType).use { write(it) }
|
||||
true
|
||||
} catch (e: IOException) {
|
||||
} catch (_: IOException) {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ private fun Context.openExternalOutputStream(
|
||||
fileName: String,
|
||||
mimeType: String
|
||||
): OutputStream = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
@Suppress("DEPRECATION")
|
||||
val file = File(
|
||||
Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS
|
||||
|
||||
@@ -22,7 +22,7 @@ fun beepError() {
|
||||
ToneGenerator.MAX_VOLUME
|
||||
)
|
||||
errorToneGenerator = tg
|
||||
tg.startTone(ToneGenerator.TONE_SUP_ERROR)
|
||||
tg.startTone(ToneGenerator.TONE_SUP_ERROR, 1000)
|
||||
}
|
||||
|
||||
fun releaseToneGenerators() {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package de.markusfisch.android.binaryeye.net
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
private const val SCHEME = "binaryeye"
|
||||
private const val HOSTNAME = "markusfisch.de"
|
||||
private const val ENCODE_PATH = "encode"
|
||||
|
||||
fun isScanDeeplink(text: String) = listOf(
|
||||
"$SCHEME://scan",
|
||||
"http://$HOSTNAME/BinaryEye",
|
||||
"https://$HOSTNAME/BinaryEye"
|
||||
).any { text.startsWith(it) }
|
||||
|
||||
fun isEncodeDeeplink(text: String) = listOf(
|
||||
"$SCHEME://$ENCODE_PATH",
|
||||
"http://$HOSTNAME/$ENCODE_PATH",
|
||||
"https://$HOSTNAME/$ENCODE_PATH"
|
||||
).any { text.startsWith(it) }
|
||||
|
||||
fun createEncodeDeeplink(format: String, content: String): String {
|
||||
return Uri.Builder()
|
||||
.scheme("https")
|
||||
.authority(HOSTNAME)
|
||||
.appendPath(ENCODE_PATH)
|
||||
.appendQueryParameter("format", format)
|
||||
.appendQueryParameter("content", content)
|
||||
.build()
|
||||
.toString()
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package de.markusfisch.android.binaryeye.net
|
||||
|
||||
import de.markusfisch.android.binaryeye.content.toHexString
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -36,7 +35,10 @@ private fun Scan.send(url: String, type: String): Response {
|
||||
|
||||
"2" -> request(url) { con ->
|
||||
con.requestMethod = "POST"
|
||||
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
|
||||
con.setRequestProperty(
|
||||
"Content-Type",
|
||||
"application/x-www-form-urlencoded"
|
||||
)
|
||||
con.outputStream.apply {
|
||||
write(asUrlArguments().toByteArray())
|
||||
close()
|
||||
@@ -53,7 +55,7 @@ private fun Scan.send(url: String, type: String): Response {
|
||||
}
|
||||
|
||||
else -> request(
|
||||
url + content.urlEncode()
|
||||
url + text.urlEncode()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -67,7 +69,7 @@ private fun Scan.asJson() = JSONObject().apply {
|
||||
}
|
||||
|
||||
private fun Scan.getMap(): Map<String, String> = mapOf(
|
||||
"content" to content,
|
||||
"content" to text,
|
||||
"raw" to raw?.toHexString(),
|
||||
"format" to format.name,
|
||||
"errorCorrectionLevel" to errorCorrectionLevel,
|
||||
|
||||
@@ -5,6 +5,6 @@ import java.net.URLEncoder
|
||||
|
||||
fun String.urlEncode(): String = try {
|
||||
URLEncoder.encode(this, "UTF-8")
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
} catch (_: UnsupportedEncodingException) {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package de.markusfisch.android.binaryeye.os
|
||||
|
||||
import android.app.Activity
|
||||
import android.view.WindowManager
|
||||
|
||||
fun Activity.getScreenBrightness(): Float {
|
||||
val layoutParams = window.attributes
|
||||
return if (layoutParams.screenBrightness ==
|
||||
WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
||||
) {
|
||||
android.provider.Settings.System.getInt(
|
||||
contentResolver,
|
||||
android.provider.Settings.System.SCREEN_BRIGHTNESS
|
||||
) / 255.0f
|
||||
} else {
|
||||
layoutParams.screenBrightness
|
||||
}
|
||||
}
|
||||
|
||||
fun Activity.setScreenBrightness(brightness: Float) {
|
||||
val layoutParams = window.attributes
|
||||
layoutParams.screenBrightness = brightness
|
||||
window.attributes = layoutParams
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package de.markusfisch.android.binaryeye.preference
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Point
|
||||
import org.json.JSONObject
|
||||
|
||||
fun SharedPreferences.storeCropHandle(
|
||||
name: String,
|
||||
viewWidth: Int,
|
||||
viewHeight: Int,
|
||||
cropHandle: Point
|
||||
) {
|
||||
edit().putString(
|
||||
cropHandleKey(name, viewWidth, viewHeight),
|
||||
JSONObject().apply {
|
||||
put("x", cropHandle.x)
|
||||
put("y", cropHandle.y)
|
||||
}.toString()
|
||||
).apply()
|
||||
}
|
||||
|
||||
fun SharedPreferences.restoreCropHandle(
|
||||
name: String,
|
||||
viewWidth: Int,
|
||||
viewHeight: Int,
|
||||
default: Point = Point(-2, -2) // -2 means set default roi.
|
||||
): Point {
|
||||
val s = getString(
|
||||
cropHandleKey(name, viewWidth, viewHeight), null
|
||||
) ?: return default
|
||||
return try {
|
||||
val json = JSONObject(s)
|
||||
Point(
|
||||
json.getInt("x"),
|
||||
json.getInt("y")
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
return default
|
||||
}
|
||||
}
|
||||
|
||||
private fun cropHandleKey(
|
||||
name: String,
|
||||
viewWidth: Int,
|
||||
viewHeight: Int
|
||||
) = "${name}:${viewWidth}:${viewHeight}"
|
||||
@@ -3,15 +3,28 @@ package de.markusfisch.android.binaryeye.preference
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Point
|
||||
import android.media.ToneGenerator
|
||||
import android.os.Build
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.annotation.RequiresApi
|
||||
import de.markusfisch.android.binaryeye.automation.AutomatedAction
|
||||
import de.markusfisch.android.binaryeye.automation.AutomatedAction.Companion.fromJsonArray
|
||||
import de.markusfisch.android.binaryeye.automation.AutomatedAction.Companion.toJsonArray
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
|
||||
import org.json.JSONArray
|
||||
|
||||
class Preferences {
|
||||
lateinit var defaultPreferences: SharedPreferences
|
||||
lateinit var preferences: SharedPreferences
|
||||
|
||||
val profiles = ArrayList<String>()
|
||||
|
||||
var profile: String? = null
|
||||
set(value) {
|
||||
defaultPreferences.edit().putString(PROFILE, value).apply()
|
||||
field = value
|
||||
}
|
||||
var barcodeFormats = setOf(
|
||||
BarcodeFormat.AZTEC.name,
|
||||
BarcodeFormat.CODABAR.name,
|
||||
@@ -21,6 +34,7 @@ class Preferences {
|
||||
BarcodeFormat.DATA_BAR.name,
|
||||
BarcodeFormat.DATA_BAR_EXPANDED.name,
|
||||
BarcodeFormat.DATA_MATRIX.name,
|
||||
BarcodeFormat.DX_FILM_EDGE.name,
|
||||
BarcodeFormat.EAN_8.name,
|
||||
BarcodeFormat.EAN_13.name,
|
||||
BarcodeFormat.ITF.name,
|
||||
@@ -37,21 +51,6 @@ class Preferences {
|
||||
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 = -2
|
||||
set(value) {
|
||||
apply(CROP_HANDLE_Y, value)
|
||||
field = value
|
||||
}
|
||||
var cropHandleOrientation = 0
|
||||
set(value) {
|
||||
apply(CROP_HANDLE_ORIENTATION, value)
|
||||
field = value
|
||||
}
|
||||
var showCropHandle = true
|
||||
set(value) {
|
||||
apply(SHOW_CROP_HANDLE, value)
|
||||
@@ -107,9 +106,9 @@ class Preferences {
|
||||
apply(USE_HISTORY, value)
|
||||
field = value
|
||||
}
|
||||
var ignoreConsecutiveDuplicates = true
|
||||
var ignoreDuplicatesName = "ignore_consecutive_duplicates"
|
||||
set(value) {
|
||||
apply(IGNORE_CONSECUTIVE_DUPLICATES, value)
|
||||
apply(IGNORE_DUPLICATES_NAME, value)
|
||||
field = value
|
||||
}
|
||||
var copyImmediately = false
|
||||
@@ -132,6 +131,11 @@ class Preferences {
|
||||
apply(SHOW_HEX_DUMP, value)
|
||||
field = value
|
||||
}
|
||||
var showChecksum = ""
|
||||
set(value) {
|
||||
apply(SHOW_CHECKSUM, value)
|
||||
field = value
|
||||
}
|
||||
var showRecreation = true
|
||||
set(value) {
|
||||
apply(SHOW_RECREATION, value)
|
||||
@@ -152,7 +156,7 @@ class Preferences {
|
||||
apply(OPEN_WITH_URL, value)
|
||||
field = value
|
||||
}
|
||||
var sendScanActive = true
|
||||
var sendScanActive = false
|
||||
set(value) {
|
||||
apply(SEND_SCAN_ACTIVE, value)
|
||||
field = value
|
||||
@@ -194,12 +198,7 @@ class Preferences {
|
||||
apply(INDEX_OF_LAST_SELECTED_EC_LEVEL, value)
|
||||
field = value
|
||||
}
|
||||
var lastMargin: Int = -1
|
||||
set(value) {
|
||||
apply(LAST_MARGIN, value)
|
||||
field = value
|
||||
}
|
||||
var freeRotation = true
|
||||
var freeRotation = false
|
||||
set(value) {
|
||||
apply(FREE_ROTATION, value)
|
||||
field = value
|
||||
@@ -209,24 +208,94 @@ class Preferences {
|
||||
apply(EXPAND_ESCAPE_SEQUENCES, value)
|
||||
field = value
|
||||
}
|
||||
var addQuietZone = true
|
||||
set(value) {
|
||||
apply(ADD_QUIET_ZONE, value)
|
||||
field = value
|
||||
}
|
||||
var brightenScreen = false
|
||||
set(value) {
|
||||
apply(BRIGHTEN_SCREEN, value)
|
||||
field = value
|
||||
}
|
||||
var previewScale = 0f
|
||||
set(value) {
|
||||
apply(PREVIEW_SCALE, value)
|
||||
field = value
|
||||
}
|
||||
var automatedActions = mutableListOf<AutomatedAction>()
|
||||
private set
|
||||
|
||||
fun init(context: Context) {
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
// I'm not including a support library just to get the
|
||||
// default preferences. Dependencies are a burden.
|
||||
@Suppress("DEPRECATION")
|
||||
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
loadProfiles()
|
||||
load(context, defaultPreferences.getString(PROFILE, null))
|
||||
}
|
||||
|
||||
fun load(context: Context, profileName: String?) {
|
||||
preferences = if (profileName.isNullOrEmpty()) {
|
||||
profile = null
|
||||
defaultPreferences
|
||||
} else {
|
||||
profile = profileName
|
||||
context.getSharedPreferences(
|
||||
profileName,
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
||||
fun addProfile(name: String): Boolean {
|
||||
if (!name.matches("^[a-zA-Z0-9_-]+$".toRegex()) ||
|
||||
profiles.contains(name)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
profiles.add(name)
|
||||
saveProfiles()
|
||||
return true
|
||||
}
|
||||
|
||||
fun removeProfile(name: String): Boolean {
|
||||
if (!profiles.remove(name)) {
|
||||
return false
|
||||
}
|
||||
saveProfiles()
|
||||
return true
|
||||
}
|
||||
|
||||
fun saveProfiles() {
|
||||
defaultPreferences.edit().putString(
|
||||
PROFILES,
|
||||
JSONArray(profiles).toString()
|
||||
).apply()
|
||||
}
|
||||
|
||||
fun loadProfiles() {
|
||||
profiles.clear()
|
||||
JSONArray(
|
||||
defaultPreferences.getString(PROFILES, "[]")
|
||||
).let {
|
||||
profiles.addAll(Array(it.length()) { i -> it.getString(i) })
|
||||
}
|
||||
}
|
||||
|
||||
fun update() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
preferences.getStringSet(BARCODE_FORMATS, barcodeFormats)?.let {
|
||||
barcodeFormats = it
|
||||
barcodeFormats = addFormatsOnUpdate(
|
||||
it,
|
||||
BarcodeFormat.RMQR_CODE,
|
||||
BarcodeFormat.DX_FILM_EDGE
|
||||
)
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -249,10 +318,20 @@ class Preferences {
|
||||
beepToneName = it
|
||||
}
|
||||
useHistory = preferences.getBoolean(USE_HISTORY, useHistory)
|
||||
ignoreConsecutiveDuplicates = preferences.getBoolean(
|
||||
IGNORE_CONSECUTIVE_DUPLICATES,
|
||||
ignoreConsecutiveDuplicates
|
||||
)
|
||||
|
||||
// Map old setting to new one if it wasn't on default value.
|
||||
val ignoreConsecutiveDuplicates = "ignore_consecutive_duplicates"
|
||||
if (!preferences.getBoolean(ignoreConsecutiveDuplicates, true)) {
|
||||
preferences.edit().remove(ignoreConsecutiveDuplicates).apply()
|
||||
ignoreDuplicatesName = "accept_duplicates"
|
||||
}
|
||||
|
||||
preferences.getString(
|
||||
IGNORE_DUPLICATES_NAME,
|
||||
ignoreDuplicatesName
|
||||
)?.also {
|
||||
ignoreDuplicatesName = it
|
||||
}
|
||||
copyImmediately = preferences.getBoolean(
|
||||
COPY_IMMEDIATELY,
|
||||
copyImmediately
|
||||
@@ -263,6 +342,9 @@ class Preferences {
|
||||
)
|
||||
showMetaData = preferences.getBoolean(SHOW_META_DATA, showMetaData)
|
||||
showHexDump = preferences.getBoolean(SHOW_HEX_DUMP, showHexDump)
|
||||
preferences.getString(SHOW_CHECKSUM, showChecksum)?.also {
|
||||
showChecksum = it
|
||||
}
|
||||
showRecreation = preferences.getBoolean(SHOW_RECREATION, showRecreation)
|
||||
closeAutomatically = preferences.getBoolean(
|
||||
CLOSE_AUTOMATICALLY,
|
||||
@@ -305,17 +387,56 @@ class Preferences {
|
||||
INDEX_OF_LAST_SELECTED_EC_LEVEL,
|
||||
indexOfLastSelectedEcLevel
|
||||
)
|
||||
lastMargin = preferences.getInt(
|
||||
LAST_MARGIN,
|
||||
lastMargin
|
||||
)
|
||||
freeRotation = preferences.getBoolean(FREE_ROTATION, freeRotation)
|
||||
expandEscapeSequences = preferences.getBoolean(
|
||||
EXPAND_ESCAPE_SEQUENCES,
|
||||
expandEscapeSequences
|
||||
)
|
||||
addQuietZone = preferences.getBoolean(ADD_QUIET_ZONE, addQuietZone)
|
||||
brightenScreen = preferences.getBoolean(
|
||||
BRIGHTEN_SCREEN,
|
||||
brightenScreen
|
||||
)
|
||||
previewScale = preferences.getFloat(
|
||||
PREVIEW_SCALE,
|
||||
previewScale
|
||||
)
|
||||
automatedActions = fromJsonArray(
|
||||
preferences.getString(AUTOMATED_ACTIONS, "[]") ?: "[]"
|
||||
)
|
||||
}
|
||||
|
||||
fun setAutomatedActions(actions: List<AutomatedAction>) {
|
||||
automatedActions = actions.toMutableList()
|
||||
apply(AUTOMATED_ACTIONS, toJsonArray(actions))
|
||||
}
|
||||
|
||||
private fun addFormatsOnUpdate(
|
||||
restored: Set<String>,
|
||||
vararg formats: BarcodeFormat
|
||||
) = restored.toMutableSet().apply {
|
||||
for (format in formats) {
|
||||
val name = "${format.name}_added"
|
||||
if (!preferences.getBoolean(name, false)) {
|
||||
preferences.edit().putBoolean(name, true).apply()
|
||||
add(format.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun restoreCropHandle(
|
||||
name: String,
|
||||
viewWidth: Int,
|
||||
viewHeight: Int
|
||||
) = preferences.restoreCropHandle(name, viewWidth, viewHeight)
|
||||
|
||||
fun storeCropHandle(
|
||||
name: String,
|
||||
viewWidth: Int,
|
||||
viewHeight: Int,
|
||||
cropHandle: Point
|
||||
) = preferences.storeCropHandle(name, viewWidth, viewHeight, cropHandle)
|
||||
|
||||
fun beepTone() = when (beepToneName) {
|
||||
"tone_cdma_confirm" -> ToneGenerator.TONE_CDMA_CONFIRM
|
||||
"tone_sup_radio_ack" -> ToneGenerator.TONE_SUP_RADIO_ACK
|
||||
@@ -325,12 +446,15 @@ class Preferences {
|
||||
else -> ToneGenerator.TONE_PROP_BEEP
|
||||
}
|
||||
|
||||
fun ignoreDuplicates() = when (ignoreDuplicatesName) {
|
||||
"ignore_consecutive_duplicates" -> IgnoreDuplicates.Consecutive
|
||||
"ignore_any_duplicates" -> IgnoreDuplicates.Any
|
||||
else -> IgnoreDuplicates.Never
|
||||
}
|
||||
|
||||
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 apply(label: String, value: Boolean) {
|
||||
put(label, value).apply()
|
||||
}
|
||||
@@ -343,6 +467,10 @@ class Preferences {
|
||||
preferences.edit().putInt(label, value).apply()
|
||||
}
|
||||
|
||||
private fun apply(label: String, value: Float) {
|
||||
preferences.edit().putFloat(label, value).apply()
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private fun commit(label: String, value: String) {
|
||||
preferences.edit().putString(label, value).commit()
|
||||
@@ -354,10 +482,13 @@ class Preferences {
|
||||
}
|
||||
|
||||
companion object {
|
||||
enum class IgnoreDuplicates {
|
||||
Consecutive, Any, Never
|
||||
}
|
||||
|
||||
private const val PROFILES = "profiles"
|
||||
private const val PROFILE = "profile"
|
||||
private const val BARCODE_FORMATS = "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"
|
||||
@@ -369,11 +500,12 @@ class Preferences {
|
||||
private const val BEEP = "beep"
|
||||
private const val BEEP_TONE_NAME = "beep_tone_name"
|
||||
private const val USE_HISTORY = "use_history"
|
||||
private const val IGNORE_CONSECUTIVE_DUPLICATES = "ignore_consecutive_duplicates"
|
||||
private const val IGNORE_DUPLICATES_NAME = "ignore_duplicates_name"
|
||||
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 SHOW_CHECKSUM = "show_checksum"
|
||||
private const val SHOW_RECREATION = "show_recreation"
|
||||
private const val CLOSE_AUTOMATICALLY = "close_automatically"
|
||||
private const val DEFAULT_SEARCH_URL = "default_search_url"
|
||||
@@ -386,8 +518,11 @@ class Preferences {
|
||||
private const val CUSTOM_LOCALE = "custom_locale"
|
||||
private const val INDEX_OF_LAST_SELECTED_FORMAT = "index_of_last_selected_format"
|
||||
private const val INDEX_OF_LAST_SELECTED_EC_LEVEL = "index_of_last_selected_ec_level"
|
||||
private const val LAST_MARGIN = "last_margin"
|
||||
private const val FREE_ROTATION = "free_rotation"
|
||||
private const val EXPAND_ESCAPE_SEQUENCES = "expand_escape_sequences"
|
||||
private const val ADD_QUIET_ZONE = "add_quiet_zone"
|
||||
private const val BRIGHTEN_SCREEN = "brighten_screen"
|
||||
private const val PREVIEW_SCALE = "preview_scale"
|
||||
private const val AUTOMATED_ACTIONS = "automated_actions"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,13 @@ fun String.unescape(): String {
|
||||
val sb = StringBuilder()
|
||||
while (i < len) {
|
||||
val ch = get(i++)
|
||||
sb.append(if (ch == '\\' && i < len) expand() else ch)
|
||||
sb.append(
|
||||
if (ch == '\\' && i < len) {
|
||||
expand()
|
||||
} else {
|
||||
ch
|
||||
}
|
||||
)
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ 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.support.v7.widget.Toolbar
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.AbsListView
|
||||
@@ -25,7 +26,8 @@ val systemBarListViewScrollListener = object : AbsListView.OnScrollListener {
|
||||
view.post {
|
||||
val scrolled = firstVisibleItem > 0 ||
|
||||
(totalItemCount > 0 && firstChildScrolled(view))
|
||||
val scrollable = scrolled || totalItemCount > 0 && lastChildOutOfView(view)
|
||||
val scrollable = scrolled ||
|
||||
totalItemCount > 0 && lastChildOutOfView(view)
|
||||
colorSystemAndToolBars(view.context, scrolled, scrollable)
|
||||
}
|
||||
}
|
||||
@@ -37,8 +39,10 @@ val systemBarListViewScrollListener = object : AbsListView.OnScrollListener {
|
||||
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() <
|
||||
val scrolled =
|
||||
layoutManager.findFirstCompletelyVisibleItemPosition() != 0
|
||||
val scrollable = scrolled ||
|
||||
layoutManager.findLastVisibleItemPosition() <
|
||||
recyclerView.adapter.itemCount - 1
|
||||
colorSystemAndToolBars(recyclerView.context, scrolled, scrollable)
|
||||
}
|
||||
@@ -57,10 +61,9 @@ private fun lastChildOutOfView(listView: AbsListView): Boolean {
|
||||
return child != null && child.bottom >= listView.height
|
||||
}
|
||||
|
||||
fun initSystemBars(activity: AppCompatActivity) {
|
||||
fun AppCompatActivity.initBars() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// Keeps the soft keyboard from repositioning the layout.
|
||||
val window = activity.window
|
||||
window.setSoftInputMode(
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
)
|
||||
@@ -69,7 +72,16 @@ fun initSystemBars(activity: AppCompatActivity) {
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
}
|
||||
colorSystemAndToolBars(activity)
|
||||
colorSystemAndToolBars(this)
|
||||
setPaddingFromWindowInsets(
|
||||
findViewById(R.id.main),
|
||||
(findViewById(R.id.toolbar) as Toolbar).apply {
|
||||
setSupportActionBar(this)
|
||||
},
|
||||
findViewById(R.id.navbar)?.apply {
|
||||
setBackgroundColor(translucentPrimaryColor)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private var statusBarColorLocked = false
|
||||
@@ -97,14 +109,27 @@ fun colorSystemAndToolBars(
|
||||
val topColor = if (scrolled) translucentPrimaryColor else 0
|
||||
val activity = getAppCompatActivity(context) ?: return
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val window = activity.window
|
||||
if (!statusBarColorLocked) {
|
||||
window.statusBarColor = topColor
|
||||
}
|
||||
window.navigationBarColor = if (scrolled || scrollable) {
|
||||
translucentPrimaryColor
|
||||
// System bars no longer have a background from SDK35+.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
val window = activity.window
|
||||
if (!statusBarColorLocked) {
|
||||
@Suppress("DEPRECATION")
|
||||
window.statusBarColor = topColor
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
window.navigationBarColor = if (scrolled || scrollable) {
|
||||
translucentPrimaryColor
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
0
|
||||
activity.findViewById(R.id.navbar)?.apply {
|
||||
visibility = if (scrolled || scrollable) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
activity.supportActionBar?.setBackgroundDrawable(
|
||||
|
||||
@@ -7,10 +7,41 @@ import android.support.v4.view.ViewCompat
|
||||
import android.support.v4.view.WindowInsetsCompat
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
|
||||
private var toolbarHeight = 0
|
||||
fun recordToolbarHeight(toolbar: Toolbar) {
|
||||
fun setPaddingFromWindowInsets(
|
||||
mainLayout: View,
|
||||
toolbar: Toolbar,
|
||||
navbar: View?
|
||||
) {
|
||||
toolbarHeight = toolbar.layoutParams.height
|
||||
// SDK 35+ is edge-to-edge by default, so the toolbar needs to extend
|
||||
// below the status bar.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
return
|
||||
}
|
||||
mainLayout.setOnApplyWindowInsetsListener { _, insets ->
|
||||
val systemBarInsets = insets.getInsets(
|
||||
WindowInsets.Type.systemBars()
|
||||
)
|
||||
|
||||
val statusBarTop = systemBarInsets.top
|
||||
if (toolbar.paddingTop == 0 && statusBarTop > 0) {
|
||||
toolbar.setPadding(0, statusBarTop, 0, 0)
|
||||
toolbar.layoutParams.height += statusBarTop
|
||||
}
|
||||
|
||||
val navBarBottom = systemBarInsets.bottom
|
||||
if (navBarBottom > 0) {
|
||||
mainLayout.setPadding(0, 0, 0, navBarBottom);
|
||||
navbar?.apply {
|
||||
layoutParams.height = navBarBottom
|
||||
}
|
||||
}
|
||||
|
||||
insets
|
||||
}
|
||||
}
|
||||
|
||||
fun View.setPaddingFromWindowInsets() {
|
||||
|
||||
+5
@@ -10,6 +10,8 @@ import de.markusfisch.android.scalingimageview.widget.ScalingImageView
|
||||
open class ConfinedScalingImageView : ScalingImageView {
|
||||
val insets = Rect()
|
||||
|
||||
var runAfterLayout: (() -> Unit)? = null
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyle: Int) :
|
||||
super(context, attrs, defStyle)
|
||||
|
||||
@@ -37,5 +39,8 @@ open class ConfinedScalingImageView : ScalingImageView {
|
||||
)
|
||||
}
|
||||
centerRemap()
|
||||
runAfterLayout?.invoke()
|
||||
}
|
||||
|
||||
fun isInBounds(): Boolean = inBounds()
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ class ConfinedScrollView : ScrollView {
|
||||
// not putting it on the queue makes it only work sometimes.
|
||||
post {
|
||||
getChildAt(0)?.also { child ->
|
||||
scrollable = height < child.height + paddingTop + paddingBottom
|
||||
scrollable = height <
|
||||
child.height + paddingTop + paddingBottom
|
||||
colorSystemAndToolBars(
|
||||
context,
|
||||
scrollY > 0,
|
||||
|
||||
@@ -2,10 +2,13 @@ package de.markusfisch.android.binaryeye.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Region
|
||||
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
|
||||
@@ -23,10 +26,10 @@ class DetectorView : View {
|
||||
val coordinates = FloatArray(32)
|
||||
val roi = Rect()
|
||||
|
||||
var cropHandleName: String? = null
|
||||
var onRoiChange: (() -> Unit)? = null
|
||||
var onRoiChanged: (() -> Unit)? = null
|
||||
|
||||
private val currentOrientation = resources.configuration.orientation
|
||||
private val invalidateRunnable: Runnable = Runnable {
|
||||
coordinatesLast = 0
|
||||
invalidate()
|
||||
@@ -42,7 +45,8 @@ class DetectorView : View {
|
||||
)
|
||||
private val handleXRadius = handleBitmap.width / 2
|
||||
private val handleYRadius = handleBitmap.height / 2
|
||||
private val handlePos = Point(-1, -1)
|
||||
private val inactiveHandlePos = Point(-1, -1)
|
||||
private val handlePos = Point(inactiveHandlePos)
|
||||
private val handleHome = Point()
|
||||
private val center = Point()
|
||||
private val touchDown = Point()
|
||||
@@ -52,6 +56,8 @@ class DetectorView : View {
|
||||
private val fabHeight: Int
|
||||
private val padding: Int
|
||||
|
||||
private var viewWidth = 0
|
||||
private var viewHeight = 0
|
||||
private var coordinatesLast = 0
|
||||
private var handleGrabbed = false
|
||||
private var handleActive = false
|
||||
@@ -78,37 +84,38 @@ class DetectorView : View {
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
|
||||
super(context, attrs, defStyleAttr)
|
||||
|
||||
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
|
||||
fun storeCropHandlePos() {
|
||||
val name = cropHandleName ?: return
|
||||
val pos = if (handleActive) {
|
||||
handlePos
|
||||
} else {
|
||||
inactiveHandlePos
|
||||
}
|
||||
prefs.storeCropHandle(
|
||||
name,
|
||||
viewWidth,
|
||||
viewHeight,
|
||||
pos
|
||||
)
|
||||
}
|
||||
|
||||
private fun setCropHandlePos(x: Int, y: Int, orientation: Int) {
|
||||
// Always set handlePos even if it's invalid because
|
||||
// handlePos.x may be -2 which is a signal to set the
|
||||
// default ROI.
|
||||
if (orientation == currentOrientation) {
|
||||
handlePos.set(x, y)
|
||||
} else {
|
||||
handlePos.set(y, x)
|
||||
fun updateCropHandlePos() {
|
||||
val name = cropHandleName ?: return
|
||||
if (viewWidth < 1 || viewHeight < 1) {
|
||||
return
|
||||
}
|
||||
val cropHandle = prefs.restoreCropHandle(name, viewWidth, viewHeight)
|
||||
if (cropHandle.x == -2) {
|
||||
setHandleToDefaultRoi()
|
||||
} else {
|
||||
handlePos.set(cropHandle.x, cropHandle.y)
|
||||
handleActive = handlePos.x > -1
|
||||
}
|
||||
if (handleActive) {
|
||||
updateClipRect()
|
||||
} else {
|
||||
reset()
|
||||
}
|
||||
handleActive = handlePos.x > -1
|
||||
}
|
||||
|
||||
fun update(numberOfCoordinates: Int) {
|
||||
@@ -118,31 +125,6 @@ class DetectorView : View {
|
||||
postDelayed(invalidateRunnable, 500)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(): Parcelable? {
|
||||
if (!handleActive) {
|
||||
return super.onSaveInstanceState()
|
||||
}
|
||||
return SavedState(super.onSaveInstanceState()).apply {
|
||||
savedHandlePos.set(getCropHandlePos())
|
||||
savedOrientation = currentOrientation
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(state: Parcelable) {
|
||||
super.onRestoreInstanceState(
|
||||
if (state is SavedState) {
|
||||
setCropHandlePos(
|
||||
state.savedHandlePos.x,
|
||||
state.savedHandlePos.y,
|
||||
state.savedOrientation
|
||||
)
|
||||
state.superState
|
||||
} else {
|
||||
state
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||
event ?: return super.onTouchEvent(event)
|
||||
@@ -239,28 +221,27 @@ class DetectorView : View {
|
||||
roi.set(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
override fun onLayout(
|
||||
changed: Boolean,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int
|
||||
) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
val width = right - left
|
||||
val height = bottom - top
|
||||
viewWidth = right - left
|
||||
viewHeight = bottom - top
|
||||
center.set(
|
||||
left + (width / 2),
|
||||
top + (height / 2)
|
||||
left + (viewWidth / 2),
|
||||
top + (viewHeight / 2)
|
||||
)
|
||||
minY = padding * 2
|
||||
maxY = height - minY
|
||||
maxY = viewHeight - minY
|
||||
handleHome.set(
|
||||
width - handleXRadius - paddingRight - padding,
|
||||
height - handleYRadius - paddingBottom - fabHeight
|
||||
viewWidth - handleXRadius - paddingRight - padding,
|
||||
viewHeight - handleYRadius - paddingBottom - fabHeight
|
||||
)
|
||||
if (handlePos.x == -2) {
|
||||
setHandleToDefaultRoi()
|
||||
}
|
||||
if (handleActive) {
|
||||
updateClipRect()
|
||||
} else {
|
||||
reset()
|
||||
}
|
||||
updateCropHandlePos()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
@@ -344,37 +325,6 @@ class DetectorView : View {
|
||||
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()
|
||||
|
||||
var savedOrientation = 0
|
||||
|
||||
constructor(superState: Parcelable?) : super(superState)
|
||||
|
||||
private constructor(parcel: Parcel) : super(parcel) {
|
||||
savedHandlePos.set(
|
||||
parcel.readInt(),
|
||||
parcel.readInt()
|
||||
)
|
||||
savedOrientation = parcel.readInt()
|
||||
}
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
super.writeToParcel(out, flags)
|
||||
out.writeInt(savedHandlePos.x)
|
||||
out.writeInt(savedHandlePos.y)
|
||||
out.writeInt(savedOrientation)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATOR = object : Parcelable.Creator<SavedState> {
|
||||
override fun createFromParcel(source: Parcel) = SavedState(source)
|
||||
override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun distSq(a: Point, b: Point): Int {
|
||||
|
||||
@@ -3,19 +3,29 @@ package de.markusfisch.android.binaryeye.widget
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
|
||||
fun Context.toast(message: Int) = Toast.makeText(
|
||||
applicationContext,
|
||||
message,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
private var lastToast: Toast? = null
|
||||
|
||||
fun Context.toast(message: String) = Toast.makeText(
|
||||
applicationContext,
|
||||
message.ellipsize(128),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
fun <T> Context.toast(message: T) {
|
||||
lastToast?.cancel()
|
||||
lastToast = when (message) {
|
||||
is String -> Toast.makeText(
|
||||
applicationContext,
|
||||
message.ellipsize(),
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
|
||||
private fun String.ellipsize(max: Int) = if (length < max) {
|
||||
is Int -> Toast.makeText(
|
||||
applicationContext,
|
||||
message,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
|
||||
else -> return
|
||||
}
|
||||
lastToast?.show()
|
||||
}
|
||||
|
||||
private fun String.ellipsize(max: Int = 128) = if (length < max) {
|
||||
this
|
||||
} else {
|
||||
"${take(max)}…"
|
||||
|
||||
@@ -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_history_foreground"/>
|
||||
</adaptive-icon>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 835 B |
Binary file not shown.
|
After Width: | Height: | Size: 471 B |
Binary file not shown.
|
After Width: | Height: | Size: 590 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,16 @@
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:constantSize="true">
|
||||
<item android:state_pressed="false">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="2dp"/>
|
||||
<solid android:color="@color/selected_row"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="2dp"/>
|
||||
<solid android:color="@color/pressed_row"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
<path android:fillColor="#fff" android:pathData="M20 8.69V4h-4.69L12 0.69 8.69 4H4v4.69L0.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69zM12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6zm0-10c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"/>
|
||||
</vector>
|
||||
@@ -0,0 +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="M14.54 18.49v-1.98h2.97v-2.97h1.98v2.97h2.97v1.98l-2.952 0.018-0.018 2.952h-1.98l-0.018-2.952-2.952-0.018zm-1.19-3.74L11 12.4V7h2v4.6l1.4 1.4-1.05 1.75zM3 10V4h2v2.35c0.85-1.067 1.888-1.892 3.113-2.475C9.338 3.292 10.633 3 12 3c2.5 0 4.625 0.875 6.375 2.625S21 9.5 21 12h-2c0-1.95-0.679-3.604-2.037-4.963C15.604 5.679 13.95 5 12 5c-1.15 0-2.225 0.267-3.225 0.8S6.933 7.067 6.25 8H9v2H3zm9 11c-2.3 0-4.304-0.763-6.012-2.288C4.279 17.187 3.3 15.283 3.05 13H5.1c0.233 1.733 1.004 3.167 2.313 4.3C8.721 18.433 10.25 19 12 19c0.183 0 0.354-0.004 0.513-0.013 0.158-0.008 0.32-0.029 0.487-0.062v2.025c-0.167 0.017-0.329 0.029-0.487 0.037C12.354 20.996 12.183 21 12 21z"/>
|
||||
</vector>
|
||||
@@ -0,0 +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="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
||||
<path android:fillColor="#fff" android:pathData="M480,480Q414,480 367,433Q320,386 320,320Q320,254 367,207Q414,160 480,160Q546,160 593,207Q640,254 640,320Q640,386 593,433Q546,480 480,480ZM160,800L160,688Q160,654 177.5,625.5Q195,597 224,582Q286,551 350,535.5Q414,520 480,520Q546,520 610,535.5Q674,551 736,582Q765,597 782.5,625.5Q800,654 800,688L800,800L160,800ZM240,720L720,720L720,688Q720,677 714.5,668Q709,659 700,654Q646,627 591,613.5Q536,600 480,600Q424,600 369,613.5Q314,627 260,654Q251,659 245.5,668Q240,677 240,688L240,720ZM480,400Q513,400 536.5,376.5Q560,353 560,320Q560,287 536.5,263.5Q513,240 480,240Q447,240 423.5,263.5Q400,287 400,320Q400,353 423.5,376.5Q447,400 480,400ZM480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320ZM480,720L480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720L480,720Z"/>
|
||||
</vector>
|
||||
@@ -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="M54 72.9c-4.83 0-9.04-1.6-12.63-4.8-3.59-3.21-5.64-7.2-6.17-12h4.31c0.49 3.64 2.11 6.65 4.86 9.03 2.74 2.38 5.95 3.57 9.63 3.57 4.1 0 7.57-1.42 10.42-4.28C67.28 61.57 68.7 58.1 68.7 54s-1.42-7.57-4.28-10.42C61.57 40.72 58.1 39.3 54 39.3c-2.42 0-4.67 0.56-6.77 1.68-2.1 1.12-3.87 2.66-5.31 4.62h5.78v4.2H35.1V37.2h4.2v4.93c1.78-2.24 3.96-3.97 6.54-5.19 2.57-1.23 5.29-1.84 8.16-1.84 2.63 0 5.08 0.5 7.38 1.49 2.29 1 4.29 2.35 5.98 4.05 1.7 1.69 3.05 3.69 4.05 5.98 0.99 2.3 1.49 4.75 1.49 7.38s-0.5 5.08-1.49 7.38c-1 2.29-2.35 4.29-4.05 5.98-1.69 1.7-3.69 3.05-5.98 4.05-2.3 0.99-4.75 1.49-7.38 1.49zm5.88-10.08l-7.98-7.98V43.5h4.2v9.66l6.72 6.72-2.94 2.94z" android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
@@ -8,9 +8,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
android:fitsSystemWindows="@bool/fitsSystemWindows">
|
||||
<include layout="@layout/toolbar"/>
|
||||
<SeekBar
|
||||
android:id="@+id/zoom"
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
android:fitsSystemWindows="@bool/fitsSystemWindows">
|
||||
<include layout="@layout/toolbar"/>
|
||||
</FrameLayout>
|
||||
<include layout="@layout/navbar"/>
|
||||
</merge>
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
android:fitsSystemWindows="@bool/fitsSystemWindows">
|
||||
<include layout="@layout/toolbar"/>
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
style="@style/FAB"
|
||||
@@ -19,4 +20,5 @@
|
||||
android:contentDescription="@string/scan_code"
|
||||
android:src="@drawable/ic_action_next"/>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
<include layout="@layout/navbar"/>
|
||||
</merge>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
<!-- The FrameLayout is necessary because this LinearLayout
|
||||
should have a padding which would be reset by the window
|
||||
insets from `fitsSystemWindows`. -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="bottom"
|
||||
android:padding="24dp"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="UselessParent">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:drawableTop="@drawable/logo"
|
||||
android:drawablePadding="16dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:text="@string/welcome"/>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/WelcomeButton"
|
||||
android:id="@+id/expert"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
style="@style/WelcomeButtonText"
|
||||
android:text="@string/mode_expert"/>
|
||||
<TextView
|
||||
style="@style/WelcomeButtonSubText"
|
||||
android:text="@string/mode_expert_description"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/WelcomeButton"
|
||||
android:id="@+id/simple"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
style="@style/WelcomeButtonText"
|
||||
android:text="@string/mode_simple"/>
|
||||
<TextView
|
||||
style="@style/WelcomeButtonSubText"
|
||||
android:text="@string/mode_simple_description"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</ScrollView>
|
||||
@@ -0,0 +1,76 @@
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/automated_action_regex"/>
|
||||
<EditText
|
||||
android:id="@+id/regex"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/automated_action_regex_hint"
|
||||
android:inputType="text"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="12dp"
|
||||
android:text="@string/automated_action_type"/>
|
||||
<RadioGroup
|
||||
android:id="@+id/action_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<RadioButton
|
||||
android:id="@+id/action_type_intent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/automated_action_type_intent"/>
|
||||
<RadioButton
|
||||
android:id="@+id/action_type_custom_intent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/automated_action_type_custom_intent"/>
|
||||
</RadioGroup>
|
||||
<LinearLayout
|
||||
android:id="@+id/url_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/automated_action_url_template"/>
|
||||
<EditText
|
||||
android:id="@+id/url_template"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/automated_action_url_hint"
|
||||
android:inputType="textUri"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/intent_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/automated_action_intent_uri_template"/>
|
||||
<EditText
|
||||
android:id="@+id/intent_uri_template"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/automated_action_intent_uri_hint"
|
||||
android:inputType="textUri"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -0,0 +1,22 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/find_code_with_content"/>
|
||||
<EditText
|
||||
android:id="@+id/term"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textShortMessage"
|
||||
android:imeOptions="actionDone"
|
||||
android:hint="@string/find_code_hint"
|
||||
android:maxLines="1"
|
||||
android:maxLength="1024"
|
||||
android:importantForAutofill="no"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,22 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/profile_enter_name"/>
|
||||
<EditText
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textShortMessage|textAutoCorrect|textCapSentences"
|
||||
android:imeOptions="actionDone"
|
||||
android:hint="@string/profile_enter_name_hint"
|
||||
android:maxLines="1"
|
||||
android:maxLength="1024"
|
||||
android:importantForAutofill="no"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,23 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:id="@+id/size_display"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
<SeekBar
|
||||
style="@style/Widget.AppCompat.SeekBar.Discrete"
|
||||
android:id="@+id/size_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:progress="2"
|
||||
android:max="4"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,31 @@
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ListView
|
||||
android:id="@+id/actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:divider="@color/separator"
|
||||
android:dividerHeight="1px"/>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/inset_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:visibility="gone"
|
||||
android:id="@+id/no_actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:padding="32dp"
|
||||
android:text="@string/automated_actions_empty"/>
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
style="@style/FAB"
|
||||
android:id="@+id/add"
|
||||
android:contentDescription="@string/automated_action_add"
|
||||
android:src="@drawable/ic_action_create"/>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</FrameLayout>
|
||||
@@ -13,6 +13,17 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<EditText
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="start|top"
|
||||
android:hint="@string/label"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
tools:ignore="UnusedAttribute"/>
|
||||
<EditText
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -65,42 +65,12 @@
|
||||
android:id="@+id/colors"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:entries="@array/colors"/>
|
||||
<TextView
|
||||
android:id="@+id/size_display"
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:visibility="gone"
|
||||
android:id="@+id/add_quiet_zone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
<SeekBar
|
||||
style="@style/Widget.AppCompat.SeekBar.Discrete"
|
||||
android:id="@+id/size_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:progress="4"
|
||||
android:max="7"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/margin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
<TextView
|
||||
android:id="@+id/margin_display"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
<SeekBar
|
||||
style="@style/Widget.AppCompat.SeekBar.Discrete"
|
||||
android:id="@+id/margin_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:max="32"/>
|
||||
</LinearLayout>
|
||||
android:text="@string/add_quiet_zone"/>
|
||||
</LinearLayout>
|
||||
</de.markusfisch.android.binaryeye.widget.ConfinedScrollView>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ListView
|
||||
android:id="@+id/profiles"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:divider="@color/separator"
|
||||
android:dividerHeight="1px"/>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/inset_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:visibility="gone"
|
||||
android:id="@+id/no_profiles"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:padding="32dp"
|
||||
android:text="@string/profile_empty"/>
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
style="@style/FAB"
|
||||
android:id="@+id/add"
|
||||
android:contentDescription="@string/profile_add"
|
||||
android:src="@drawable/ic_action_create"/>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
<!-- Navigation bar background from SDK35+ -->
|
||||
<View
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/navbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:visibility="gone"/>
|
||||
@@ -32,6 +32,11 @@
|
||||
android:title="@string/restrict_format"
|
||||
android:icon="@drawable/ic_action_restrict_format"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/find_code"
|
||||
android:title="@string/find_code"
|
||||
android:icon="@drawable/ic_action_search"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/preferences"
|
||||
android:title="@string/preferences"
|
||||
@@ -42,4 +47,9 @@
|
||||
android:title="@string/info"
|
||||
android:icon="@drawable/ic_action_info"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/profile"
|
||||
android:title="@string/profile_default"
|
||||
android:icon="@drawable/ic_action_profile"
|
||||
material:showAsAction="ifRoom"/>
|
||||
</menu>
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:material="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/add_to_history"
|
||||
android:title="@string/add_to_history"
|
||||
android:icon="@drawable/ic_action_history_add"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
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/copy_as_deeplink"
|
||||
android:title="@string/copy_as_deeplink"
|
||||
android:icon="@drawable/ic_action_link"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/export_to_file"
|
||||
android:title="@string/export_to_file"
|
||||
android:icon="@drawable/ic_action_save"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/brighten_screen"
|
||||
android:title="@string/brighten_screen"
|
||||
android:icon="@drawable/ic_action_bright"
|
||||
android:checkable="true"
|
||||
material:showAsAction="ifRoom"/>
|
||||
</menu>
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
android:title="@string/copy_to_clipboard"
|
||||
android:icon="@drawable/ic_action_copy"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/copy_as_deeplink"
|
||||
android:title="@string/copy_as_deeplink"
|
||||
android:icon="@drawable/ic_action_link"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/share"
|
||||
android:title="@string/share"
|
||||
@@ -26,7 +31,7 @@
|
||||
<item
|
||||
android:id="@+id/share_file"
|
||||
android:title="@string/share_file"
|
||||
android:icon="@drawable/ic_action_share"
|
||||
android:icon="@drawable/ic_action_pick_file"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/create"
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
android:icon="@drawable/ic_action_copy"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/edit_scan"
|
||||
android:title="@string/edit_scan"
|
||||
android:id="@+id/edit_label"
|
||||
android:title="@string/edit_label"
|
||||
android:icon="@drawable/ic_action_edit"
|
||||
material:showAsAction="ifRoom"/>
|
||||
<item
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="welcome">مرحبًا!</string>
|
||||
<string name="mode_expert">متقدم</string>
|
||||
<string name="mode_expert_description">أود التحقق من المحتوى المقروء وفتحه يدويًا.</string>
|
||||
<string name="mode_simple">بسيط</string>
|
||||
<string name="mode_simple_description">أريد فقط قراءة الباركود وفتحه فورًا.</string>
|
||||
<string name="camera_error">يتعذر الوصول إلى الكاميرا، يرجى المحاولة مرة أخرى</string>
|
||||
<string name="scan_code">مسح الرمز</string>
|
||||
<string name="compose_barcode">إنشاء رمز Bardcode</string>
|
||||
<string name="decode_barcode">فك رمز Barcode</string>
|
||||
<string name="content">المحتوى</string>
|
||||
<string name="binary_data">(بيانات ثنائية)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="zero">%2$d حروف، %1$s</item>
|
||||
<item quantity="one">حرف واحد، %1$s</item>
|
||||
<item quantity="two">حرفين، %1$s</item>
|
||||
<item quantity="few">%2$d حروف، %1$s</item>
|
||||
<item quantity="many">%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">منخفض (~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="label">العنوان</string>
|
||||
<string name="colors">المقدمة والخلفية</string>
|
||||
<string name="colors_black_on_white">أسود على خلفية بيضاء</string>
|
||||
<string name="colors_white_on_black">أبيض على خلفية سوداء</string>
|
||||
<string name="colors_black_on_transparent">أسود على خلفية شفافة</string>
|
||||
<string name="colors_white_on_transparent">أبيض على خلفية شفافة</string>
|
||||
<string name="sequence_size">حجم التسلسل</string>
|
||||
<string name="sequence_index">رقم التسلسل</string>
|
||||
<string name="sequence_id">معرف التسلسل</string>
|
||||
<string name="gtin_issue_number">رقم الإصدار</string>
|
||||
<string name="gtin_country">بلد التصنيع المحتمل</string>
|
||||
<string name="gtin_price">السعر المقترح</string>
|
||||
<string name="gtin_add_on">إضافة UPC EAN</string>
|
||||
<string name="barcode_version_number">الإصدار</string>
|
||||
<string name="qr_version_and_modules" tools:ignore="PluralsCandidate">%1$s (%2$d وحدات)</string>
|
||||
<string name="qr_data_mask">قناع البيانات</string>
|
||||
<string name="toggle_flash">تشغيل/إيقاف الوميض</string>
|
||||
<string name="error_flash">خطأ في تشغيل الوميض</string>
|
||||
<string name="share">مشاركة</string>
|
||||
<string name="share_file">مشاركة كملف</string>
|
||||
<string name="share_as">مشاركة كـ؟</string>
|
||||
<string name="copy_to_clipboard">نسخ إلى الحافظة</string>
|
||||
<string name="copy_as_deeplink">نسخ كرابط عميق</string>
|
||||
<string name="copied_to_clipboard">تم النسخ إلى الحافظة</string>
|
||||
<string name="copy_password">نسخ كلمة المرور إلى الحافظة</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_width_by_height">الحجم بالبكسل: %1$d×%2$d</string>
|
||||
<string name="size_no_magnification">بدون تكبير</string>
|
||||
<string name="add_quiet_zone">إضافة منطقة هادئة</string>
|
||||
<string name="input_content_here">أدخل المحتوى هنا</string>
|
||||
<string name="unescape">توسيع رموز الهروب (escape sequences)</string>
|
||||
<string name="encode">ترميز</string>
|
||||
<string name="error_no_content">المحتوى مفقود</string>
|
||||
<string name="error_encoding_barcode">لا يمكن إنشاء رمز Barcode</string>
|
||||
<string name="view_barcode">عرض رمز 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">الفارق الزمني بين عمليات المسح المستمرة</string>
|
||||
<string name="restrict_format">تحديد التنسيق</string>
|
||||
<string name="remove_restriction">إزالة التحديد</string>
|
||||
<string name="find_code">العثور على الرمز</string>
|
||||
<string name="find_code_with_content">ما المحتوى؟</string>
|
||||
<string name="find_code_hint">مصطلح أو تعبير عادي</string>
|
||||
<string name="does_not_match_search_term">لا يطابق مصطلح البحث</string>
|
||||
<string name="scan_for">مسح %s</string>
|
||||
<string name="quarter_second">ربع ثانية</string>
|
||||
<string name="half_second">نصف ثانية</string>
|
||||
<string name="a_second">ثانية واحدة</string>
|
||||
<string name="two_seconds">ثانيتان</string>
|
||||
<string name="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="profile_category">الملف الشخصي</string>
|
||||
<string name="profile">الملف الشخصي</string>
|
||||
<string name="profile_default">افتراضي</string>
|
||||
<string name="profile_add">إضافة ملف شخصي جديد</string>
|
||||
<string name="profile_enter_name">أدخل اسم الملف الشخصي</string>
|
||||
<string name="profile_enter_name_hint">الاسم</string>
|
||||
<string name="profile_invalid_name">اسم الملف الشخصي غير صالح</string>
|
||||
<string name="profile_empty">لا توجد ملفات شخصية بعد</string>
|
||||
<string name="profile_current">الملف الشخصي الحالي</string>
|
||||
<string name="profile_remove_confirm">إزالة هذا الملف الشخصي؟</string>
|
||||
<string name="current_profile">الملف الشخصي: %s</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</string>
|
||||
<string name="zoom_by_swiping">مرر لأعلى أو لأسفل للتكبير/التصغير</string>
|
||||
<string name="zoom_by_swiping_summary">مرر لأعلى أو لأسفل على واجهة الكاميرا للتحكم في التكبير/التصغير لها.</string>
|
||||
<string name="auto_rotate">التعرف على رموز Barcode العمودية أحادية الأبعاد</string>
|
||||
<string name="auto_rotate_summary">تدوير إطار الكاميرا تلقائيًا للتعرف على رموز Barcode العمودية أحادية الأبعاد. بناءً على الجهاز المستخدم، قد يؤثر ذلك على الأداء.</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="beep">صوت صفير عند الكشف</string>
|
||||
<string name="beep_summary">قم بتمكين هذا إذا كنت تريد أن يصدر جهازك صوتًا عند التعرف على رمز.</string>
|
||||
<string name="beep_tone">نغمة الصفير</string>
|
||||
<string name="tone_cdma_confirm">تأكيد CDMA</string>
|
||||
<string name="tone_sup_confirm">تأكيد الإشراف على الاتصال</string>
|
||||
<string name="tone_sup_radio_ack">إقرار مسار الراديو الإشرافي على الاتصال</string>
|
||||
<string name="tone_prop_ack">إقرار إيجابي</string>
|
||||
<string name="tone_prop_beep">صوت صفير عام</string>
|
||||
<string name="tone_prop_beep2">صوت صفير مزدوج عام</string>
|
||||
<string name="use_history">حفظ تاريخ المسح</string>
|
||||
<string name="use_history_summary">حفظ الرموز المتعرف عليها على جهازك.</string>
|
||||
<string name="ignore_duplicates">تجاهل النسخ المكررة</string>
|
||||
<string name="ignore_consecutive_duplicates">النسخ المكررة المتتالية</string>
|
||||
<string name="ignore_any_duplicates">أي نسخ مكررة</string>
|
||||
<string name="accept_duplicates">قبول النسخ المكررة</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">عرض بيانات إضافية حول رمز Barcode الممسوح.</string>
|
||||
<string name="show_hex_dump">عرض التفريغ سداسي عشري</string>
|
||||
<string name="show_hex_dump_summary">عرض تفريغ سداسي عشري للمحتويات الممسوحة.</string>
|
||||
<string name="show_checksum">عرض الـ checksum</string>
|
||||
<string name="no_checksum">الـ checksum غير متوفر</string>
|
||||
<string name="show_recreation">إظهار رمز Barcode المعاد إنشاؤه</string>
|
||||
<string name="show_recreation_summary">إعادة إنشاء وإظهار رمز Barcode من المحتوى الذي تمت قراءته إن أمكن.</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">فتح البيانات المجهولة باستخدام رابط</string>
|
||||
|
||||
<string name="automated_actions">الإجراءات الآلية</string>
|
||||
<string name="automated_actions_none">لا توجد إجراءات آلية</string>
|
||||
<string name="automated_actions_empty">لا توجد إجراءات آلية بعد</string>
|
||||
<plurals name="automated_actions_count">
|
||||
<item quantity="zero">%d إجراء آلي</item>
|
||||
<item quantity="one">%d إجراء آلي</item>
|
||||
<item quantity="two">%d إجراءان آليان</item>
|
||||
<item quantity="few">%d إجراءات آلية</item>
|
||||
<item quantity="many">%d إجراءً آليًا</item>
|
||||
<item quantity="other">%d إجراءات آلية</item>
|
||||
</plurals>
|
||||
<string name="automated_action_add">إضافة إجراء</string>
|
||||
<string name="automated_action_edit">تعديل الإجراء</string>
|
||||
<string name="automated_action_regex">تعبير نمطي</string>
|
||||
<string name="automated_action_regex_hint">مثل ^https?://.*</string>
|
||||
<string name="automated_action_regex_required">التعبير النمطي مطلوب</string>
|
||||
<string name="automated_action_regex_invalid">تعبير نمطي غير صالح</string>
|
||||
<string name="automated_action_type">نوع الإجراء</string>
|
||||
<string name="automated_action_type_intent">فتح URL</string>
|
||||
<string name="automated_action_url_template">قالب URL</string>
|
||||
<string name="automated_action_url_hint">https://example.com/?q={RESULT}</string>
|
||||
<string name="automated_action_url_required">قالب URL مطلوب</string>
|
||||
<string name="automated_action_invalid_url">قالب URL غير صالح</string>
|
||||
<string name="automated_action_remove_confirm">إزالة هذا الإجراء؟</string>
|
||||
<string name="automated_action_list_intent">فتح URL: %s</string>
|
||||
<string name="automated_action_list_intent_empty">فتح URL</string>
|
||||
<string name="automated_action_webview_title">إجراء آلي</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">إعادة توجيه عمليات المسح</string>
|
||||
<string name="send_scan_active_summary">قم بالتمكين لإعادة توجيه عمليات المسح إلى رابط معين.</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">فتح في متصفح خارجي</string>
|
||||
<string name="test_url">رابط للاختبار</string>
|
||||
<string name="send_scan_bluetooth">إرسال عمليات المسح عبر بلوتوث</string>
|
||||
<string name="send_scan_bluetooth_summary">تمكين إرسال عمليات المسح إلى مضيف بلوتوث معين.</string>
|
||||
<string name="send_scan_bluetooth_host">المضيف</string>
|
||||
<string name="no_bluetooth_hosts_paired">لا يوجد اقتران مع مضيفين</string>
|
||||
<string name="bluetooth_connect_fail">تعذر الاتصال بجهاز بلوتوث.</string>
|
||||
<string name="bluetooth_send_fail">تعذر الإرسال إلى جهاز بلوتوث.</string>
|
||||
<string name="bluetooth_send_success">نجح الإرسال إلى جهاز بلوتوث.</string>
|
||||
<string name="generation_category">التوليد</string>
|
||||
<string name="brighten_screen">شاشة ساطعة</string>
|
||||
<string name="brighten_screen_summary">تعيين مستوى السطوع إلى الحد الأقصى لتحسين مسح الرمز من الشاشة.</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_label">تعديل العنوان</string>
|
||||
<string name="remove_scan">حذف عملية المسح</string>
|
||||
<string name="enter_name">أدخل اسمًا لوصف عملية المسح</string>
|
||||
<string name="enter_name_hint">اسم عملية المسح</string>
|
||||
<string name="no_barcode_found">لا يوجد أي رمز Barcode</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">الاتصال بشبكة Wi-Fi</string>
|
||||
<string name="wifi_config_failed">تعذر تهيئة شبكة Wi-Fi</string>
|
||||
<string name="wifi_added">تمت إضافة شبكة Wi-Fi</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="add_to_history">إضافة إلى التاريخ</string>
|
||||
<string name="added_to_history">تمت الإضافة إلى التاريخ</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="toggle_free_rotation">تبديل التدوير الحر</string>
|
||||
<string name="pick_file">اختر ملف</string>
|
||||
<string name="pick_image_file">اختر صورة</string>
|
||||
<string name="pick_code_to_scan">اختر رمزًا لمسحه</string>
|
||||
<string name="shortcut_decode">مسح رمز Barcode</string>
|
||||
<string name="shortcut_encode">إنشاء رمز Barcode</string>
|
||||
<string name="shortcut_history">التاريخ</string>
|
||||
<string name="shortcut_preferences">الإعدادات</string>
|
||||
<string name="background_request_failed">فشل الطلب للعمل في الخلفية</string>
|
||||
<string name="entry_type">النوع</string>
|
||||
<string name="wifi_network">شبكة Wi-Fi</string>
|
||||
<string name="wifi_ssid">الاسم</string>
|
||||
<string name="wifi_type">نوع المصادقة</string>
|
||||
<string name="wifi_password">كلمة المرور</string>
|
||||
<string name="wifi_hidden">شبكة مخفية</string>
|
||||
<string name="wifi_eap">طريقة EAP</string>
|
||||
<string name="wifi_anonymous_identity">هوية مجهولة</string>
|
||||
<string name="wifi_identity">الهوية</string>
|
||||
<string name="wifi_phase2">طريقة Phase 2</string>
|
||||
<string name="network_suggestions">الشبكات المقترحة</string>
|
||||
<string name="remove_suggestion">إزالة الاقتراحات</string>
|
||||
<string name="email_to">المستلم</string>
|
||||
<string name="email_subject">الموضوع</string>
|
||||
<string name="email_body">النص</string>
|
||||
<string name="scheme">المخطط</string>
|
||||
<string name="host">المضيف</string>
|
||||
<string name="query">استعلام</string>
|
||||
<string name="automated_action_type_custom_intent">Intent مخصص</string>
|
||||
<string name="automated_action_intent_uri_template">قالب عنوان Intent</string>
|
||||
<string name="automated_action_intent_uri_hint">intent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT={RESULT};end</string>
|
||||
<string name="automated_action_intent_uri_required">عنوان Intent مطلوب</string>
|
||||
<string name="automated_action_intent_uri_invalid">عنوان Intent غير صالح</string>
|
||||
<string name="automated_action_list_custom_intent">Intent مخصص: %s</string>
|
||||
<string name="automated_action_list_custom_intent_empty">Intent مخصص</string>
|
||||
|
||||
</resources>
|
||||
@@ -1,4 +1,9 @@
|
||||
<resources>
|
||||
<string name="welcome">Добре дошли!</string>
|
||||
<string name="mode_expert">Разширен</string>
|
||||
<string name="mode_expert_description">Искам да проверя прочетеното съдържание и да го отворя ръчно.</string>
|
||||
<string name="mode_simple">Опростен</string>
|
||||
<string name="mode_simple_description">Искам просто да сканирам баркодове и да ги отварям веднага.</string>
|
||||
<string name="camera_error">Няма достъп до камерата, моля, опитайте отново</string>
|
||||
<string name="scan_code">Сканирайте код</string>
|
||||
<string name="compose_barcode">Създаване на баркод</string>
|
||||
@@ -14,6 +19,7 @@
|
||||
<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="label">Етикет</string>
|
||||
<string name="colors">Преден и заден план</string>
|
||||
<string name="colors_black_on_white">Черно на бяло</string>
|
||||
<string name="colors_white_on_black">Бяло на черно</string>
|
||||
@@ -28,21 +34,23 @@
|
||||
<string name="gtin_add_on">UPC EAN разширение</string>
|
||||
<string name="barcode_version_number">Версия</string>
|
||||
<string name="qr_version_and_modules">%1$s (%2$d модули)</string>
|
||||
<string name="qr_data_mask">Маска за данни</string>
|
||||
<string name="toggle_flash">Превключване на светкавицата</string>
|
||||
<string name="error_flash">Грешка при превключването на светкавицата</string>
|
||||
<string name="share">Сподели</string>
|
||||
<string name="share_file">Share as file</string>
|
||||
<string name="share_file">Споделяне като файл</string>
|
||||
<string name="share_as">Сподели като?</string>
|
||||
<string name="copy_to_clipboard">Копиране в клипборда</string>
|
||||
<string name="copy_as_deeplink">Копиране като дийп линк</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_width_by_height">Размер в пиксели: %1$d×%2$d</string>
|
||||
<string name="margin_size">Марж в пиксели: %1$d</string>
|
||||
<string name="size_no_magnification">Без увеличение</string>
|
||||
<string name="add_quiet_zone">Добавяне на тиха зона</string>
|
||||
<string name="input_content_here">Въведете съдържание тук</string>
|
||||
<string name="unescape">Разширете последователности за извеждане</string>
|
||||
<string name="encode">КОДИРАНЕ</string>
|
||||
@@ -56,7 +64,11 @@
|
||||
<string name="bulk_mode_delay">Забавяне между непрекъснатите сканирания</string>
|
||||
<string name="restrict_format">Ограничаване на формат</string>
|
||||
<string name="remove_restriction">Премахване на ограничението</string>
|
||||
<string name="scan_format">Сканиране %s</string>
|
||||
<string name="find_code">Намерете кода</string>
|
||||
<string name="find_code_with_content">Какво съдържание?</string>
|
||||
<string name="find_code_hint">Термин или регулярен израз</string>
|
||||
<string name="does_not_match_search_term">Не съответства на търсения термин</string>
|
||||
<string name="scan_for">Сканиране %s</string>
|
||||
<string name="quarter_second">Червърт секунда</string>
|
||||
<string name="half_second">Половин секунда</string>
|
||||
<string name="a_second">Една секунда</string>
|
||||
@@ -67,6 +79,17 @@
|
||||
<string name="scan_category">Сканиране</string>
|
||||
<string name="content_category">Съдържание</string>
|
||||
<string name="locale_category">Език</string>
|
||||
<string name="profile_category">Профил</string>
|
||||
<string name="profile">Профил</string>
|
||||
<string name="profile_default">По подразбиране</string>
|
||||
<string name="profile_add">Добавяне на нов профил</string>
|
||||
<string name="profile_enter_name">Въведете име на профила</string>
|
||||
<string name="profile_enter_name_hint">Име</string>
|
||||
<string name="profile_invalid_name">Невалидно име на профила</string>
|
||||
<string name="profile_empty">Все още няма профили</string>
|
||||
<string name="profile_current">Текущ профил</string>
|
||||
<string name="profile_remove_confirm">Премахване на този профил?</string>
|
||||
<string name="current_profile">Профил: %s</string>
|
||||
<string name="custom_locale">Персонализиран език</string>
|
||||
<string name="follow_system_settings">Следване на системните настройки</string>
|
||||
<string name="show_crop_handle">Показване на ограничителя за изрязване</string>
|
||||
@@ -93,8 +116,10 @@
|
||||
<string name="tone_prop_beep2">Общ двоен звуков сигнал</string>
|
||||
<string name="use_history">Хронология на сканирането</string>
|
||||
<string name="use_history_summary">Запазва разпознатите кодове на вашето устройство.</string>
|
||||
<string name="ignore_duplicates">Игнорирайте дубликати</string>
|
||||
<string name="ignore_consecutive_duplicates">Игнориране на дубликатите</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">Не запазва последователни дубликати в хронологията на сканирането.</string>
|
||||
<string name="ignore_any_duplicates">Ignore any duplicates</string>
|
||||
<string name="accept_duplicates">Приемане на дубликати</string>
|
||||
<string name="open_immediately">Отваряй внезапно</string>
|
||||
<string name="open_immediately_summary">Пропуска проверката и внезапно отваря съдържанието. Може да отвори вредно съдържание, ако е активирано.</string>
|
||||
<string name="copy_immediately">Поставяй в клипборда</string>
|
||||
@@ -103,6 +128,8 @@
|
||||
<string name="show_meta_data_summary">Показване на допълнителни данни за сканирания баркод.</string>
|
||||
<string name="show_hex_dump">Показване на шестнадесетичен дъмп</string>
|
||||
<string name="show_hex_dump_summary">Показване на шестнадесетичен дъмп на сканираното съдържание.</string>
|
||||
<string name="show_checksum">Покажи контролна сума</string>
|
||||
<string name="no_checksum">Без контролна сума</string>
|
||||
<string name="show_recreation">Показване на пресъздаден баркод</string>
|
||||
<string name="show_recreation_summary">Създай и покажи баркод отново от прочетеното съдържание, ако е възможно.</string>
|
||||
<string name="close_automatically">Върщане се след Копиране/Споделяне</string>
|
||||
@@ -110,6 +137,30 @@
|
||||
<string name="default_search_engine">Отворяй неизвестни данни в</string>
|
||||
<string name="always_ask">Винаги питай</string>
|
||||
<string name="open_with_url">Отворяне на неизвестни данни с URL</string>
|
||||
|
||||
<string name="automated_actions">Автоматизирани действия</string>
|
||||
<string name="automated_actions_none">Няма автоматизирани действия</string>
|
||||
<string name="automated_actions_empty">Все още няма автоматизирани действия</string>
|
||||
<plurals name="automated_actions_count">
|
||||
<item quantity="one">%d автоматизирано действие</item>
|
||||
<item quantity="other">%d автоматизирани действия</item>
|
||||
</plurals>
|
||||
<string name="automated_action_add">Добавяне на действие</string>
|
||||
<string name="automated_action_edit">Редактиране на действие</string>
|
||||
<string name="automated_action_regex">Регулярен израз</string>
|
||||
<string name="automated_action_regex_hint">напр. ^https?://.*</string>
|
||||
<string name="automated_action_regex_required">Регулярен израз е задължителен</string>
|
||||
<string name="automated_action_regex_invalid">Невалиден регулярен израз</string>
|
||||
<string name="automated_action_type">Тип действие</string>
|
||||
<string name="automated_action_type_intent">Отваряне на URL</string>
|
||||
<string name="automated_action_url_template">Шаблон на URL</string>
|
||||
<string name="automated_action_url_hint">https://example.com/?q={RESULT}</string>
|
||||
<string name="automated_action_url_required">Шаблон на URL е задължителен</string>
|
||||
<string name="automated_action_invalid_url">Невалиден шаблон на URL</string>
|
||||
<string name="automated_action_remove_confirm">Да се премахне ли това действие?</string>
|
||||
<string name="automated_action_list_intent">Отваряне на URL: %s</string>
|
||||
<string name="automated_action_list_intent_empty">Отваряне на URL</string>
|
||||
<string name="automated_action_webview_title">Автоматизирано действие</string>
|
||||
<string name="clear_network_suggestions">Премахнете мрежовите предложения</string>
|
||||
<string name="clear_network_suggestions_summary">Премахване на мрежовите предложения, които преди са били добавени с това приложение.</string>
|
||||
<string name="really_remove_all_networks">Наистина ли искате да премахнете всички мрежи?</string>
|
||||
@@ -133,12 +184,15 @@
|
||||
<string name="bluetooth_connect_fail">Неуспешно свързване с Bluetooth устройство.</string>
|
||||
<string name="bluetooth_send_fail">Неуспешно изпращане до Bluetooth устройство.</string>
|
||||
<string name="bluetooth_send_success">Успешно Bluetooth изпращане.</string>
|
||||
<string name="generation_category">Поколение</string>
|
||||
<string name="brighten_screen">Подсвети екрана</string>
|
||||
<string name="brighten_screen_summary">Задайте яркостта на максимум, за да подобрите сканирането на баркод от екрана.</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="edit_label">Редактиране на етикет</string>
|
||||
<string name="remove_scan">Премахване на сканиране</string>
|
||||
<string name="enter_name">Въведете име, за да опишете сканирането</string>
|
||||
<string name="enter_name_hint">Име на сканирането</string>
|
||||
@@ -166,6 +220,8 @@
|
||||
<string name="otpauth_add">Добави 2FA</string>
|
||||
<string name="search_web">Търси в мрежата</string>
|
||||
<string name="search_scan">Търсещо сканиране</string>
|
||||
<string name="add_to_history">Добави към историята</string>
|
||||
<string name="added_to_history">Добавено към историята</string>
|
||||
<string name="export_to_file">Извеждане в файл</string>
|
||||
<string name="export_as">Извеждане като?</string>
|
||||
<string name="export_csv_comma">CSV файл със запетаи</string>
|
||||
@@ -180,6 +236,7 @@
|
||||
<string name="pick_code_to_scan">Изберете код за сканиране</string>
|
||||
<string name="shortcut_decode">Сканирай баркод</string>
|
||||
<string name="shortcut_encode">Създай баркод</string>
|
||||
<string name="shortcut_history">История</string>
|
||||
<string name="shortcut_preferences">Настройки</string>
|
||||
<string name="background_request_failed">Неуспешна заявка в фонов режим</string>
|
||||
<string name="entry_type">Тип</string>
|
||||
@@ -194,4 +251,18 @@
|
||||
<string name="wifi_phase2">Фаза 2 метод</string>
|
||||
<string name="network_suggestions">Предложения за интернет мрежа</string>
|
||||
<string name="remove_suggestion">Премахване на предложението</string>
|
||||
<string name="email_to">Получател</string>
|
||||
<string name="email_subject">Тема</string>
|
||||
<string name="email_body">Съдържание</string>
|
||||
<string name="scheme">Схема</string>
|
||||
<string name="host">Домакин</string>
|
||||
<string name="query">Заявка</string>
|
||||
<string name="automated_action_type_custom_intent">Персонализиран Intent</string>
|
||||
<string name="automated_action_intent_uri_template">Шаблон за Intent URI</string>
|
||||
<string name="automated_action_intent_uri_hint">intent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT={RESULT};end</string>
|
||||
<string name="automated_action_intent_uri_required">Intent URI е задължителен</string>
|
||||
<string name="automated_action_intent_uri_invalid">Невалиден Intent URI</string>
|
||||
<string name="automated_action_list_custom_intent">Персонализиран Intent: %s</string>
|
||||
<string name="automated_action_list_custom_intent_empty">Персонализиран Intent</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<resources>
|
||||
<string name="welcome">স্বাগতম!</string>
|
||||
<string name="mode_expert">উন্নত</string>
|
||||
<string name="mode_expert_description">আমি পড়া বিষয়বস্তু যাচাই করতে চাই এবং এটি নিজে খুলতে চাই।</string>
|
||||
<string name="mode_simple">সহজ</string>
|
||||
<string name="mode_simple_description">আমি কেবল বারকোড স্ক্যান করতে চাই এবং তা সঙ্গে সঙ্গে খুলতে চাই।</string>
|
||||
<string name="camera_error">ক্যামেরা পাওয়া যাচ্ছে না, পরে চেষ্টা করো</string>
|
||||
<string name="scan_code">সংকেতের পাঠোদ্ধার</string>
|
||||
<string name="compose_barcode">বারকোড বানাও</string>
|
||||
@@ -14,37 +19,40 @@
|
||||
<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="colors">Fore and background</string>
|
||||
<string name="colors_black_on_white">Black on white</string>
|
||||
<string name="colors_white_on_black">White on black</string>
|
||||
<string name="colors_black_on_transparent">Black on transparent</string>
|
||||
<string name="colors_white_on_transparent">White on transparent</string>
|
||||
<string name="sequence_size">Sequence size</string>
|
||||
<string name="sequence_index">Sequence index</string>
|
||||
<string name="sequence_id">Sequence ID</string>
|
||||
<string name="label">লেবেল</string>
|
||||
<string name="colors">পূর্ববর্তী এবং সৃজনাধীন</string>
|
||||
<string name="colors_black_on_white">সাদা উপরে কালো</string>
|
||||
<string name="colors_white_on_black">কালো উপর সাদা</string>
|
||||
<string name="colors_black_on_transparent">সাদা সহজ পারদার উপর</string>
|
||||
<string name="colors_white_on_transparent">পারদার উপর সাদা</string>
|
||||
<string name="sequence_size">ধারার আকার</string>
|
||||
<string name="sequence_index">ক্রমিক সূচী</string>
|
||||
<string name="sequence_id">ক্রম আইডি</string>
|
||||
<string name="gtin_issue_number">সমস্যা ক্রম</string>
|
||||
<string name="gtin_country">তৈরির সম্ভাব্য দেশ</string>
|
||||
<string name="gtin_price">প্রস্তসবিত দাম</string>
|
||||
<string name="gtin_add_on">ইউপিসি ইএএন বর্ধিত</string>
|
||||
<string name="barcode_version_number">Version</string>
|
||||
<string name="barcode_version_number">সংস্করণ</string>
|
||||
<string name="qr_version_and_modules">%1$s (%2$d modules)</string>
|
||||
<string name="qr_data_mask">ডেটা মাস্ক</string>
|
||||
<string name="toggle_flash">আলো</string>
|
||||
<string name="error_flash">Error toggling flash</string>
|
||||
<string name="error_flash">ফ্ল্যাশ টগল করার সময় ত্রুটি</string>
|
||||
<string name="share">ভাগ করো</string>
|
||||
<string name="share_file">Share as file</string>
|
||||
<string name="share_file">ফাইল হিসেবে ভাগাভাগি</string>
|
||||
<string name="share_as">ভাগ করার মাধ্যম</string>
|
||||
<string name="copy_to_clipboard">ক্লিপবোর্ডে অনুলিপি</string>
|
||||
<string name="copy_as_deeplink">ডিপলিংক হিসেবে কপি করুন</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_width_by_height">পিক্সেলে আকা:%1$d×%2$d</string>
|
||||
<string name="margin_size">Margin in pixels: %1$d</string>
|
||||
<string name="size_no_magnification">কোনো বৃদ্ধি নেই</string>
|
||||
<string name="add_quiet_zone">শান্তি জোন যোগ করুন</string>
|
||||
<string name="input_content_here">এখানে তথ্য দাও</string>
|
||||
<string name="unescape">Expand escape sequences</string>
|
||||
<string name="unescape">এস্কেপ সিকোয়েন্স প্রসারিত করুন</string>
|
||||
<string name="encode">সংকেতকরণ</string>
|
||||
<string name="error_no_content">তথ্য নেই</string>
|
||||
<string name="error_encoding_barcode">বারকোড বানানো যায়নি</string>
|
||||
@@ -53,25 +61,40 @@
|
||||
<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="bulk_mode_delay">একটি অবিরত স্ক্যান মধ্যে বিলম্ব</string>
|
||||
<string name="restrict_format">বিনিয়োগ বাধাগ্রস্ত ফরম্যাট</string>
|
||||
<string name="remove_restriction">সীমাবদ্ধতা অপসারণ করুন</string>
|
||||
<string name="find_code">কোড খুঁজুন</string>
|
||||
<string name="find_code_with_content"> কী বিষয়টি? </string>
|
||||
<string name="find_code_hint">শব্দ বা রেগুলার এক্সপ্রেশন</string>
|
||||
<string name="does_not_match_search_term">অনুসন্ধান শর্তের সাথে মিলে না</string>
|
||||
<string name="scan_for">স্ক্যান %s</string>
|
||||
<string name="quarter_second">এক চতুর্থ সেকেন্ড</string>
|
||||
<string name="half_second">আধা সেকেন্ড</string>
|
||||
<string name="a_second">এক সেকেন্ড</string>
|
||||
<string name="two_seconds">দুই সেকেন্ড</string>
|
||||
<string name="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="profile_category">প্রোফাইল</string>
|
||||
<string name="profile">প্রোফাইল</string>
|
||||
<string name="profile_default">ডিফল্ট</string>
|
||||
<string name="profile_add">নতুন প্রোফাইল যোগ করো</string>
|
||||
<string name="profile_enter_name">প্রোফাইলের নাম লেখো</string>
|
||||
<string name="profile_enter_name_hint">নাম</string>
|
||||
<string name="profile_invalid_name">প্রোফাইলের নাম অবৈধ</string>
|
||||
<string name="profile_empty">এখনও কোনো প্রোফাইল নেই</string>
|
||||
<string name="profile_current">বর্তমান প্রোফাইল</string>
|
||||
<string name="profile_remove_confirm">এই প্রোফাইলটি সরাবেন?</string>
|
||||
<string name="current_profile">প্রোফাইল: %s</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="barcode_formats">বারকোড বিন্যাস</string>
|
||||
<string name="zoom_by_swiping">ওপরে/নিচে টানো বড়ো করার জন্য</string>
|
||||
<string name="zoom_by_swiping_summary">ক্যামেরার ছোটো-বড় নিয়ন্ত্রণের জন্য উপরে-নিচে টানো।</string>
|
||||
<string name="auto_rotate">খাড়া ১ডি বারকোড সনাক্ত করো</string>
|
||||
@@ -82,19 +105,21 @@
|
||||
<string name="show_toast_in_bulk_mode_summary">ধারাবাহিকভাবে তথ্য দেখানোর সময় মুহূর্তের জন্য তথ্য দেখাও।</string>
|
||||
<string name="vibrate">চিনলে কাঁপো</string>
|
||||
<string name="vibrate_summary">এটা সক্ষম রাখলে সংকেত চিনার সাথে সাথে কাঁপবে।</string>
|
||||
<string name="beep">Beep on detection</string>
|
||||
<string name="beep_summary">Enable this if you want your device to beep when a code is recognized.</string>
|
||||
<string name="beep_tone">Beep tone</string>
|
||||
<string name="tone_cdma_confirm">CDMA confirm</string>
|
||||
<string name="tone_sup_confirm">Call supervisory confirm</string>
|
||||
<string name="tone_sup_radio_ack">Call supervisory radio path acknowledgment</string>
|
||||
<string name="tone_prop_ack">Positive acknowledgment</string>
|
||||
<string name="tone_prop_beep">General beep</string>
|
||||
<string name="tone_prop_beep2">General double beep</string>
|
||||
<string name="beep">প্রকোপে বিপ দিন</string>
|
||||
<string name="beep_summary">যদি আপনি চান যে আপনার ডিভাইসটি কোড প্রাপ্ত হলে বিপ শব্দ করে।</string>
|
||||
<string name="beep_tone">বিপ টোন</string>
|
||||
<string name="tone_cdma_confirm">সিডিএমএ নিশ্চিত করুন</string>
|
||||
<string name="tone_sup_confirm">কল সুপারভাইজরী কনফার্ম</string>
|
||||
<string name="tone_sup_radio_ack">কল সুপারভাইজরি রেডিও পাথ স্বীকৃতি</string>
|
||||
<string name="tone_prop_ack">ধন্যবাদ</string>
|
||||
<string name="tone_prop_beep">সাধারণ বিপ</string>
|
||||
<string name="tone_prop_beep2">সাধারণ ডাবল বিপ</string>
|
||||
<string name="use_history">ইতিহাস সংরক্ষণ করো।</string>
|
||||
<string name="use_history_summary">সনাক্তকৃত সংকেত যন্ত্রে সংরক্ষণ করো।</string>
|
||||
<string name="ignore_duplicates">নকল উপেক্ষা করুন</string>
|
||||
<string name="ignore_consecutive_duplicates">দ্বিত্ব উপেক্ষা করো</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">দুইটা ইতিহাস সংরক্ষণ করবে না।</string>
|
||||
<string name="ignore_any_duplicates">Ignore any duplicates</string>
|
||||
<string name="accept_duplicates">ডুপ্লিকেট গ্রহণ করুন</string>
|
||||
<string name="open_immediately">সাথে সাথে খুলো</string>
|
||||
<string name="open_immediately_summary">না দেখে সাথে সাথে খুলো। সক্রিয় থাকলে ক্ষতিকারক তথ্য খুলতে পারে।</string>
|
||||
<string name="copy_immediately">ক্লিপবোর্ডে রাখো</string>
|
||||
@@ -103,20 +128,46 @@
|
||||
<string name="show_meta_data_summary">সনাক্তকৃত বারকোড নিয়ে অতিরিক্ত তথ্য দেখাও।</string>
|
||||
<string name="show_hex_dump">ষড়(hex) সংকেত দেখাও</string>
|
||||
<string name="show_hex_dump_summary">পড়া তথ্যের ষড় সংকেত দেখাও</string>
|
||||
<string name="show_recreation">Show recreated barcode</string>
|
||||
<string name="show_recreation_summary">Recreate and show barcode from read content if possible.</string>
|
||||
<string name="show_checksum">সামগ্রিক চেকসাম দেখান</string>
|
||||
<string name="no_checksum">কোনো চেকসাম নেই</string>
|
||||
<string name="show_recreation">পুনরায় তৈরি বারকোড দেখান</string>
|
||||
<string name="show_recreation_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="default_search_engine">অজানা ডেটা খোলুন</string>
|
||||
<string name="always_ask">সবসময় জিজ্ঞাসা করুন</string>
|
||||
<string name="open_with_url">অজানা ইউআরএল খুলো</string>
|
||||
|
||||
<string name="automated_actions">স্বয়ংক্রিয় ক্রিয়াসমূহ</string>
|
||||
<string name="automated_actions_none">কোনো স্বয়ংক্রিয় ক্রিয়া নেই</string>
|
||||
<string name="automated_actions_empty">এখনো কোনো স্বয়ংক্রিয় ক্রিয়া নেই</string>
|
||||
<plurals name="automated_actions_count">
|
||||
<item quantity="one">%dটি স্বয়ংক্রিয় ক্রিয়া</item>
|
||||
<item quantity="other">%dটি স্বয়ংক্রিয় ক্রিয়া</item>
|
||||
</plurals>
|
||||
<string name="automated_action_add">ক্রিয়া যোগ করুন</string>
|
||||
<string name="automated_action_edit">ক্রিয়া সম্পাদনা</string>
|
||||
<string name="automated_action_regex">রেগেক্স</string>
|
||||
<string name="automated_action_regex_hint">উদাহরণ ^https?://.*</string>
|
||||
<string name="automated_action_regex_required">রেগেক্স প্রয়োজন</string>
|
||||
<string name="automated_action_regex_invalid">অবৈধ রেগেক্স</string>
|
||||
<string name="automated_action_type">ক্রিয়ার ধরন</string>
|
||||
<string name="automated_action_type_intent">URL খুলুন</string>
|
||||
<string name="automated_action_url_template">URL টেমপ্লেট</string>
|
||||
<string name="automated_action_url_hint">https://example.com/?q={RESULT}</string>
|
||||
<string name="automated_action_url_required">URL টেমপ্লেট প্রয়োজন</string>
|
||||
<string name="automated_action_invalid_url">অবৈধ URL টেমপ্লেট</string>
|
||||
<string name="automated_action_remove_confirm">এই ক্রিয়াটি সরাবেন?</string>
|
||||
<string name="automated_action_list_intent">URL খুলুন: %s</string>
|
||||
<string name="automated_action_list_intent_empty">URL খুলুন</string>
|
||||
<string name="automated_action_webview_title">স্বয়ংক্রিয় ক্রিয়া</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="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">পুনরায় স্ক্যান পাঠান</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>
|
||||
@@ -124,21 +175,24 @@
|
||||
<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="send_type_external_browser">বাহ্যিক ব্রাউজারে খোলুন</string>
|
||||
<string name="test_url">পরীক্ষার জন্য ইউআরএল</string>
|
||||
<string name="send_scan_bluetooth">Forward scans with Bluetooth</string>
|
||||
<string name="send_scan_bluetooth">ব্লুটুথ দিয়ে স্ক্যান ফরোয়ার্ড করুন</string>
|
||||
<string name="send_scan_bluetooth_summary">Enable to forwards scans to a given Bluetooth host.</string>
|
||||
<string name="send_scan_bluetooth_host">Host</string>
|
||||
<string name="no_bluetooth_hosts_paired">No Hosts Paired</string>
|
||||
<string name="send_scan_bluetooth_host">হোস্ট</string>
|
||||
<string name="no_bluetooth_hosts_paired">কোন হোস্ট পেয়ার করা হয়নি</string>
|
||||
<string name="bluetooth_connect_fail">Could not connect to bluetooth device.</string>
|
||||
<string name="bluetooth_send_fail">Could not send to bluetooth device.</string>
|
||||
<string name="bluetooth_send_success">Successful bluetooth send.</string>
|
||||
<string name="generation_category">প্রজন্ম</string>
|
||||
<string name="brighten_screen">স্ক্রিন উজ্জ্বল করুন</string>
|
||||
<string name="brighten_screen_summary">স্ক্রিন থেকে বারকোড স্ক্যান করার জন্য উজ্জ্বলতা সর্বোচ্চ করুন।</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="edit_label">চিহ্ন সম্পাদনা করো</string>
|
||||
<string name="remove_scan">পাঠোদ্ধার মুছো</string>
|
||||
<string name="enter_name">পাঠোদ্ধার ব্যাখ্যা করতে একটা নাম দাও</string>
|
||||
<string name="enter_name_hint">পাঠোদ্ধারের নাম</string>
|
||||
@@ -166,6 +220,8 @@
|
||||
<string name="otpauth_add">২এফএ যোগ করো</string>
|
||||
<string name="search_web">ওয়েবে খুঁজো</string>
|
||||
<string name="search_scan">আগেরগুলোতে খুঁজো</string>
|
||||
<string name="add_to_history">ইতিহাসে যোগ করুন</string>
|
||||
<string name="added_to_history">ইতিহাসে যোগ করা হয়েছে</string>
|
||||
<string name="export_to_file">নথিতে রপ্তানি করো</string>
|
||||
<string name="export_as">রপ্তানির ধরণ?</string>
|
||||
<string name="export_csv_comma">কমাসহ সিএসভি(.csv) নথি</string>
|
||||
@@ -174,24 +230,39 @@
|
||||
<string name="export_database">এসকিউলাইট তথ্যভিত্তি</string>
|
||||
<string name="saved_in_downloads">ডাউনলোডে সংরক্ষিত</string>
|
||||
<string name="rotate_image_cw">ছবি ঘড়ির দিকে ঘুরাও</string>
|
||||
<string name="toggle_free_rotation">Toggle free rotation</string>
|
||||
<string name="pick_file">Pick a file</string>
|
||||
<string name="toggle_free_rotation">মুক্ত ঘূর্ণন টগল করুন</string>
|
||||
<string name="pick_file">একটি ফাইল চয়ন করুন</string>
|
||||
<string name="pick_image_file">ছবি নাও</string>
|
||||
<string name="pick_code_to_scan">খুঁজার জন্য কোড নাও</string>
|
||||
<string name="shortcut_decode">বারকোড খুজো</string>
|
||||
<string name="shortcut_encode">বারকোড বানাও</string>
|
||||
<string name="shortcut_history">ইতিহাস</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>
|
||||
<string name="network_suggestions">Network suggestions</string>
|
||||
<string name="remove_suggestion">Remove suggestion</string>
|
||||
<string name="entry_type">ধরণ</string>
|
||||
<string name="wifi_network">ওয়াই-ফাই নেটওয়ার্ক</string>
|
||||
<string name="wifi_ssid">নাম</string>
|
||||
<string name="wifi_type">প্রমাণীকরণ ধরণ</string>
|
||||
<string name="wifi_password">গুরুত্বপূর্ণ</string>
|
||||
<string name="wifi_hidden">লুকানো নেটওয়ার্ক</string>
|
||||
<string name="wifi_eap">EAP পদ্ধতি</string>
|
||||
<string name="wifi_anonymous_identity">অজ্ঞাত পরিচিতি</string>
|
||||
<string name="wifi_identity">পরিচয়</string>
|
||||
<string name="wifi_phase2">ধাপ ২ পদ্ধতি</string>
|
||||
<string name="network_suggestions">নেটওয়ার্ক সুপারিশ</string>
|
||||
<string name="remove_suggestion">প্রস্তাব সরান</string>
|
||||
<string name="email_to">প্রাপক</string>
|
||||
<string name="email_subject">বিষয়</string>
|
||||
<string name="email_body">বার্তা</string>
|
||||
<string name="scheme">ব্যবস্থা</string>
|
||||
<string name="host">হোস্ট</string>
|
||||
<string name="query">অনুসন্ধান</string>
|
||||
<string name="automated_action_type_custom_intent">কাস্টম Intent</string>
|
||||
<string name="automated_action_intent_uri_template">Intent URI টেমপ্লেট</string>
|
||||
<string name="automated_action_intent_uri_hint">intent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT={RESULT};end</string>
|
||||
<string name="automated_action_intent_uri_required">Intent URI প্রয়োজন</string>
|
||||
<string name="automated_action_intent_uri_invalid">অবৈধ Intent URI</string>
|
||||
<string name="automated_action_list_custom_intent">কাস্টম Intent: %s</string>
|
||||
<string name="automated_action_list_custom_intent_empty">কাস্টম Intent</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="welcome">Vítejte!</string>
|
||||
<string name="mode_expert">Pokročilý</string>
|
||||
<string name="mode_expert_description">Chci zkontrolovat načtený obsah a otevřít jej ručně.</string>
|
||||
<string name="mode_simple">Jednoduchý</string>
|
||||
<string name="mode_simple_description">Chci jen skenovat čárové kódy a okamžitě je otevírat.</string>
|
||||
<string name="camera_error">Nepodařilo se otevřít fotoaparát, prosím zkuste to znovu</string>
|
||||
<string name="scan_code">Naskenovat kód</string>
|
||||
<string name="compose_barcode">Vytvořit čárový kód</string>
|
||||
@@ -12,10 +18,11 @@
|
||||
<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="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="label">Štítek</string>
|
||||
<string name="colors">Popředí a pozadí</string>
|
||||
<string name="colors_black_on_white">Černá na bílé</string>
|
||||
<string name="colors_white_on_black">Bílá na černé</string>
|
||||
@@ -30,21 +37,23 @@
|
||||
<string name="gtin_add_on">rozšíření UPC EAN</string>
|
||||
<string name="barcode_version_number">Verze</string>
|
||||
<string name="qr_version_and_modules">%1$s (%2$d modulů)</string>
|
||||
<string name="qr_data_mask">Maska dat</string>
|
||||
<string name="toggle_flash">Přepnout blesk</string>
|
||||
<string name="error_flash">Zapnutí/vypnutí blesku selhalo</string>
|
||||
<string name="share">Sdílet</string>
|
||||
<string name="share_file">Sdílet jako soubor</string>
|
||||
<string name="share_as">Sdílet jako</string>
|
||||
<string name="share_as">Sdílet jako?</string>
|
||||
<string name="copy_to_clipboard">Zkopírovat do schránky</string>
|
||||
<string name="copy_as_deeplink">Kopírovat jako deeplink</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 toto uměla otevřít</string>
|
||||
<string name="pick_search_engine">Zvolte vyhledávač</string>
|
||||
<string name="format">Formát</string>
|
||||
<string name="size_width_by_height">Velikost v pixelech: %1$d×%2$d</string>
|
||||
<string name="margin_size">Okraje v pixelech: %1$d</string>
|
||||
<string name="size_no_magnification">Žádné zvětšení</string>
|
||||
<string name="add_quiet_zone">Přidat tichou zónu</string>
|
||||
<string name="input_content_here">Sem vložte obsah</string>
|
||||
<string name="unescape">Rozšířit escape sekvence</string>
|
||||
<string name="encode">ZAKÓDOVAT</string>
|
||||
@@ -58,7 +67,11 @@
|
||||
<string name="bulk_mode_delay">Prodleva mezi skeny</string>
|
||||
<string name="restrict_format">Omezit na formát</string>
|
||||
<string name="remove_restriction">Zrušit omezení formátu</string>
|
||||
<string name="scan_format">Naskenovat %s</string>
|
||||
<string name="find_code">Najít kód</string>
|
||||
<string name="find_code_with_content">Co obsah?</string>
|
||||
<string name="find_code_hint">Termín nebo regulární výraz</string>
|
||||
<string name="does_not_match_search_term">Neodpovídá vyhledávacímu termínu</string>
|
||||
<string name="scan_for">Naskenovat %s</string>
|
||||
<string name="quarter_second">Čtvrt sekundy</string>
|
||||
<string name="half_second">Půl sekundy</string>
|
||||
<string name="a_second">Jedna sekunda</string>
|
||||
@@ -69,6 +82,17 @@
|
||||
<string name="scan_category">Skenování</string>
|
||||
<string name="content_category">Obsah</string>
|
||||
<string name="locale_category">Jazyk</string>
|
||||
<string name="profile_category">Profil</string>
|
||||
<string name="profile">Profil</string>
|
||||
<string name="profile_default">Výchozí</string>
|
||||
<string name="profile_add">Přidat nový profil</string>
|
||||
<string name="profile_enter_name">Zadejte název profilu</string>
|
||||
<string name="profile_enter_name_hint">Název</string>
|
||||
<string name="profile_invalid_name">Neplatný název profilu</string>
|
||||
<string name="profile_empty">Zatím žádné profily</string>
|
||||
<string name="profile_current">Aktuální profil</string>
|
||||
<string name="profile_remove_confirm">Odebrat tento profil?</string>
|
||||
<string name="current_profile">Profil: %s</string>
|
||||
<string name="custom_locale">Nastavit jazyk</string>
|
||||
<string name="follow_system_settings">Podle systému</string>
|
||||
<string name="show_crop_handle">Zobrazovat hrany výřezu</string>
|
||||
@@ -88,15 +112,17 @@
|
||||
<string name="beep_summary">Povolte, pokud chcete pípnutí při rozpoznání kódu.</string>
|
||||
<string name="beep_tone">Tón pípnutí</string>
|
||||
<string name="tone_cdma_confirm">Potvrzení CDMA</string>
|
||||
<string name="tone_sup_confirm">Call supervisory confirm</string>
|
||||
<string name="tone_sup_radio_ack">Call supervisory radio path acknowledgment</string>
|
||||
<string name="tone_sup_confirm">Potvrzení dohledu volání</string>
|
||||
<string name="tone_sup_radio_ack">Hovor na dohledové rádiové cestě potvrzení</string>
|
||||
<string name="tone_prop_ack">Kladné potvrzení příjmu</string>
|
||||
<string name="tone_prop_beep">Obecné pípnutí</string>
|
||||
<string name="tone_prop_beep2">Dvojité obecné pípnutí</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_duplicates">Ignorovat duplicity</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="ignore_any_duplicates">Ignore any duplicates</string>
|
||||
<string name="accept_duplicates">Přijmout duplicity</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>
|
||||
@@ -105,6 +131,8 @@
|
||||
<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="show_checksum">Zobrazit kontrolní součet</string>
|
||||
<string name="no_checksum">Žádný kontrolní součet</string>
|
||||
<string name="show_recreation">Zobrazit zrekonstruovaný kód</string>
|
||||
<string name="show_recreation_summary">Zrekonstruovat a zobrazit čárový kód z přečteného obrazu, pokud je to možné.</string>
|
||||
<string name="close_automatically">Vrátit se po zkopírování/sdílení</string>
|
||||
@@ -112,6 +140,32 @@
|
||||
<string name="default_search_engine">Nerozpoznaná data otevřít v</string>
|
||||
<string name="always_ask">Vždy se zeptat</string>
|
||||
<string name="open_with_url">Otevřít nerozpoznaná data pomocí URL</string>
|
||||
|
||||
<string name="automated_actions">Automatizované akce</string>
|
||||
<string name="automated_actions_none">Žádné automatizované akce</string>
|
||||
<string name="automated_actions_empty">Zatím žádné automatizované akce</string>
|
||||
<plurals name="automated_actions_count">
|
||||
<item quantity="one">%d automatizovaná akce</item>
|
||||
<item quantity="few">%d automatizované akce</item>
|
||||
<item quantity="many">%d automatizovaných akcí</item>
|
||||
<item quantity="other">%d automatizované akce</item>
|
||||
</plurals>
|
||||
<string name="automated_action_add">Přidat akci</string>
|
||||
<string name="automated_action_edit">Upravit akci</string>
|
||||
<string name="automated_action_regex">Regulární výraz</string>
|
||||
<string name="automated_action_regex_hint">např. ^https?://.*</string>
|
||||
<string name="automated_action_regex_required">Regulární výraz je povinný</string>
|
||||
<string name="automated_action_regex_invalid">Neplatný regulární výraz</string>
|
||||
<string name="automated_action_type">Typ akce</string>
|
||||
<string name="automated_action_type_intent">Otevřít URL</string>
|
||||
<string name="automated_action_url_template">Šablona URL</string>
|
||||
<string name="automated_action_url_hint">https://example.com/?q={RESULT}</string>
|
||||
<string name="automated_action_url_required">Šablona URL je povinná</string>
|
||||
<string name="automated_action_invalid_url">Neplatná šablona URL</string>
|
||||
<string name="automated_action_remove_confirm">Odstranit tuto akci?</string>
|
||||
<string name="automated_action_list_intent">Otevřít URL: %s</string>
|
||||
<string name="automated_action_list_intent_empty">Otevřít URL</string>
|
||||
<string name="automated_action_webview_title">Automatizovaná akce</string>
|
||||
<string name="clear_network_suggestions">Smazat navrhované sítě</string>
|
||||
<string name="clear_network_suggestions_summary">Smazat všechny sítě, které byly navrženy touto aplikací.</string>
|
||||
<string name="really_remove_all_networks">Opravdu smazat všechny sítě?</string>
|
||||
@@ -119,11 +173,11 @@
|
||||
<string name="clear_network_suggestions_nothing_to_remove">Nic ke smazání</string>
|
||||
<string name="send_category">Předávání</string>
|
||||
<string name="send_scan_active">Předávat skeny</string>
|
||||
<string name="send_scan_active_summary">Zapnout předávání skenů na danou URL</string>
|
||||
<string name="send_scan_active_summary">Zapnout předávání skenů na danou URL.</string>
|
||||
<string name="send_scan_url">Posílat každý sken na URL</string>
|
||||
<string name="send_scan_type">Typ požadavku 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_get_query_string">GET s kompletním řetězcem dotazu</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">Otevřít v prohlížeči</string>
|
||||
@@ -135,12 +189,15 @@
|
||||
<string name="bluetooth_connect_fail">Nelze se připojit k zařízení Bluetooth.</string>
|
||||
<string name="bluetooth_send_fail">Nelze odesílat na zařízení Bluetooth.</string>
|
||||
<string name="bluetooth_send_success">Odesílání přes Bluetooth se zdařilo.</string>
|
||||
<string name="generation_category">Generace</string>
|
||||
<string name="brighten_screen">Zvýšit jas obrazovky</string>
|
||||
<string name="brighten_screen_summary">Nastavte jas na maximum pro zlepšení skenování čárového kódu ze obrazovky.</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="edit_label">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>
|
||||
@@ -148,7 +205,7 @@
|
||||
<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="save_as_file_name">Uložit 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>
|
||||
@@ -168,8 +225,10 @@
|
||||
<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="add_to_history">Přidat do historie</string>
|
||||
<string name="added_to_history">Přidáno do historie</string>
|
||||
<string name="export_to_file">Exportovat do souboru</string>
|
||||
<string name="export_as">Exportovat jako</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>
|
||||
@@ -182,6 +241,7 @@
|
||||
<string name="pick_code_to_scan">Vyberte 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_history">Historie</string>
|
||||
<string name="shortcut_preferences">Nastavení</string>
|
||||
<string name="background_request_failed">Požadavek na pozadí selhal</string>
|
||||
<string name="entry_type">Typ</string>
|
||||
@@ -196,4 +256,18 @@
|
||||
<string name="wifi_phase2">Ověření Phase 2</string>
|
||||
<string name="network_suggestions">Návrhy sítí</string>
|
||||
<string name="remove_suggestion">Odebrat návrh</string>
|
||||
<string name="email_to">Příjemce</string>
|
||||
<string name="email_subject">Předmět</string>
|
||||
<string name="email_body">Tělo zprávy</string>
|
||||
<string name="scheme">Schéma</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="query">Dotaz</string>
|
||||
<string name="automated_action_type_custom_intent">Vlastní intent</string>
|
||||
<string name="automated_action_intent_uri_template">Šablona URI intentu</string>
|
||||
<string name="automated_action_intent_uri_hint">intent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT={RESULT};end</string>
|
||||
<string name="automated_action_intent_uri_required">URI intentu je povinné</string>
|
||||
<string name="automated_action_intent_uri_invalid">Neplatné URI intentu</string>
|
||||
<string name="automated_action_list_custom_intent">Vlastní intent: %s</string>
|
||||
<string name="automated_action_list_custom_intent_empty">Vlastní intent</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<resources>
|
||||
<string name="welcome">Velkommen!</string>
|
||||
<string name="mode_expert">Avanceret</string>
|
||||
<string name="mode_expert_description">Jeg vil kontrollere det læste indhold og åbne det manuelt.</string>
|
||||
<string name="mode_simple">Simpel</string>
|
||||
<string name="mode_simple_description">Jeg vil bare scanne stregkoder og få dem åbnet med det samme.</string>
|
||||
<string name="camera_error">Kan ikke få adgang til kamera, prøv igen</string>
|
||||
<string name="scan_code">Scan kode</string>
|
||||
<string name="compose_barcode">Opret stregkode</string>
|
||||
@@ -14,6 +19,7 @@
|
||||
<string name="error_correction_level_m" formatted="false">Medium (~15% korrigering)</string>
|
||||
<string name="error_correction_level_q" formatted="false">Kvartil (~25% korrigering)</string>
|
||||
<string name="error_correction_level_h" formatted="false">Høj (~30% korrigering)</string>
|
||||
<string name="label">Mærke</string>
|
||||
<string name="colors">For- og baggrund</string>
|
||||
<string name="colors_black_on_white">Sort på hvidt</string>
|
||||
<string name="colors_white_on_black">Hvid på sort</string>
|
||||
@@ -28,23 +34,25 @@
|
||||
<string name="gtin_add_on">UPC EAN udvidelse</string>
|
||||
<string name="barcode_version_number">Version</string>
|
||||
<string name="qr_version_and_modules">%1$s (%2$d moduler)</string>
|
||||
<string name="qr_data_mask">Data mask</string>
|
||||
<string name="toggle_flash">Slå blitz til/fra</string>
|
||||
<string name="error_flash">Der opstod en fejl ved at slå blitz til/fra</string>
|
||||
<string name="share">Del</string>
|
||||
<string name="share_file">Share as file</string>
|
||||
<string name="share_file">Del som fil</string>
|
||||
<string name="share_as">Del som?</string>
|
||||
<string name="copy_to_clipboard">Kopier til udklipsholder</string>
|
||||
<string name="copy_as_deeplink">Kopiér som deeplink</string>
|
||||
<string name="copied_to_clipboard">Kopieret til udklipsholder</string>
|
||||
<string name="copy_password">Kopier adgangskode til udklipsholderen</string>
|
||||
<string name="copied_password_to_clipboard">Adgangskode kopieret til udklipsholderen</string>
|
||||
<string name="open_url">Åbn URL</string>
|
||||
<string name="cannot_resolve_action">Inge applikation kan åbne det</string>
|
||||
<string name="pick_search_engine">Vælg en søgemaskine</string>
|
||||
<string name="format">Format</string>
|
||||
<string name="size_width_by_height">Størrelse i pixels: %1$d×%2$d</string>
|
||||
<string name="margin_size">Margin in pixels: %1$d</string>
|
||||
<string name="size_no_magnification">Ingen forstørrelse</string>
|
||||
<string name="add_quiet_zone">Tilføj stille zone</string>
|
||||
<string name="input_content_here">Indtast indhold her</string>
|
||||
<string name="unescape">Expand escape sequences</string>
|
||||
<string name="unescape">Udvid escape-sekvenser</string>
|
||||
<string name="encode">INDKOD</string>
|
||||
<string name="error_no_content">Manglende indhold</string>
|
||||
<string name="error_encoding_barcode">Stregkode kan ikke genereres</string>
|
||||
@@ -56,7 +64,11 @@
|
||||
<string name="bulk_mode_delay">Forsinkelse mellem kontinuerlige scanninger</string>
|
||||
<string name="restrict_format">Begræns format</string>
|
||||
<string name="remove_restriction">Fjern begrænsningen</string>
|
||||
<string name="scan_format">Scan %s</string>
|
||||
<string name="find_code">Find kode</string>
|
||||
<string name="find_code_with_content">Hvad indhold?</string>
|
||||
<string name="find_code_hint">Term eller regulært udtryk</string>
|
||||
<string name="does_not_match_search_term">Stemmer ikke overens med søgeterm</string>
|
||||
<string name="scan_for">Scan %s</string>
|
||||
<string name="quarter_second">Et kvart sekund</string>
|
||||
<string name="half_second">Et halvt sekund</string>
|
||||
<string name="a_second">Et sekund</string>
|
||||
@@ -67,6 +79,17 @@
|
||||
<string name="scan_category">Scan</string>
|
||||
<string name="content_category">Indhold</string>
|
||||
<string name="locale_category">Sprog</string>
|
||||
<string name="profile_category">Profil</string>
|
||||
<string name="profile">Profil</string>
|
||||
<string name="profile_default">Standard</string>
|
||||
<string name="profile_add">Tilføj ny profil</string>
|
||||
<string name="profile_enter_name">Indtast profilnavn</string>
|
||||
<string name="profile_enter_name_hint">Navn</string>
|
||||
<string name="profile_invalid_name">Ugyldigt profilnavn</string>
|
||||
<string name="profile_empty">Ingen profiler endnu</string>
|
||||
<string name="profile_current">Nuværende profil</string>
|
||||
<string name="profile_remove_confirm">Fjern denne profil?</string>
|
||||
<string name="current_profile">Profil: %s</string>
|
||||
<string name="custom_locale">Brugerdefineret sprog</string>
|
||||
<string name="follow_system_settings">Følg systemindstillingerne</string>
|
||||
<string name="show_crop_handle">Vis beskæringsbegrænser</string>
|
||||
@@ -86,15 +109,17 @@
|
||||
<string name="beep_summary">Aktiver dette, hvis du vil have din enhed til at bippe, når en kode genkendes.</string>
|
||||
<string name="beep_tone">Bip tone</string>
|
||||
<string name="tone_cdma_confirm">CDMA bekræftelse</string>
|
||||
<string name="tone_sup_confirm">Call supervisory confirm</string>
|
||||
<string name="tone_sup_radio_ack">Call supervisory radio path acknowledgment</string>
|
||||
<string name="tone_sup_confirm">Opkald tilsyn bekræft</string>
|
||||
<string name="tone_sup_radio_ack">Opkald tilsynsradio sti anerkendelse</string>
|
||||
<string name="tone_prop_ack">Positiv anerkendelse</string>
|
||||
<string name="tone_prop_beep">Generelt bip</string>
|
||||
<string name="tone_prop_beep2">Generelt dobbelt bip</string>
|
||||
<string name="use_history">Gem scanningshistorik</string>
|
||||
<string name="use_history_summary">Gem genkendte koder på din enhed.</string>
|
||||
<string name="ignore_duplicates">Ignorer duplikater</string>
|
||||
<string name="ignore_consecutive_duplicates">Ignorer dubletter</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">Gem ikke fortløbende dubletter i scanningshistorikken.</string>
|
||||
<string name="ignore_any_duplicates">Ignore any duplicates</string>
|
||||
<string name="accept_duplicates">Accept duplicates</string>
|
||||
<string name="open_immediately">Åbn straks</string>
|
||||
<string name="open_immediately_summary">Spring inspektion over og åbn indholdet med det samme. Kan åbne skadeligt indhold, hvis det er aktiveret.</string>
|
||||
<string name="copy_immediately">Læg i udklipsholderen</string>
|
||||
@@ -103,6 +128,8 @@
|
||||
<string name="show_meta_data_summary">Vis yderligere data om den scannede stregkode.</string>
|
||||
<string name="show_hex_dump">Vis hex-dump</string>
|
||||
<string name="show_hex_dump_summary">Vis et hex-dump af det scannede indhold.</string>
|
||||
<string name="show_checksum">Vis kontrolsum</string>
|
||||
<string name="no_checksum">Ingen kontrolsum</string>
|
||||
<string name="show_recreation">Vis genskabt stregkode</string>
|
||||
<string name="show_recreation_summary">Genskab og vis stregkode fra læst indhold, hvis det er muligt.</string>
|
||||
<string name="close_automatically">Gå tilbage efter Kopier/Del</string>
|
||||
@@ -110,6 +137,30 @@
|
||||
<string name="default_search_engine">Åbn ukendte data i</string>
|
||||
<string name="always_ask">Spørg altid</string>
|
||||
<string name="open_with_url">Åbn ukendte data med URL</string>
|
||||
|
||||
<string name="automated_actions">Automatiserede handlinger</string>
|
||||
<string name="automated_actions_none">Ingen automatiserede handlinger</string>
|
||||
<string name="automated_actions_empty">Ingen automatiserede handlinger endnu</string>
|
||||
<plurals name="automated_actions_count">
|
||||
<item quantity="one">%d automatiseret handling</item>
|
||||
<item quantity="other">%d automatiserede handlinger</item>
|
||||
</plurals>
|
||||
<string name="automated_action_add">Tilføj handling</string>
|
||||
<string name="automated_action_edit">Rediger handling</string>
|
||||
<string name="automated_action_regex">Regex</string>
|
||||
<string name="automated_action_regex_hint">f.eks. ^https?://.*</string>
|
||||
<string name="automated_action_regex_required">Regex påkrævet</string>
|
||||
<string name="automated_action_regex_invalid">Ugyldig regex</string>
|
||||
<string name="automated_action_type">Handlingstype</string>
|
||||
<string name="automated_action_type_intent">Åbn URL</string>
|
||||
<string name="automated_action_url_template">URL-skabelon</string>
|
||||
<string name="automated_action_url_hint">https://example.com/?q={RESULT}</string>
|
||||
<string name="automated_action_url_required">URL-skabelon påkrævet</string>
|
||||
<string name="automated_action_invalid_url">Ugyldig URL-skabelon</string>
|
||||
<string name="automated_action_remove_confirm">Fjern denne handling?</string>
|
||||
<string name="automated_action_list_intent">Åbn URL: %s</string>
|
||||
<string name="automated_action_list_intent_empty">Åbn URL</string>
|
||||
<string name="automated_action_webview_title">Automatiseret handling</string>
|
||||
<string name="clear_network_suggestions">Fjern netværksforslag</string>
|
||||
<string name="clear_network_suggestions_summary">Fjern netværksforslag, der tidligere blev tilføjet med denne app.</string>
|
||||
<string name="really_remove_all_networks">Vil du virkelig fjerne alle netværk?</string>
|
||||
@@ -126,19 +177,22 @@
|
||||
<string name="send_type_post_json">POST application/json</string>
|
||||
<string name="send_type_external_browser">Åbn i ekstern browser</string>
|
||||
<string name="test_url">Test URL</string>
|
||||
<string name="send_scan_bluetooth">Forward scans with Bluetooth</string>
|
||||
<string name="send_scan_bluetooth">Send scanninger med Bluetooth</string>
|
||||
<string name="send_scan_bluetooth_summary">Enable to forwards scans to a given Bluetooth host.</string>
|
||||
<string name="send_scan_bluetooth_host">Host</string>
|
||||
<string name="no_bluetooth_hosts_paired">No Hosts Paired</string>
|
||||
<string name="send_scan_bluetooth_host">Vært</string>
|
||||
<string name="no_bluetooth_hosts_paired">Ingen værter parret</string>
|
||||
<string name="bluetooth_connect_fail">Could not connect to bluetooth device.</string>
|
||||
<string name="bluetooth_send_fail">Could not send to bluetooth device.</string>
|
||||
<string name="bluetooth_send_success">Successful bluetooth send.</string>
|
||||
<string name="generation_category">Generation</string>
|
||||
<string name="brighten_screen">Skærm lysere</string>
|
||||
<string name="brighten_screen_summary">Indstil lysstyrken til maksimum for at forbedre scanning af en stregkode fra skærmen.</string>
|
||||
<string name="really_remove_scan">Vil du virkelig fjerne scanningen?</string>
|
||||
<string name="really_remove_all_scans">Vil du virkelig fjerne alle scanninger?</string>
|
||||
<string name="really_remove_selected_scans">Vil du virkelig fjerne valgte scanninger?</string>
|
||||
<string name="clear_history">Ryd historik</string>
|
||||
<string name="copy_scan">Kopier scanning</string>
|
||||
<string name="edit_scan">Rediger etiket</string>
|
||||
<string name="edit_label">Rediger etiket</string>
|
||||
<string name="remove_scan">Fjern scanning</string>
|
||||
<string name="enter_name">Indtast et navn for at beskrive scanningen</string>
|
||||
<string name="enter_name_hint">Navn på scanningen</string>
|
||||
@@ -166,20 +220,23 @@
|
||||
<string name="otpauth_add">Tilføj 2FA</string>
|
||||
<string name="search_web">Søg på nettet</string>
|
||||
<string name="search_scan">Søg scanning</string>
|
||||
<string name="add_to_history">Tilføj til historik</string>
|
||||
<string name="added_to_history">Tilføjet til historik</string>
|
||||
<string name="export_to_file">Eksporter til fil</string>
|
||||
<string name="export_as">Eksporter som?</string>
|
||||
<string name="export_csv_comma">CSV-fil med kommaer</string>
|
||||
<string name="export_csv_semicolon">CSV-fil med semikoloner</string>
|
||||
<string name="export_json">JSON fil</string>
|
||||
<string name="export_database">SQLite database</string>
|
||||
<string name="export_database">SQLite-database</string>
|
||||
<string name="saved_in_downloads">Gemt i downloads</string>
|
||||
<string name="rotate_image_cw">Roter billedet med uret</string>
|
||||
<string name="toggle_free_rotation">Slå fri rotation til/fra</string>
|
||||
<string name="pick_file">Pick a file</string>
|
||||
<string name="pick_file">Vælg en fil</string>
|
||||
<string name="pick_image_file">Vælg billedfil</string>
|
||||
<string name="pick_code_to_scan">Vælg kode til at scanne</string>
|
||||
<string name="shortcut_decode">Scan en stregkode</string>
|
||||
<string name="shortcut_encode">Opret en stregkode</string>
|
||||
<string name="shortcut_history">Historik</string>
|
||||
<string name="shortcut_preferences">Indstillinger</string>
|
||||
<string name="background_request_failed">Baggrundsanmodning mislykkedes</string>
|
||||
<string name="entry_type">Type</string>
|
||||
@@ -194,4 +251,18 @@
|
||||
<string name="wifi_phase2">Fase 2 metode</string>
|
||||
<string name="network_suggestions">Netværksforslag</string>
|
||||
<string name="remove_suggestion">Fjern forslag</string>
|
||||
<string name="email_to">Modtager</string>
|
||||
<string name="email_subject">Emne</string>
|
||||
<string name="email_body">Brødtekst</string>
|
||||
<string name="scheme">Ordning</string>
|
||||
<string name="host">Vært</string>
|
||||
<string name="query">Forespørgsel</string>
|
||||
<string name="automated_action_type_custom_intent">Brugerdefineret intent</string>
|
||||
<string name="automated_action_intent_uri_template">Intent-URI-skabelon</string>
|
||||
<string name="automated_action_intent_uri_hint">intent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT={RESULT};end</string>
|
||||
<string name="automated_action_intent_uri_required">Intent-URI påkrævet</string>
|
||||
<string name="automated_action_intent_uri_invalid">Ugyldig Intent-URI</string>
|
||||
<string name="automated_action_list_custom_intent">Brugerdefineret intent: %s</string>
|
||||
<string name="automated_action_list_custom_intent_empty">Brugerdefineret intent</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<resources>
|
||||
<string name="welcome">Willkommen!</string>
|
||||
<string name="mode_expert">Erweitert</string>
|
||||
<string name="mode_expert_description">Ich möchte den gelesenen Inhalt überprüfen und manuell öffnen.</string>
|
||||
<string name="mode_simple">Einfach</string>
|
||||
<string name="mode_simple_description">Ich möchte nur Barcodes scannen und sie sofort öffnen.</string>
|
||||
<string name="camera_error">Auf die Kamera kann nicht zugegriffen werden, bitte versuche es nochmal</string>
|
||||
<string name="scan_code">Code scannen</string>
|
||||
<string name="compose_barcode">Barcode erstellen</string>
|
||||
@@ -14,35 +19,38 @@
|
||||
<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="label">Bezeichnung</string>
|
||||
<string name="colors">Vor und Hintergrund</string>
|
||||
<string name="colors_black_on_white">Schwarz auf Weiß</string>
|
||||
<string name="colors_white_on_black">Weiß auf Schwarz</string>
|
||||
<string name="colors_black_on_transparent">Schwarz auf Transparent</string>
|
||||
<string name="colors_white_on_transparent">Weiß auf Transparent</string>
|
||||
<string name="sequence_size">Sequence size</string>
|
||||
<string name="sequence_index">Sequence index</string>
|
||||
<string name="sequence_id">Sequence ID</string>
|
||||
<string name="sequence_size">Sequenzgröße</string>
|
||||
<string name="sequence_index">Sequenzindex</string>
|
||||
<string name="sequence_id">Sequenz-ID</string>
|
||||
<string name="gtin_issue_number">Ausgabe Nummer</string>
|
||||
<string name="gtin_country">Mögliches Ursprungsland</string>
|
||||
<string name="gtin_price">Preisvorschlag</string>
|
||||
<string name="gtin_add_on">UPC EAN Erweiterung</string>
|
||||
<string name="barcode_version_number">Version</string>
|
||||
<string name="qr_version_and_modules">%1$s (%2$d Module)</string>
|
||||
<string name="qr_data_mask">Maske</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_file">Als Datei teilen</string>
|
||||
<string name="share_as">Teilen als?</string>
|
||||
<string name="copy_to_clipboard">In die Zwischenablage kopieren</string>
|
||||
<string name="copy_as_deeplink">Als Deeplink 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>
|
||||
<string name="pick_search_engine">Suchmaschine auswählen</string>
|
||||
<string name="format">Format</string>
|
||||
<string name="size_width_by_height">Größe in Pixel: %1$d×%2$d</string>
|
||||
<string name="margin_size">Randgröße in Pixel: %1$d</string>
|
||||
<string name="size_no_magnification">Keine Vergrößerung</string>
|
||||
<string name="add_quiet_zone">Ruhezone hinzufügen</string>
|
||||
<string name="input_content_here">Gib den Inhalt hier ein</string>
|
||||
<string name="unescape">Escape-Sequenzen interpretieren</string>
|
||||
<string name="encode">KODIEREN</string>
|
||||
@@ -56,7 +64,11 @@
|
||||
<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="find_code">Barcode finden</string>
|
||||
<string name="find_code_with_content">Mit welchem Inhalt?</string>
|
||||
<string name="find_code_hint">Begriff oder regulärer Ausdruck</string>
|
||||
<string name="does_not_match_search_term">Entspricht nicht dem Suchbegriff</string>
|
||||
<string name="scan_for">%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>
|
||||
@@ -67,6 +79,17 @@
|
||||
<string name="scan_category">Scannen</string>
|
||||
<string name="content_category">Inhalt</string>
|
||||
<string name="locale_category">Sprache</string>
|
||||
<string name="profile_category">Profil</string>
|
||||
<string name="profile">Profil</string>
|
||||
<string name="profile_default">Standard</string>
|
||||
<string name="profile_add">Neues Profil hinzufügen</string>
|
||||
<string name="profile_enter_name">Profilnamen eingeben</string>
|
||||
<string name="profile_enter_name_hint">Name</string>
|
||||
<string name="profile_invalid_name">Ungültiger Profilname</string>
|
||||
<string name="profile_empty">Noch keine Profile</string>
|
||||
<string name="profile_current">Aktuelles Profil</string>
|
||||
<string name="profile_remove_confirm">Dieses Profil entfernen?</string>
|
||||
<string name="current_profile">Profil: %s</string>
|
||||
<string name="custom_locale">Spracheinstellung</string>
|
||||
<string name="follow_system_settings">Systemeinstellung übernehmen</string>
|
||||
<string name="show_crop_handle">Ausschnitt verkleinern anzeigen</string>
|
||||
@@ -85,16 +108,18 @@
|
||||
<string name="beep">Signalton bei Erkennung</string>
|
||||
<string name="beep_summary">Bei Erkennung eines Codes soll ein Signalton abgespielt werden.</string>
|
||||
<string name="beep_tone">Signalton</string>
|
||||
<string name="tone_cdma_confirm">CDMA confirm</string>
|
||||
<string name="tone_sup_confirm">Call supervisory confirm</string>
|
||||
<string name="tone_sup_radio_ack">Call supervisory radio path acknowledgment</string>
|
||||
<string name="tone_prop_ack">Positive acknowledgment</string>
|
||||
<string name="tone_prop_beep">General beep</string>
|
||||
<string name="tone_prop_beep2">General double beep</string>
|
||||
<string name="tone_cdma_confirm">CDMA bestätigen</string>
|
||||
<string name="tone_sup_confirm">Anrufaufsicht bestätigen</string>
|
||||
<string name="tone_sup_radio_ack">Anrufüberwachungsfunkpfadbestätigung</string>
|
||||
<string name="tone_prop_ack">Positive Bestätigung</string>
|
||||
<string name="tone_prop_beep">Allgemeines Piepen</string>
|
||||
<string name="tone_prop_beep2">Allgemeiner Doppelton</string>
|
||||
<string name="use_history">Codes speichern</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="ignore_duplicates">Ignoriere Duplikate</string>
|
||||
<string name="ignore_consecutive_duplicates">Aufeinanderfolgende Duplikate</string>
|
||||
<string name="ignore_any_duplicates">Jedes Duplicat</string>
|
||||
<string name="accept_duplicates">Akzeptiere Duplikate</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>
|
||||
@@ -103,6 +128,8 @@
|
||||
<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="show_checksum">Prüfsumme anzeigen</string>
|
||||
<string name="no_checksum">Keine Prüfsumme anzeigen</string>
|
||||
<string name="show_recreation">Wiederhergestellten Barcode anzeigen</string>
|
||||
<string name="show_recreation_summary">Barcode aus dem gelesenen Inhalt wiederherstellen und anzeigen, wenn möglich.</string>
|
||||
<string name="close_automatically">Automatisch zurückkehren</string>
|
||||
@@ -110,6 +137,30 @@
|
||||
<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="automated_actions">Automatisierte Aktionen</string>
|
||||
<string name="automated_actions_none">Keine automatisierten Aktionen</string>
|
||||
<string name="automated_actions_empty">Noch keine automatisierten Aktionen</string>
|
||||
<plurals name="automated_actions_count">
|
||||
<item quantity="one">%d automatisierte Aktion</item>
|
||||
<item quantity="other">%d automatisierte Aktionen</item>
|
||||
</plurals>
|
||||
<string name="automated_action_add">Aktion hinzufügen</string>
|
||||
<string name="automated_action_edit">Aktion bearbeiten</string>
|
||||
<string name="automated_action_regex">Regex</string>
|
||||
<string name="automated_action_regex_hint">z.B. ^https?://.*</string>
|
||||
<string name="automated_action_regex_required">Regex erforderlich</string>
|
||||
<string name="automated_action_regex_invalid">Ungültiger Regex</string>
|
||||
<string name="automated_action_type">Aktionstyp</string>
|
||||
<string name="automated_action_type_intent">URL öffnen</string>
|
||||
<string name="automated_action_url_template">URL-Vorlage</string>
|
||||
<string name="automated_action_url_hint">https://example.com/?q={RESULT}</string>
|
||||
<string name="automated_action_url_required">URL-Vorlage erforderlich</string>
|
||||
<string name="automated_action_invalid_url">Ungültige URL-Vorlage</string>
|
||||
<string name="automated_action_remove_confirm">Diese Aktion entfernen?</string>
|
||||
<string name="automated_action_list_intent">URL öffnen: %s</string>
|
||||
<string name="automated_action_list_intent_empty">URL öffnen</string>
|
||||
<string name="automated_action_webview_title">Automatisierte Aktion</string>
|
||||
<string name="clear_network_suggestions">WiFi Netzwerkvorschläge entfernen</string>
|
||||
<string name="clear_network_suggestions_summary">Betrifft nur Netzwerke, die zuvor mit dieser App bereitgestellt wurden.</string>
|
||||
<string name="really_remove_all_networks">Alle Netzwerkvorschläge entfernen?</string>
|
||||
@@ -133,12 +184,15 @@
|
||||
<string name="bluetooth_connect_fail">Es kann leider keine Verbindung zum Bluetooth-Gerät hergestellt werden</string>
|
||||
<string name="bluetooth_send_fail">Senden zum Bluetooth-Gerät fehlgeschlagen</string>
|
||||
<string name="bluetooth_send_success">Erfolgreich zum Bluetooth-Gerät gesendet</string>
|
||||
<string name="generation_category">Generation</string>
|
||||
<string name="brighten_screen">Bildschirm aufhellen</string>
|
||||
<string name="brighten_screen_summary">Helligkeit auf Maximum stellen um das Scannen eines Barcodes vom Bildschirm zu verbessern.</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>
|
||||
<string name="clear_history">Gespeicherte Codes löschen</string>
|
||||
<string name="copy_scan">Code kopieren</string>
|
||||
<string name="edit_scan">Bezeichnung ändern</string>
|
||||
<string name="edit_label">Bezeichnung ändern</string>
|
||||
<string name="remove_scan">Code löschen</string>
|
||||
<string name="enter_name">Geben Sie eine Bezeichnung für den Code ein</string>
|
||||
<string name="enter_name_hint">Bezeichung des Codes</string>
|
||||
@@ -166,6 +220,8 @@
|
||||
<string name="otpauth_add">2FA hinzufügen</string>
|
||||
<string name="search_web">Suche im Internet</string>
|
||||
<string name="search_scan">Code suchen</string>
|
||||
<string name="add_to_history">Zu gespeicherten Codes hinzufügen</string>
|
||||
<string name="added_to_history">Zu den gespeicherten Codes hinzugefügt</string>
|
||||
<string name="export_to_file">In Datei exportieren</string>
|
||||
<string name="export_as">Exportieren als?</string>
|
||||
<string name="export_csv_comma">CSV Datei mit Kommas</string>
|
||||
@@ -180,6 +236,7 @@
|
||||
<string name="pick_code_to_scan">Code im Bild auswählen</string>
|
||||
<string name="shortcut_decode">Barcode lesen</string>
|
||||
<string name="shortcut_encode">Barcode erstellen</string>
|
||||
<string name="shortcut_history">Verlauf</string>
|
||||
<string name="shortcut_preferences">Einstellungen</string>
|
||||
<string name="background_request_failed">Hintergrundanfrage fehlgeschlagen</string>
|
||||
<string name="entry_type">Typ</string>
|
||||
@@ -194,4 +251,18 @@
|
||||
<string name="wifi_phase2">Phase 2 Methode</string>
|
||||
<string name="network_suggestions">WiFi Netzwerkvorschläge</string>
|
||||
<string name="remove_suggestion">Vorschlag entfernen</string>
|
||||
<string name="email_to">Empfänger</string>
|
||||
<string name="email_subject">Betreff</string>
|
||||
<string name="email_body">Nachricht</string>
|
||||
<string name="scheme">Schema</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="query">Query</string>
|
||||
<string name="automated_action_type_custom_intent">Benutzerdefinierter Intent</string>
|
||||
<string name="automated_action_intent_uri_template">Intent-URI-Vorlage</string>
|
||||
<string name="automated_action_intent_uri_hint">intent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT={RESULT};end</string>
|
||||
<string name="automated_action_intent_uri_required">Intent-URI erforderlich</string>
|
||||
<string name="automated_action_intent_uri_invalid">Ungültige Intent-URI</string>
|
||||
<string name="automated_action_list_custom_intent">Benutzerdefinierter Intent: %s</string>
|
||||
<string name="automated_action_list_custom_intent_empty">Benutzerdefinierter Intent</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<resources>
|
||||
<string name="welcome">¡Bienvenido!</string>
|
||||
<string name="mode_expert">Avanzado</string>
|
||||
<string name="mode_expert_description">Quiero verificar el contenido leído y abrirlo manualmente.</string>
|
||||
<string name="mode_simple">Sencillo</string>
|
||||
<string name="mode_simple_description">Solo quiero escanear códigos de barras y abrirlos de inmediato.</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="scan_code">Escanear código</string>
|
||||
<string name="compose_barcode">Componer código</string>
|
||||
<string name="decode_barcode">Decodificar código de barras</string>
|
||||
<string name="content">Contenido</string>
|
||||
<string name="binary_data">(datos binarios)</string>
|
||||
@@ -14,49 +19,56 @@
|
||||
<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="colors">Fore and background</string>
|
||||
<string name="colors_black_on_white">Black on white</string>
|
||||
<string name="colors_white_on_black">White on black</string>
|
||||
<string name="colors_black_on_transparent">Black on transparent</string>
|
||||
<string name="colors_white_on_transparent">White on transparent</string>
|
||||
<string name="sequence_size">Sequence size</string>
|
||||
<string name="sequence_index">Sequence index</string>
|
||||
<string name="sequence_id">Sequence ID</string>
|
||||
<string name="label">Etiqueta</string>
|
||||
<string name="colors">Color frontal y fondo</string>
|
||||
<string name="colors_black_on_white">Negro sobre blanco</string>
|
||||
<string name="colors_white_on_black">Blanco sobre negro</string>
|
||||
<string name="colors_black_on_transparent">Negro sobre transparente</string>
|
||||
<string name="colors_white_on_transparent">Blanco sobre transparente</string>
|
||||
<string name="sequence_size">Tamaño secuencia</string>
|
||||
<string name="sequence_index">Índice secuencia</string>
|
||||
<string name="sequence_id">ID secuencia</string>
|
||||
<string name="gtin_issue_number">Número de emisión</string>
|
||||
<string name="gtin_country">Posible país de fabricación</string>
|
||||
<string name="gtin_price">Precio sugerido</string>
|
||||
<string name="gtin_add_on">Extensión UPC EAN</string>
|
||||
<string name="barcode_version_number">Version</string>
|
||||
<string name="qr_version_and_modules">%1$s (%2$d modules)</string>
|
||||
<string name="barcode_version_number">Versión</string>
|
||||
<string name="qr_version_and_modules">%1$s (%2$d modulos)</string>
|
||||
<string name="qr_data_mask">Máscara de datos</string>
|
||||
<string name="toggle_flash">Activar el flash</string>
|
||||
<string name="error_flash">Error toggling flash</string>
|
||||
<string name="error_flash">Error activando flash</string>
|
||||
<string name="share">Compartir</string>
|
||||
<string name="share_file">Share as file</string>
|
||||
<string name="share_file">Compartir como archivo</string>
|
||||
<string name="share_as">¿Compartir como?</string>
|
||||
<string name="copy_to_clipboard">Copiar al portapapeles</string>
|
||||
<string name="copy_as_deeplink">Copiar como deeplink</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 eso</string>
|
||||
<string name="pick_search_engine">Elija un motor de búsqueda</string>
|
||||
<string name="pick_search_engine">Seleccionar motor de búsqueda</string>
|
||||
<string name="format">Formato</string>
|
||||
<string name="size_width_by_height">Tamaño en píxeles: %1$d×%2$d</string>
|
||||
<string name="margin_size">Margin in pixels: %1$d</string>
|
||||
<string name="size_no_magnification">Sin aumento</string>
|
||||
<string name="add_quiet_zone">Agregar zona tranquila</string>
|
||||
<string name="input_content_here">Introduzca el contenido aquí</string>
|
||||
<string name="unescape">Expand escape sequences</string>
|
||||
<string name="unescape">Expandir secuencia de escape</string>
|
||||
<string name="encode">CODIFICAR</string>
|
||||
<string name="error_no_content">Contenido no disponible</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">Escanear constantemente</string>
|
||||
<string name="bulk_mode_summary">Siempre iniciar escaneando constantemente</string>
|
||||
<string name="bulk_mode">Escaneo continuo</string>
|
||||
<string name="bulk_mode_summary">Iniciar siempre en modo de escaneo continuo</string>
|
||||
<string name="bulk_mode_delay">Demora entre escaneos continuos</string>
|
||||
<string name="restrict_format">Restringir formato</string>
|
||||
<string name="remove_restriction">Eliminar restricción</string>
|
||||
<string name="scan_format">Escanear %s</string>
|
||||
<string name="find_code">Encontrar código</string>
|
||||
<string name="find_code_with_content">¿Qué contenido?</string>
|
||||
<string name="find_code_hint">Término o expresión regular</string>
|
||||
<string name="does_not_match_search_term">No coincide con el término de búsqueda</string>
|
||||
<string name="scan_for">Escanear %s</string>
|
||||
<string name="quarter_second">Un cuarto de segundo</string>
|
||||
<string name="half_second">Medio segundo</string>
|
||||
<string name="a_second">Un segundo</string>
|
||||
@@ -67,57 +79,96 @@
|
||||
<string name="scan_category">Escanear</string>
|
||||
<string name="content_category">Contenido</string>
|
||||
<string name="locale_category">Idioma</string>
|
||||
<string name="profile_category">Perfil</string>
|
||||
<string name="profile">Perfil</string>
|
||||
<string name="profile_default">Predeterminado</string>
|
||||
<string name="profile_add">Añadir nuevo perfil</string>
|
||||
<string name="profile_enter_name">Introduce el nombre del perfil</string>
|
||||
<string name="profile_enter_name_hint">Nombre</string>
|
||||
<string name="profile_invalid_name">Nombre de perfil no válido</string>
|
||||
<string name="profile_empty">Aún no hay perfiles</string>
|
||||
<string name="profile_current">Perfil actual</string>
|
||||
<string name="profile_remove_confirm">¿Eliminar este perfil?</string>
|
||||
<string name="current_profile">Perfil: %s</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="show_crop_handle">Mostrar delimitador de recorte</string>
|
||||
<string name="show_crop_handle_summary">Permite personalizar la región a escanear.</string>
|
||||
<string name="barcode_formats">Formatos de códigos de barras</string>
|
||||
<string name="zoom_by_swiping">Deslice hacia arriba/abajo para ampliar</string>
|
||||
<string name="zoom_by_swiping_summary">Deslice su dedo hacia arriba o abajo en la pantalla para controlar el aumento de la cámara.</string>
|
||||
<string name="auto_rotate">Reconocer los códigos de barras 1D verticalmente</string>
|
||||
<string name="auto_rotate_summary">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="zoom_by_swiping">Ampliar deslizando arriba/abajo</string>
|
||||
<string name="zoom_by_swiping_summary">Deslice el dedo hacia arriba o abajo sobre la pantalla para controlar el zoom de la cámara.</string>
|
||||
<string name="auto_rotate">Reconocer códigos 1D verticales</string>
|
||||
<string name="auto_rotate_summary">Rota automáticamente el marco de la cámara para reconocer los códigos de barras 1D verticales. Dependiendo del dispositivo, podría 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="beep">Beep on detection</string>
|
||||
<string name="beep_summary">Enable this if you want your device to beep when a code is recognized.</string>
|
||||
<string name="beep_tone">Beep tone</string>
|
||||
<string name="tone_cdma_confirm">CDMA confirm</string>
|
||||
<string name="tone_sup_confirm">Call supervisory confirm</string>
|
||||
<string name="tone_sup_radio_ack">Call supervisory radio path acknowledgment</string>
|
||||
<string name="tone_prop_ack">Positive acknowledgment</string>
|
||||
<string name="tone_prop_beep">General beep</string>
|
||||
<string name="tone_prop_beep2">General double beep</string>
|
||||
<string name="vibrate">Vibración al detectar</string>
|
||||
<string name="vibrate_summary">El dispositivo vibra cuando se reconoce un código.</string>
|
||||
<string name="beep">Sonido al detectar</string>
|
||||
<string name="beep_summary">El dispositivo emite un sonido cuando se reconoce un código.</string>
|
||||
<string name="beep_tone">Sonido</string>
|
||||
<string name="tone_cdma_confirm">Confirmar CDMA</string>
|
||||
<string name="tone_sup_confirm">Confirmar llamada de supervisión</string>
|
||||
<string name="tone_sup_radio_ack">Reconocer ruta de radio de llamada de supervisión</string>
|
||||
<string name="tone_prop_ack">Reconocimiento positivo</string>
|
||||
<string name="tone_prop_beep">Sonido general</string>
|
||||
<string name="tone_prop_beep2">Sonido doble general</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="use_history_summary">Guarda los códigos reconocidos en su dispositivo.</string>
|
||||
<string name="ignore_duplicates">Ignorar duplicados</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="ignore_any_duplicates">Ignore any duplicates</string>
|
||||
<string name="accept_duplicates">Aceptar duplicados</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="open_immediately_summary">Omite la inspección y abre el contenido inmediatamente. Podría abrir contenido dañino si está activado.</string>
|
||||
<string name="copy_immediately">Copiar en el portapapeles</string>
|
||||
<string name="copy_immediately_summary">Copia automáticamente el contenido al portapapeles. Ten en cuenta que otras aplicaciones pueden husmear en tu portapapeles en segundo plano.</string>
|
||||
<string name="show_meta_data">Mostrar metadatos</string>
|
||||
<string name="show_meta_data_summary">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="show_recreation">Show recreated barcode</string>
|
||||
<string name="show_recreation_summary">Recreate and show barcode from read content if possible.</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="show_meta_data_summary">Muestra datos adicionales del código escaneado.</string>
|
||||
<string name="show_hex_dump">Mostrar volcado hexadecimal</string>
|
||||
<string name="show_hex_dump_summary">Muestra un volcado hexadecimal del contenido escaneado.</string>
|
||||
<string name="show_checksum">Mostrar suma de comprobación</string>
|
||||
<string name="no_checksum">Sin suma de comprobación</string>
|
||||
<string name="show_recreation">Mostrar reconstrucción del código</string>
|
||||
<string name="show_recreation_summary">Reconstruye y muestra el código de barras desde el contenido leído si es posible.</string>
|
||||
<string name="close_automatically">Retroceder tras Copiar/Compartir</string>
|
||||
<string name="close_automatically_summary">Retrocede automáticamente a la pantalla de escaneo después 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="open_with_url">Abrir datos desconocidos en la URL</string>
|
||||
|
||||
<string name="automated_actions">Acciones automatizadas</string>
|
||||
<string name="automated_actions_none">No hay acciones automatizadas</string>
|
||||
<string name="automated_actions_empty">Aún no hay acciones automatizadas</string>
|
||||
<plurals name="automated_actions_count">
|
||||
<item quantity="one">%d acción automatizada</item>
|
||||
<item quantity="other">%d acciones automatizadas</item>
|
||||
</plurals>
|
||||
<string name="automated_action_add">Añadir acción</string>
|
||||
<string name="automated_action_edit">Editar acción</string>
|
||||
<string name="automated_action_regex">Expresión regular</string>
|
||||
<string name="automated_action_regex_hint">p. ej. ^https?://.*</string>
|
||||
<string name="automated_action_regex_required">Se requiere expresión regular</string>
|
||||
<string name="automated_action_regex_invalid">Expresión regular inválida</string>
|
||||
<string name="automated_action_type">Tipo de acción</string>
|
||||
<string name="automated_action_type_intent">Abrir URL</string>
|
||||
<string name="automated_action_url_template">Plantilla de URL</string>
|
||||
<string name="automated_action_url_hint">https://example.com/?q={RESULT}</string>
|
||||
<string name="automated_action_url_required">Se requiere plantilla de URL</string>
|
||||
<string name="automated_action_invalid_url">Plantilla de URL inválida</string>
|
||||
<string name="automated_action_remove_confirm">¿Eliminar esta acción?</string>
|
||||
<string name="automated_action_list_intent">Abrir URL: %s</string>
|
||||
<string name="automated_action_list_intent_empty">Abrir URL</string>
|
||||
<string name="automated_action_webview_title">Acción automatizada</string>
|
||||
<string name="clear_network_suggestions">Eliminar sugerencias de red</string>
|
||||
<string name="clear_network_suggestions_summary">Elimina todas las sugerencias de red 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_active">Enviar escaneos</string>
|
||||
<string name="send_scan_active_summary">Habilita el envío de escaneos a una 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>
|
||||
@@ -126,20 +177,23 @@
|
||||
<string name="send_type_post_json">POST application/json</string>
|
||||
<string name="send_type_external_browser">Abrir en el navegador</string>
|
||||
<string name="test_url">URL de prueba</string>
|
||||
<string name="send_scan_bluetooth">Forward scans with Bluetooth</string>
|
||||
<string name="send_scan_bluetooth_summary">Enable to forwards scans to a given Bluetooth host.</string>
|
||||
<string name="send_scan_bluetooth_host">Host</string>
|
||||
<string name="no_bluetooth_hosts_paired">No Hosts Paired</string>
|
||||
<string name="bluetooth_connect_fail">Could not connect to bluetooth device.</string>
|
||||
<string name="bluetooth_send_fail">Could not send to bluetooth device.</string>
|
||||
<string name="bluetooth_send_success">Successful bluetooth send.</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 el historial</string>
|
||||
<string name="send_scan_bluetooth">Enviar escaneos con Bluetooth</string>
|
||||
<string name="send_scan_bluetooth_summary">Habilita el envío de escaneos a un host Bluetooth.</string>
|
||||
<string name="send_scan_bluetooth_host">Anfitrión</string>
|
||||
<string name="no_bluetooth_hosts_paired">Ningún host emparejado</string>
|
||||
<string name="bluetooth_connect_fail">No se pudo conectar a un dispositivo bluetooth.</string>
|
||||
<string name="bluetooth_send_fail">No se pudo enviar a un dispositivo bluetooth.</string>
|
||||
<string name="bluetooth_send_success">Envío bluetooth completado.</string>
|
||||
<string name="generation_category">Generación</string>
|
||||
<string name="brighten_screen">Aumentar el brillo de la pantalla</string>
|
||||
<string name="brighten_screen_summary">Suba el brillo al máximo para mejorar la lectura del código de barras desde la pantalla.</string>
|
||||
<string name="really_remove_scan">¿Eliminar el escaneo seleccionado?</string>
|
||||
<string name="really_remove_all_scans">¿Eliminar todos los escaneos?</string>
|
||||
<string name="really_remove_selected_scans">¿Eliminar los escaneos seleccionados?</string>
|
||||
<string name="clear_history">Eliminar el historial</string>
|
||||
<string name="copy_scan">Copiar escaneo</string>
|
||||
<string name="edit_scan">Editar etiqueta</string>
|
||||
<string name="remove_scan">Borrar escaneo</string>
|
||||
<string name="edit_label">Editar etiqueta</string>
|
||||
<string name="remove_scan">Eliminar escaneo</string>
|
||||
<string name="enter_name">Introduzca un nombre para describir el escaneo</string>
|
||||
<string name="enter_name_hint">Nombre del escaneo</string>
|
||||
<string name="no_barcode_found">No se encontró ningún código de barras</string>
|
||||
@@ -154,7 +208,7 @@
|
||||
<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="sms_error">No se pudo 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>
|
||||
@@ -166,6 +220,8 @@
|
||||
<string name="otpauth_add">Añadir 2FA</string>
|
||||
<string name="search_web">Buscar en la web</string>
|
||||
<string name="search_scan">Buscar escaneo</string>
|
||||
<string name="add_to_history">Añadir al historial</string>
|
||||
<string name="added_to_history">Añadido al historial</string>
|
||||
<string name="export_to_file">Exportar a archivo</string>
|
||||
<string name="export_as">¿Exportar como?</string>
|
||||
<string name="export_csv_comma">Archivo CSV con comas</string>
|
||||
@@ -174,16 +230,17 @@
|
||||
<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 sentido horario</string>
|
||||
<string name="toggle_free_rotation">Toggle free rotation</string>
|
||||
<string name="pick_file">Pick a file</string>
|
||||
<string name="toggle_free_rotation">Activar rotación libre</string>
|
||||
<string name="pick_file">Seleccionar archivo</string>
|
||||
<string name="pick_image_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_history">Historial</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">Red wi-fi</string>
|
||||
<string name="entry_type">Tipo</string>
|
||||
<string name="wifi_network">Red WiFi</string>
|
||||
<string name="wifi_ssid">Nombre</string>
|
||||
<string name="wifi_type">Tipo de autenticación</string>
|
||||
<string name="wifi_password">Contraseña</string>
|
||||
@@ -191,7 +248,21 @@
|
||||
<string name="wifi_eap">Método EAP</string>
|
||||
<string name="wifi_anonymous_identity">Identidad anónima</string>
|
||||
<string name="wifi_identity">Identidad</string>
|
||||
<string name="wifi_phase2">Phase 2 method</string>
|
||||
<string name="network_suggestions">Network suggestions</string>
|
||||
<string name="remove_suggestion">Remove suggestion</string>
|
||||
<string name="wifi_phase2">Método Phase 2</string>
|
||||
<string name="network_suggestions">Sugerencias de red</string>
|
||||
<string name="remove_suggestion">Eliminar sugerencia</string>
|
||||
<string name="email_to">Destinatario</string>
|
||||
<string name="email_subject">Asunto</string>
|
||||
<string name="email_body">Cuerpo</string>
|
||||
<string name="scheme">Esquema</string>
|
||||
<string name="host">Anfitrión</string>
|
||||
<string name="query">Consulta</string>
|
||||
<string name="automated_action_type_custom_intent">Intent personalizado</string>
|
||||
<string name="automated_action_intent_uri_template">Plantilla de URI de Intent</string>
|
||||
<string name="automated_action_intent_uri_hint">intent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT={RESULT};end</string>
|
||||
<string name="automated_action_intent_uri_required">URI de Intent requerida</string>
|
||||
<string name="automated_action_intent_uri_invalid">URI de Intent no válida</string>
|
||||
<string name="automated_action_list_custom_intent">Intent personalizado: %s</string>
|
||||
<string name="automated_action_list_custom_intent_empty">Intent personalizado</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<resources>
|
||||
<string name="welcome">خوش آمدید!</string>
|
||||
<string name="mode_expert">پیشرفته</string>
|
||||
<string name="mode_expert_description">میخواهم محتوای خوانده شده را بررسی کرده و به صورت دستی باز کنم.</string>
|
||||
<string name="mode_simple">ساده</string>
|
||||
<string name="mode_simple_description">فقط میخواهم بارکدها را اسکن کنم و فوراً باز شوند.</string>
|
||||
<string name="camera_error">نمیتوان به دوربین دسترسی داشت، لطفاً دوباره تلاش کنید</string>
|
||||
<string name="scan_code">پویش کد</string>
|
||||
<string name="compose_barcode">ایجاد بارکد</string>
|
||||
@@ -14,37 +19,40 @@
|
||||
<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="colors">Fore and background</string>
|
||||
<string name="colors_black_on_white">Black on white</string>
|
||||
<string name="colors_white_on_black">White on black</string>
|
||||
<string name="colors_black_on_transparent">Black on transparent</string>
|
||||
<string name="colors_white_on_transparent">White on transparent</string>
|
||||
<string name="sequence_size">Sequence size</string>
|
||||
<string name="sequence_index">Sequence index</string>
|
||||
<string name="sequence_id">Sequence ID</string>
|
||||
<string name="label">برچسب</string>
|
||||
<string name="colors">پس زمینه و پیش زمینه</string>
|
||||
<string name="colors_black_on_white">سیاه روی سفید</string>
|
||||
<string name="colors_white_on_black">سفید روی سیاه</string>
|
||||
<string name="colors_black_on_transparent">سیاه روی شفاف</string>
|
||||
<string name="colors_white_on_transparent">سفید روی شفاف</string>
|
||||
<string name="sequence_size">اندازه دنباله</string>
|
||||
<string name="sequence_index">اندیکس دنباله</string>
|
||||
<string name="sequence_id">شناسه دنباله</string>
|
||||
<string name="gtin_issue_number">شمارهٔ اشکال</string>
|
||||
<string name="gtin_country">کشور احتمالی سازنده</string>
|
||||
<string name="gtin_price">قیمت پیشنهادی</string>
|
||||
<string name="gtin_add_on">UPC EAN extension</string>
|
||||
<string name="barcode_version_number">Version</string>
|
||||
<string name="gtin_add_on">تمدید UPC EAN</string>
|
||||
<string name="barcode_version_number">نسخه</string>
|
||||
<string name="qr_version_and_modules">%1$s (%2$d modules)</string>
|
||||
<string name="qr_data_mask">ماسک داده</string>
|
||||
<string name="toggle_flash">روشن/خاموش کردن چراغ</string>
|
||||
<string name="error_flash">خطا هنگام روشن/خاموش کردن چراغ</string>
|
||||
<string name="share">همرسانی</string>
|
||||
<string name="share_file">Share as file</string>
|
||||
<string name="share_file">اشتراک گذاری به عنوان فایل</string>
|
||||
<string name="share_as">همرسانی به عنوان؟</string>
|
||||
<string name="copy_to_clipboard">رونوشت به تخهگیره</string>
|
||||
<string name="copy_as_deeplink">کپی به صورت پیوند عمیق</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_width_by_height">اندازه به پیکسل: %1$d×%2$d</string>
|
||||
<string name="margin_size">Margin in pixels: %1$d</string>
|
||||
<string name="size_no_magnification">بدون بزرگنمایی</string>
|
||||
<string name="add_quiet_zone">اضافه کردن حوزه ساکت</string>
|
||||
<string name="input_content_here">محتویات ورودی را اینجا بنویسید</string>
|
||||
<string name="unescape">Expand escape sequences</string>
|
||||
<string name="unescape">توسیع دنبالههای فراری</string>
|
||||
<string name="encode">رمزگذاری</string>
|
||||
<string name="error_no_content">محتویاتی نیست</string>
|
||||
<string name="error_encoding_barcode">بارکد نمیتواند ایجاد شود</string>
|
||||
@@ -56,7 +64,11 @@
|
||||
<string name="bulk_mode_delay">تأخیر بین پویشهای مداوم</string>
|
||||
<string name="restrict_format">محدود کردن قالب</string>
|
||||
<string name="remove_restriction">برداشتن محدودیت</string>
|
||||
<string name="scan_format">پویش %s</string>
|
||||
<string name="find_code">کد را پیدا کنید</string>
|
||||
<string name="find_code_with_content">چه محتوا؟</string>
|
||||
<string name="find_code_hint">عبارت یا عبارت منظم</string>
|
||||
<string name="does_not_match_search_term">با عبارت جستجو مطابقت ندارد</string>
|
||||
<string name="scan_for">پویش %s</string>
|
||||
<string name="quarter_second">یکچهارم ثانیه</string>
|
||||
<string name="half_second">نیم ثانیه</string>
|
||||
<string name="a_second">یک ثانیه</string>
|
||||
@@ -67,6 +79,17 @@
|
||||
<string name="scan_category">پویش</string>
|
||||
<string name="content_category">محتویات</string>
|
||||
<string name="locale_category">زبان</string>
|
||||
<string name="profile_category">نمایه</string>
|
||||
<string name="profile">نمایه</string>
|
||||
<string name="profile_default">پیشفرض</string>
|
||||
<string name="profile_add">افزودن نمایهٔ جدید</string>
|
||||
<string name="profile_enter_name">نام نمایه را وارد کنید</string>
|
||||
<string name="profile_enter_name_hint">نام</string>
|
||||
<string name="profile_invalid_name">نام نمایه نامعتبر است</string>
|
||||
<string name="profile_empty">هنوز هیچ نمایهای نیست</string>
|
||||
<string name="profile_current">نمایهٔ فعلی</string>
|
||||
<string name="profile_remove_confirm">این نمایه حذف شود؟</string>
|
||||
<string name="current_profile">نمایه: %s</string>
|
||||
<string name="custom_locale">زبان سفارشی</string>
|
||||
<string name="follow_system_settings">پیروی از زبان سامانه</string>
|
||||
<string name="show_crop_handle">نمایش محدود کننده برش</string>
|
||||
@@ -82,19 +105,21 @@
|
||||
<string name="show_toast_in_bulk_mode_summary">هنگام پویش مداوم، دادههای پویش شده را به طور خلاصه نشان میدهید.</string>
|
||||
<string name="vibrate">لرزش هنگام تشخیص</string>
|
||||
<string name="vibrate_summary">اگر میخواهید دستگاهتان هنگام شناسایی کد بلرزد، این را فعال کنید.</string>
|
||||
<string name="beep">Beep on detection</string>
|
||||
<string name="beep_summary">Enable this if you want your device to beep when a code is recognized.</string>
|
||||
<string name="beep_tone">Beep tone</string>
|
||||
<string name="tone_cdma_confirm">CDMA confirm</string>
|
||||
<string name="tone_sup_confirm">Call supervisory confirm</string>
|
||||
<string name="tone_sup_radio_ack">Call supervisory radio path acknowledgment</string>
|
||||
<string name="tone_prop_ack">Positive acknowledgment</string>
|
||||
<string name="tone_prop_beep">General beep</string>
|
||||
<string name="tone_prop_beep2">General double beep</string>
|
||||
<string name="beep">صدای بیپ در شناسایی</string>
|
||||
<string name="beep_summary">اگر میخواهید دستگاه شما هنگام شناسایی یک کد صدا کند، این را فعال کنید.</string>
|
||||
<string name="beep_tone">صدای بیپ</string>
|
||||
<string name="tone_cdma_confirm">تأیید CDMA</string>
|
||||
<string name="tone_sup_confirm">تماس نظارتی تأیید کنید</string>
|
||||
<string name="tone_sup_radio_ack">تائید مسیر رادیویی نظارت تماس</string>
|
||||
<string name="tone_prop_ack">تایید مثبت</string>
|
||||
<string name="tone_prop_beep">صدای عمومی</string>
|
||||
<string name="tone_prop_beep2">صدای دو بیپ عمومی</string>
|
||||
<string name="use_history">ذخیرهٔ تاریخچهٔ پویش</string>
|
||||
<string name="use_history_summary">کدهای شناسایی شده را در همین دستگاه ذخیره میکند.</string>
|
||||
<string name="ignore_duplicates">نادیده گرفتن تکراری ها</string>
|
||||
<string name="ignore_consecutive_duplicates">نادیده گرفتن موارد تکراری</string>
|
||||
<string name="ignore_consecutive_duplicates_summary">موارد تکراری متوالی را در تاریخچهٔ پویش ذخیره نمیکند.</string>
|
||||
<string name="ignore_any_duplicates">Ignore any duplicates</string>
|
||||
<string name="accept_duplicates">تکراریها را پذیرا باش</string>
|
||||
<string name="open_immediately">گشودن بلافاصله</string>
|
||||
<string name="open_immediately_summary">بازرسی را رها کنید و محتویات را فورا باز کنید. در صورت فعال بودن ممکن است محتوای مضر را باز کند.</string>
|
||||
<string name="copy_immediately">قرار دادن در تختهگیره</string>
|
||||
@@ -103,13 +128,39 @@
|
||||
<string name="show_meta_data_summary">نمایش دادههای اضافی در مورد بارکد پویش شده.</string>
|
||||
<string name="show_hex_dump">نمایش کد هگز استخراج شده</string>
|
||||
<string name="show_hex_dump_summary">نمایش کد هگز استخراج شده از محتویات پویش شده.</string>
|
||||
<string name="show_recreation">Show recreated barcode</string>
|
||||
<string name="show_recreation_summary">Recreate and show barcode from read content if possible.</string>
|
||||
<string name="show_checksum">نمایش مجموعه کنترلی</string>
|
||||
<string name="no_checksum">بدون چکسام</string>
|
||||
<string name="show_recreation">نمایش بارکد بازسازی شده</string>
|
||||
<string name="show_recreation_summary">دوباره ایجاد کرده و بارکد را از محتوای خوانده شده نشان دهید اگر امکان دارد.</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="automated_actions">اقدامهای خودکار</string>
|
||||
<string name="automated_actions_none">هیچ اقدام خودکاری وجود ندارد</string>
|
||||
<string name="automated_actions_empty">هنوز اقدام خودکاری وجود ندارد</string>
|
||||
<plurals name="automated_actions_count">
|
||||
<item quantity="one">%d اقدام خودکار</item>
|
||||
<item quantity="other">%d اقدام خودکار</item>
|
||||
</plurals>
|
||||
<string name="automated_action_add">افزودن اقدام</string>
|
||||
<string name="automated_action_edit">ویرایش اقدام</string>
|
||||
<string name="automated_action_regex">عبارت منظم</string>
|
||||
<string name="automated_action_regex_hint">مثلاً ^https?://.*</string>
|
||||
<string name="automated_action_regex_required">عبارت منظم لازم است</string>
|
||||
<string name="automated_action_regex_invalid">عبارت منظم نامعتبر است</string>
|
||||
<string name="automated_action_type">نوع اقدام</string>
|
||||
<string name="automated_action_type_intent">باز کردن URL</string>
|
||||
<string name="automated_action_url_template">قالب URL</string>
|
||||
<string name="automated_action_url_hint">https://example.com/?q={RESULT}</string>
|
||||
<string name="automated_action_url_required">قالب URL لازم است</string>
|
||||
<string name="automated_action_invalid_url">قالب URL نامعتبر است</string>
|
||||
<string name="automated_action_remove_confirm">این اقدام حذف شود؟</string>
|
||||
<string name="automated_action_list_intent">باز کردن URL: %s</string>
|
||||
<string name="automated_action_list_intent_empty">باز کردن URL</string>
|
||||
<string name="automated_action_webview_title">اقدام خودکار</string>
|
||||
<string name="clear_network_suggestions">پاک کردن پیشنهادات شبکه</string>
|
||||
<string name="clear_network_suggestions_summary">همه پیشنهادات شبکه را که قبلاً توسط این برنامه ارائه شده است حذف کنید.</string>
|
||||
<string name="really_remove_all_networks">واقعاً همه شبکهها را حذف میکنید؟</string>
|
||||
@@ -122,23 +173,26 @@
|
||||
<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_form">POST application/x-www-form-urlencoded ارسال</string>
|
||||
<string name="send_type_post_json">POST application/json</string>
|
||||
<string name="send_type_external_browser">گشودن در مرورگر خارجی</string>
|
||||
<string name="test_url">آزمودن نشانی</string>
|
||||
<string name="send_scan_bluetooth">Forward scans with Bluetooth</string>
|
||||
<string name="send_scan_bluetooth">انتقال اسکن ها با بلوتوث</string>
|
||||
<string name="send_scan_bluetooth_summary">Enable to forwards scans to a given Bluetooth host.</string>
|
||||
<string name="send_scan_bluetooth_host">Host</string>
|
||||
<string name="no_bluetooth_hosts_paired">No Hosts Paired</string>
|
||||
<string name="send_scan_bluetooth_host">میزبان</string>
|
||||
<string name="no_bluetooth_hosts_paired">میزبان های جفت نشده</string>
|
||||
<string name="bluetooth_connect_fail">Could not connect to bluetooth device.</string>
|
||||
<string name="bluetooth_send_fail">Could not send to bluetooth device.</string>
|
||||
<string name="bluetooth_send_success">Successful bluetooth send.</string>
|
||||
<string name="generation_category">نسل</string>
|
||||
<string name="brighten_screen">روشن کردن صفحه نمایش</string>
|
||||
<string name="brighten_screen_summary">تنظیم روشنایی به حداکثر برای افزایش اسکن کردن بارکد از صفحه نمایش.</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="edit_label">ویرایش عنوان</string>
|
||||
<string name="remove_scan">برداشتن پویش</string>
|
||||
<string name="enter_name">یک نام برای توضیح پویش وارد کنید</string>
|
||||
<string name="enter_name_hint">نام پویش</string>
|
||||
@@ -166,6 +220,8 @@
|
||||
<string name="otpauth_add">افزودن احراز هویت چندعاملی(2FA)</string>
|
||||
<string name="search_web">جستوجوی وب</string>
|
||||
<string name="search_scan">جستوجوی پویش</string>
|
||||
<string name="add_to_history">اضافه به تاریخچه</string>
|
||||
<string name="added_to_history">اضافه شد به تاریخچه</string>
|
||||
<string name="export_to_file">برونریزی به پرونده</string>
|
||||
<string name="export_as">برونریزی به عنوان؟</string>
|
||||
<string name="export_csv_comma">پروندهٔ سیاسوی با ویرگول</string>
|
||||
@@ -174,12 +230,13 @@
|
||||
<string name="export_database">پایگاهدادهٔ اسکیولایت</string>
|
||||
<string name="saved_in_downloads">در بارگیریها ذخیره شد</string>
|
||||
<string name="rotate_image_cw">تصویر را در جهت ساعتگرد بچرخانید</string>
|
||||
<string name="toggle_free_rotation">Toggle free rotation</string>
|
||||
<string name="pick_file">Pick a file</string>
|
||||
<string name="toggle_free_rotation">تغییر چرخش آزاد</string>
|
||||
<string name="pick_file">یک فایل را انتخاب کنید</string>
|
||||
<string name="pick_image_file">پروندهٔ تصویری را برگزینید</string>
|
||||
<string name="pick_code_to_scan">کدی را برای پویش برگزینید</string>
|
||||
<string name="shortcut_decode">پویش یک بارکد</string>
|
||||
<string name="shortcut_encode">ایجاد یک بارکد</string>
|
||||
<string name="shortcut_history">تاریخچه</string>
|
||||
<string name="shortcut_preferences">تنظیمات</string>
|
||||
<string name="background_request_failed">درخواست پسزمینه انجام نشد</string>
|
||||
<string name="entry_type">گونه</string>
|
||||
@@ -192,6 +249,20 @@
|
||||
<string name="wifi_anonymous_identity">هویت ناشناس</string>
|
||||
<string name="wifi_identity">هویت</string>
|
||||
<string name="wifi_phase2">روش دو مرحلهای</string>
|
||||
<string name="network_suggestions">Network suggestions</string>
|
||||
<string name="remove_suggestion">Remove suggestion</string>
|
||||
<string name="network_suggestions">پیشنهادهای شبکه</string>
|
||||
<string name="remove_suggestion">حذف پیشنهاد</string>
|
||||
<string name="email_to">گیرنده</string>
|
||||
<string name="email_subject">موضوع</string>
|
||||
<string name="email_body">متن</string>
|
||||
<string name="scheme">برنامه</string>
|
||||
<string name="host">میزبان</string>
|
||||
<string name="query">پرس و جو</string>
|
||||
<string name="automated_action_type_custom_intent">Intent سفارشی</string>
|
||||
<string name="automated_action_intent_uri_template">الگوی URI Intent</string>
|
||||
<string name="automated_action_intent_uri_hint">intent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT={RESULT};end</string>
|
||||
<string name="automated_action_intent_uri_required">URI Intent لازم است</string>
|
||||
<string name="automated_action_intent_uri_invalid">URI Intent نامعتبر</string>
|
||||
<string name="automated_action_list_custom_intent">Intent سفارشی: %s</string>
|
||||
<string name="automated_action_list_custom_intent_empty">Intent سفارشی</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<resources>
|
||||
<string name="welcome">Bienvenue !</string>
|
||||
<string name="mode_expert">Avancé</string>
|
||||
<string name="mode_expert_description">Je souhaite vérifier le contenu lu et l\'ouvrir manuellement.</string>
|
||||
<string name="mode_simple">Simple</string>
|
||||
<string name="mode_simple_description">Je veux simplement scanner les codes-barres et les ouvrir immédiatement.</string>
|
||||
<string name="camera_error">Impossible d\'accéder à la caméra, veuillez réessayer</string>
|
||||
<string name="scan_code">Scanner un code</string>
|
||||
<string name="compose_barcode">Créer un code-barres</string>
|
||||
@@ -14,6 +19,7 @@
|
||||
<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="label">Étiquette</string>
|
||||
<string name="colors">Premier plan et arrière-plan</string>
|
||||
<string name="colors_black_on_white">Noir sur blanc</string>
|
||||
<string name="colors_white_on_black">Blanc sur noir</string>
|
||||
@@ -28,21 +34,23 @@
|
||||
<string name="gtin_add_on">Extension UPC EAN</string>
|
||||
<string name="barcode_version_number">Version</string>
|
||||
<string name="qr_version_and_modules">%1$s (%2$d modules)</string>
|
||||
<string name="qr_data_mask">Masquage</string>
|
||||
<string name="toggle_flash">Activer/Désactiver le flash</string>
|
||||
<string name="error_flash">Erreur de changement d\'état du flash</string>
|
||||
<string name="share">Partager</string>
|
||||
<string name="share_file">Partager en tant que fichier</string>
|
||||
<string name="share_as">Partager en tant que ?</string>
|
||||
<string name="copy_to_clipboard">Copier dans le presse-papiers</string>
|
||||
<string name="copy_as_deeplink">Copier sous forme de lien profond</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 cela</string>
|
||||
<string name="pick_search_engine">Choisir un moteur de recherche</string>
|
||||
<string name="format">Format</string>
|
||||
<string name="size_width_by_height">Taille en pixels : %1$d×%2$d</string>
|
||||
<string name="margin_size">Marge en pixels : %1$d</string>
|
||||
<string name="size_no_magnification">Pas de grossissement</string>
|
||||
<string name="add_quiet_zone">Ajouter une zone blanche</string>
|
||||
<string name="input_content_here">Insérer le contenu ici</string>
|
||||
<string name="unescape">Étendre les caractères d\'échappement</string>
|
||||
<string name="encode">ENCODER</string>
|
||||
@@ -56,7 +64,11 @@
|
||||
<string name="bulk_mode_delay">Délai entre les scans continus</string>
|
||||
<string name="restrict_format">Restreindre le format</string>
|
||||
<string name="remove_restriction">Supprimer la restriction</string>
|
||||
<string name="scan_format">Scan %s</string>
|
||||
<string name="find_code">Trouver un code</string>
|
||||
<string name="find_code_with_content">Quel contenu ?</string>
|
||||
<string name="find_code_hint">Terme ou expression régulière</string>
|
||||
<string name="does_not_match_search_term">Ne correspond pas au terme de recherche</string>
|
||||
<string name="scan_for">Numériser %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>
|
||||
@@ -64,16 +76,27 @@
|
||||
<string name="five_seconds">Cinq secondes</string>
|
||||
<string name="history">Historique</string>
|
||||
<string name="preferences">Préférences</string>
|
||||
<string name="scan_category">Scan</string>
|
||||
<string name="scan_category">Numériser</string>
|
||||
<string name="content_category">Contenu</string>
|
||||
<string name="locale_category">Langue</string>
|
||||
<string name="profile_category">Profil</string>
|
||||
<string name="profile">Profil</string>
|
||||
<string name="profile_default">Par défaut</string>
|
||||
<string name="profile_add">Ajouter un nouveau profil</string>
|
||||
<string name="profile_enter_name">Entrez le nom du profil</string>
|
||||
<string name="profile_enter_name_hint">Nom</string>
|
||||
<string name="profile_invalid_name">Nom de profil invalide</string>
|
||||
<string name="profile_empty">Aucun profil pour le moment</string>
|
||||
<string name="profile_current">Profil actuel</string>
|
||||
<string name="profile_remove_confirm">Supprimer ce profil ?</string>
|
||||
<string name="current_profile">Profil : %s</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 contrôle du cadrage</string>
|
||||
<string name="show_crop_handle_summary">Restreindre le scan à une zone 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">Balayer vers le haut ou vers le bas de l’écran pour contrôler le niveau de zoom de la caméra.</string>
|
||||
<string name="zoom_by_swiping_summary">Balayer vers le haut ou vers le bas de l\'écran pour contrôler le niveau de 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>
|
||||
@@ -85,16 +108,18 @@
|
||||
<string name="beep">Biper à la détection</string>
|
||||
<string name="beep_summary">Émettre un son lorsqu\'un code est détecté.</string>
|
||||
<string name="beep_tone">Tonalité du signal sonore</string>
|
||||
<string name="tone_cdma_confirm">CDMA confirm</string>
|
||||
<string name="tone_sup_confirm">Call supervisory confirm</string>
|
||||
<string name="tone_sup_radio_ack">Call supervisory radio path acknowledgment</string>
|
||||
<string name="tone_prop_ack">Positive acknowledgment</string>
|
||||
<string name="tone_prop_beep">General beep</string>
|
||||
<string name="tone_prop_beep2">General double beep</string>
|
||||
<string name="tone_cdma_confirm">Confirmation CDMA</string>
|
||||
<string name="tone_sup_confirm">Confirmation de la supervision de l\'appel</string>
|
||||
<string name="tone_sup_radio_ack">Accusé de réception du chemin radio de supervision d\'appel</string>
|
||||
<string name="tone_prop_ack">Accusé de réception positif</string>
|
||||
<string name="tone_prop_beep">Bip simple</string>
|
||||
<string name="tone_prop_beep2">Bip double</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="ignore_duplicates">Ignorer les doublons</string>
|
||||
<string name="ignore_consecutive_duplicates">Ignorer les doublons consécutifs</string>
|
||||
<string name="ignore_any_duplicates">Ignorer tous les doublons</string>
|
||||
<string name="accept_duplicates">Autoriser les doublons</string>
|
||||
<string name="open_immediately">Ouvrir immédiatement</string>
|
||||
<string name="open_immediately_summary">Ignorer l\'inspection et ouvrir le contenu immédiatement. Si cette option est activée, cela peut ouvrir un contenu nocif.</string>
|
||||
<string name="copy_immediately">Copier dans le presse-papiers</string>
|
||||
@@ -103,13 +128,39 @@
|
||||
<string name="show_meta_data_summary">Afficher des informations supplémentaires sur le code-barres scanné.</string>
|
||||
<string name="show_hex_dump">Afficher le dump hexadécimal</string>
|
||||
<string name="show_hex_dump_summary">Afficher le dump hexadécimal du contenu scanné.</string>
|
||||
<string name="show_checksum">Afficher la somme de contrôle</string>
|
||||
<string name="no_checksum">Pas de somme de contrôle</string>
|
||||
<string name="show_recreation">Afficher le code-barres recréé</string>
|
||||
<string name="show_recreation_summary">Recréer et afficher, si possible, le code-barres à partir du contenu scanné.</string>
|
||||
<string name="close_automatically">Revenir après une copie/partage</string>
|
||||
<string name="close_automatically_summary">Revenir automatiquement sur l\'écran de scan après avoir copié ou partagé le contenu scanné.</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 les données inconnues avec l’URL</string>
|
||||
<string name="open_with_url">Ouvrir les données inconnues avec l\'URL</string>
|
||||
|
||||
<string name="automated_actions">Actions automatiques</string>
|
||||
<string name="automated_actions_none">Aucune action automatique</string>
|
||||
<string name="automated_actions_empty">Pas encore d’actions automatiques</string>
|
||||
<plurals name="automated_actions_count">
|
||||
<item quantity="one">%d action automatique</item>
|
||||
<item quantity="other">%d actions automatiques</item>
|
||||
</plurals>
|
||||
<string name="automated_action_add">Ajouter une action</string>
|
||||
<string name="automated_action_edit">Modifier l’action</string>
|
||||
<string name="automated_action_regex">Expression régulière</string>
|
||||
<string name="automated_action_regex_hint">ex. ^https?://.*</string>
|
||||
<string name="automated_action_regex_required">Expression régulière requise</string>
|
||||
<string name="automated_action_regex_invalid">Expression régulière invalide</string>
|
||||
<string name="automated_action_type">Type d’action</string>
|
||||
<string name="automated_action_type_intent">Ouvrir l’URL</string>
|
||||
<string name="automated_action_url_template">Modèle d’URL</string>
|
||||
<string name="automated_action_url_hint">https://example.com/?q={RESULT}</string>
|
||||
<string name="automated_action_url_required">Modèle d’URL requis</string>
|
||||
<string name="automated_action_invalid_url">Modèle d’URL invalide</string>
|
||||
<string name="automated_action_remove_confirm">Supprimer cette action ?</string>
|
||||
<string name="automated_action_list_intent">Ouvrir l’URL : %s</string>
|
||||
<string name="automated_action_list_intent_empty">Ouvrir l’URL</string>
|
||||
<string name="automated_action_webview_title">Action automatique</string>
|
||||
<string name="clear_network_suggestions">Supprimer les suggestions de réseau</string>
|
||||
<string name="clear_network_suggestions_summary">Supprimer toutes les suggestions de réseau précédemment fournies par cette application.</string>
|
||||
<string name="really_remove_all_networks">Vraiment supprimer tous les réseaux ?</string>
|
||||
@@ -133,12 +184,15 @@
|
||||
<string name="bluetooth_connect_fail">Impossible de se connecter à l\'appareil Bluetooth.</string>
|
||||
<string name="bluetooth_send_fail">Impossible d\'envoyer à l\'appareil Bluetooth.</string>
|
||||
<string name="bluetooth_send_success">Envoi Bluetooth réussi.</string>
|
||||
<string name="generation_category">Génération</string>
|
||||
<string name="brighten_screen">Augmenter la luminosité de l\'écran</string>
|
||||
<string name="brighten_screen_summary">Régler la luminosité au maximum pour améliorer la lecture d\'un code-barres à l\'écran.</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">Supprimer l\'historique</string>
|
||||
<string name="copy_scan">Copier le scan</string>
|
||||
<string name="edit_scan">Modifier l\'étiquette</string>
|
||||
<string name="edit_label">Modifier 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>
|
||||
@@ -166,6 +220,8 @@
|
||||
<string name="otpauth_add">Ajouter l\'authentification à deux facteurs</string>
|
||||
<string name="search_web">Rechercher sur internet</string>
|
||||
<string name="search_scan">Rechercher un scan</string>
|
||||
<string name="add_to_history">Ajouter à l\'historique</string>
|
||||
<string name="added_to_history">Ajouté à l\'historique</string>
|
||||
<string name="export_to_file">Exporter en tant que fichier</string>
|
||||
<string name="export_as">Exporter en tant que ?</string>
|
||||
<string name="export_csv_comma">CSV avec virgules</string>
|
||||
@@ -180,6 +236,7 @@
|
||||
<string name="pick_code_to_scan">Choisir 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_history">Historique</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>
|
||||
@@ -194,4 +251,18 @@
|
||||
<string name="wifi_phase2">Méthode de la phase 2</string>
|
||||
<string name="network_suggestions">Suggestions de réseaux</string>
|
||||
<string name="remove_suggestion">Supprimer les suggestions</string>
|
||||
<string name="email_to">Destinataire</string>
|
||||
<string name="email_subject">Sujet</string>
|
||||
<string name="email_body">Corps du message</string>
|
||||
<string name="scheme">Schéma</string>
|
||||
<string name="host">Hôte</string>
|
||||
<string name="query">Requête</string>
|
||||
<string name="automated_action_type_custom_intent">Intent personnalisé</string>
|
||||
<string name="automated_action_intent_uri_template">Modèle d’URI d’Intent</string>
|
||||
<string name="automated_action_intent_uri_hint">intent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT={RESULT};end</string>
|
||||
<string name="automated_action_intent_uri_required">URI d’Intent requise</string>
|
||||
<string name="automated_action_intent_uri_invalid">URI d’Intent invalide</string>
|
||||
<string name="automated_action_list_custom_intent">Intent personnalisé : %s</string>
|
||||
<string name="automated_action_list_custom_intent_empty">Intent personnalisé</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