Compare commits

...
94 Commits
Author SHA1 Message Date
Markus Fisch 0368ab5f68 Advance version number to 1.20.1 2019-12-22 13:50:05 +01:00
Markus Fisch c44ee8f063 Directly invoke insets listener below Lollipop
Because ViewCompat.setOnApplyWindowInsetsListener() will never
fire on devices below Lollipop.
2019-12-22 13:45:33 +01:00
Markus Fisch 3bfa6345a0 Update description in README 2019-12-21 18:31:56 +01:00
Markus Fisch 77d87ba995 Advance version number to 1.20.0 2019-12-21 18:13:48 +01:00
Markus Fisch a2ac68028f Put all FABs in a CoordinatorLayout 2019-12-21 17:49:56 +01:00
Markus Fisch aeb945f145 Make system bars a bit less transparent
So the foreground text can be seen better.
2019-12-21 17:49:28 +01:00
Markus Fisch aadde397ea Color nav bar when list is as high as the view too
Because there's now a list footer, the navigation bar at
the bottom of the screen must be translucent as soon as
the last item touches the end of the screen.
2019-12-21 17:49:19 +01:00
Markus Fisch 063fea913e Add additional next action item to encode fragment
Because android:windowSoftInputMode="adjustResize" doesn't go
well with android:windowFullscreen="true" and the latter one
is needed to draw below the system bars.
2019-12-21 17:26:07 +01:00
Markus Fisch 9d57889b81 Fix returning result for SCAN intents again
Actually use the flag to return the result and it's
working like it should.
2019-12-21 17:01:07 +01:00
Markus Fisch 233b69623f Revert "Fix returning result for SCAN intents"
SCAN intents are still processed in CameraActivity - not in
PickActivity.

This reverts commit 7416037762.
2019-12-21 16:59:42 +01:00
Markus Fisch c7d6e60efb Scroll content below tool and system bars 2019-12-21 16:11:23 +01:00
Markus Fisch 63688a9994 Refactor format to resolve variable shadowing 2019-12-20 10:53:07 +01:00
Markus Fisch dc74e33b8b Remove unnecessary <merge/> tag 2019-12-19 16:45:04 +01:00
Markus Fisch 81f2620a69 Add a style for secondary text
To better distinguish between content and secondary
information.
2019-12-19 16:44:29 +01:00
Markus Fisch 1c95550f5d Remove bottom margin after content on decode
Looks better with content length and format on one line
below it.
2019-12-19 16:43:19 +01:00
Markus Fisch 37a821818d Put character count and format on one line 2019-12-19 16:42:40 +01:00
Markus Fisch 29e0bdfbc3 Increase font size of barcode contents in history 2019-12-17 14:30:52 +01:00
Markus Fisch d4bbcef011 Fix transfering barcode format from history 2019-12-17 14:29:44 +01:00
Markus Fisch 81fc41ea81 Tag all activities as resizable 2019-12-17 14:11:42 +01:00
Markus Fisch 8cadf42c86 Convert encode button to a FAB
And make sure it's always reachable and above the
soft keyboard.
2019-12-17 14:09:10 +01:00
Markus Fisch e29ec2cc40 Fix some style issues
Apply auto format.
2019-12-17 13:57:54 +01:00
Markus Fisch 05952e7f7f Use explict locale when converting to lower case 2019-12-17 13:57:17 +01:00
Markus Fisch 0e8abb4607 Use a common style for all FABs 2019-12-17 13:51:22 +01:00
Markus Fisch cede9186a2 Use just 0 instead of an elaborate variable 2019-12-17 13:35:22 +01:00
Markus Fisch c1c9fa050b Fix casting context to AppCompatActivity
On Android 4.x, since support library 23.3.0, View.getContext()
returns a TintContextWrapper object instead of an Activity.
2019-12-17 13:30:27 +01:00
Markus Fisch 76d86288cc Preset file name to save with format and content
Saves typing it out again when saving a barcode as a file.
2019-12-17 11:34:42 +01:00
Markus Fisch c0902f8830 Update build tools 2019-12-17 11:11:22 +01:00
Markus Fisch 1d3546d676 Update Kotlin version 2019-12-09 19:48:50 +01:00
Balázs ÚrandMarkus Fisch 58baab6ffd Update Hungarian translation 2019-12-09 19:46:28 +01:00
zmniandMarkus Fisch 3bafa60d59 Update Indonesian translation 2019-11-28 13:25:57 +01:00
Markus Fisch 825062ba78 Advance version number to 1.19.0 2019-11-24 17:32:23 +01:00
Markus Fisch d128e3cc24 Use property access syntax
And remove explicit types.
2019-11-24 17:21:46 +01:00
Markus Fisch 83619834a8 Remember last selected barcode format
When encoding a new barcode.

Makes it easier to use the same format multiple times.
2019-11-24 17:11:54 +01:00
Markus Fisch cb16220ebb Update Kotlin and SDK version to 29 2019-11-22 10:55:20 +01:00
Markus Fisch 7f3af23773 Allow scaling a new barcode to half its size
Apparently some barcodes aren't created with enough "quiet space"
inside the generated image so scanning them from a screen can be
a little tough.
2019-11-19 21:52:20 +01:00
Markus Fisch 9898a14dd3 Advance version number to 1.18.0 2019-11-16 15:42:19 +01:00
Markus Fisch 85fc475118 Highlight codes in pick activity 2019-11-16 15:31:44 +01:00
Markus Fisch 987e8531e6 Add a setting to open contents without inspection 2019-11-16 14:46:25 +01:00
Markus Fisch 4d9a537bef Mention Downloads in Save-as-file dialog
To give the user an idea where the file will get saved.
2019-11-16 14:28:24 +01:00
Markus Fisch 05de15488e Fix cropping rotated images 2019-11-16 14:19:18 +01:00
Markus Fisch 721a3ee801 Improve visability of crop border 2019-11-16 14:16:33 +01:00
Markus Fisch bca000c21b Improve title of PickActivity 2019-11-16 13:20:20 +01:00
Markus Fisch 80afd0c5b7 Remove up navigation from PickActivity
There's no up - just back.
2019-11-16 13:10:09 +01:00
Markus Fisch 7416037762 Fix returning result for SCAN intents
Set parameter for `com.google.zxing.client.android.SCAN`
intents so the result is returned instead of displayed.
2019-11-16 13:00:57 +01:00
Markus Fisch 325aa00bf8 Make open URL default action on decode fragment
I think most users would rather want to open an URL from a
QR code (or something like that) than sharing the contents
of a barcode to another app.
2019-11-14 16:14:27 +01:00
Markus Fisch 40c2cc9b60 Improve handling of nullable types
And update a few other Kotlin improvements along the way.
2019-11-14 13:32:12 +01:00
Markus Fisch 1bf16a2fb7 Set explicit locale for case conversions
To make Lint happy. Shouldn't make a difference.
2019-11-14 13:31:12 +01:00
Markus Fisch 2687ce031a Add gradle.properties to repository
Since SonarQube is removed, this file can be part of the
repository again.
2019-11-14 12:58:43 +01:00
Markus Fisch 3213169ea1 Move PickActivity to the right directory
Misplaced by mistake.
2019-11-14 12:53:27 +01:00
Markus Fisch 5c3d6c4b31 Advance version number to 1.17.1 2019-11-13 22:07:06 +01:00
Markus Fisch 336f596d08 Add a warning to not upgrade ZXing
Do NOT upgrade ZXing because newer versions won't work on API < 24.
For details see: https://github.com/zxing/zxing/issues/1170
2019-11-13 15:39:34 +01:00
Markus Fisch 930656fbe5 Update tools version 2019-11-13 15:39:16 +01:00
Markus Fisch 3b45061974 Check if data intent of onActivityResult is null
Can be null if the user doesn't actually choose an image and
goes back instead.
2019-11-13 15:11:46 +01:00
Markus Fisch 983c3e3c38 Advance version number to 1.17.0 2019-11-12 23:38:24 +01:00
Markus Fisch 691fe05322 Add a save-as-file menu item to the barcode view
Because many people don't use the share menu.
2019-11-12 17:14:39 +01:00
Markus Fisch 569389bb12 Add some line breaks to improve readability 2019-11-12 15:22:29 +01:00
Markus Fisch da886291cf Add a menu item to pick an image file
Because many people don't use the share menu.
2019-11-12 14:42:06 +01:00
Markus Fisch 05b219b3ea Update bar transparency on draw in zoom/pinch view
It's almost the same as updating it on every touch because
every touch triggers a draw.
2019-11-12 13:34:47 +01:00
Markus Fisch 7cfefe7791 Add a manual picking activity for shared images
So the user can choose what barcode to scan if there are multiple
barcodes in the shared image. Also it's possible to focus a small
barcode in a shared image to make it readable.
2019-11-11 16:57:53 +01:00
Markus Fisch a2fe94285d Fix line break in PRIVACY 2019-11-11 16:53:34 +01:00
Markus Fisch 6429a1ba52 Apply auto format 2019-11-09 16:57:26 +01:00
Markus Fisch 477ae25fc7 Use discrete style for barcode size seekbar
Makes the steps visible.
2019-11-09 14:46:27 +01:00
Markus Fisch a2646ffdb3 Remove SonarQube integration
More trouble than it's worth.
2019-11-09 14:44:45 +01:00
Markus Fisch 7a93172876 Advance version number to 1.16.0 2019-11-07 21:52:59 +01:00
Markus Fisch 327cbe55d7 Update system bar transparency for double tap too
Double tapping the barcode (image) magnifies it so the system
bars need to be updated accordingly.
2019-11-06 15:10:31 +01:00
Markus Fisch 35ec26868c Show a toast if creating file to share fails
Give the user feedback if it's not possible to save the
generated barcode.
2019-11-06 15:00:55 +01:00
Balázs ÚrandMarkus Fisch 0c7317f09e Update Hungarian translation 2019-11-01 19:09:58 +01:00
karottenbaumandMarkus Fisch a049db588b Improve Dutch translations 2019-10-31 10:35:05 +01:00
zmniandMarkus Fisch 840691451a Update indonesian translation 2019-10-30 17:10:46 +01:00
Markus Fisch b50c956a21 Remove unused resources from rasterization script
Along with some stray tab characters and a superfluous
type annotation.
2019-10-26 13:23:21 +02:00
molikunerandMarkus Fisch 3820d647e0 Use Flows for DB access
* Replaced List with Flow to keep the memory usage low while exporting
* Updated kotlin to 1.3.50
* Updated kotlinx.coroutines to 1.3.2 to have stable Flow support
* Cleaned CSVBuilder#buildWith to simplify code
2019-10-13 16:25:27 +02:00
Markus Fisch ce234381c8 Add a comment why a post() is necessary
Apparently it's necessary to give Android some time before
coloring the system bars or it won't work always.
2019-10-13 13:16:19 +02:00
Ludovico de NittisandMarkus Fisch 2abeccdd1b Complete Italian translation 2019-10-13 12:47:14 +02:00
Markus Fisch ae4d6f8e2a Increase version code for Play upload 2019-10-12 18:24:41 +02:00
Markus Fisch 78c3253979 Remove Leak Canary
Because it's necessary to increase the minSDK for the
release build too. This makes no sense.
2019-10-12 18:20:17 +02:00
Markus Fisch 8026478052 Advance version number to 1.15.0 2019-10-12 17:45:14 +02:00
Markus Fisch 0d2ac7751e Color tool/system bars when barcode is zoomed 2019-10-12 16:55:05 +02:00
Markus Fisch 47cb298317 Add Leak Canary to debug build 2019-10-11 20:12:27 +02:00
Markus Fisch c1d0b98990 Move lambdas out of parenthesis
And some other optimizations.
2019-10-11 14:31:03 +02:00
Markus Fisch 798c10d420 Only color tool/statusbar when content is scrolled 2019-10-11 11:44:47 +02:00
Markus Fisch 0d65f41bb5 Color tool/system bars when a view is scrollable
According to Material Design, scrollable content should either
scroll to the edges of the screen or behind another surface.

So to avoid clipping contents against an invisible edge, it's
necessary to show the (usually transparent) tool and system
bars for a visible edge.
2019-10-11 10:17:29 +02:00
Markus Fisch eb32af9cda Apply auto format 2019-10-06 17:16:16 +02:00
Markus Fisch cb82374712 Update tools version 2019-10-06 17:06:28 +02:00
Markus Fisch c4f7189435 Use underscore for unused variable 2019-10-06 17:06:02 +02:00
molikunerandMarkus Fisch 2c037a6322 Add history export feature
* Added new action item in HistoryFragment to export the history
* Removed some GlobalScope to get more structured concurrency safety
* Replaced all usage of Database with calls to new DatabaseRepository
* Added alertDialog function to simplify usage as function
* Added information for successfully saved file
* Added CSVBuilder
2019-10-04 17:03:03 +02:00
MonochromeandMarkus Fisch 6b9e6ee5b4 Add an intent-filter for action VIEW
So it's possible to decode image by opening them, too.
2019-10-01 19:41:58 +02:00
molikunerandMarkus Fisch adecdf7e27 Remove parsing of OTPs
We should not check the content of the OTP code, instead let the receiver handle the
content and give appropriate errors, if needed.
2019-09-30 16:07:52 +02:00
Markus Fisch d925e136ef Fix Russian translation
Remove app_name (which is the same for all languages) and use
tabs instead of spaces for indentation.
2019-09-30 13:46:18 +02:00
molikunerandMarkus Fisch 3ae7e561c4 Standardise HTTP action
* Rename IAction uses to simplify code

* Renamed SimpleIntentIAction to IntentAction
* Renamed IntentAction#executeForIntent to createIntent
* Added suspend modifier to IAction#execute to simplify later work
* Added parseAndNormalizeUri
* Optimized usage of Coroutines in WifiAction
* Replaced one java Pattern instance with Kotlin Regex

* Simplify DecodeFragment#openUrl by moving stuff into it's own action

* Added OpenOrSearchAction
* Added WebAction
* Added DEFAULT_ACTION

* IgnoreCase in SchemeAction
2019-09-30 13:36:18 +02:00
Eugene KotsareuandMarkus Fisch ca619efa14 Add Russian translation 2019-09-24 22:23:01 +02:00
Markus Fisch 47d5923a04 Advance version number to 1.14.0 2019-09-08 21:19:52 +02:00
Markus Fisch 8c22a8ab00 Apply auto formatting 2019-09-08 21:15:12 +02:00
molikunerandMarkus Fisch 8152cb6a24 Add OTP support 2019-09-08 21:06:05 +02:00
zmniandMarkus Fisch ffed4fc5cf Update Indonesian translation 2019-09-04 16:47:56 +02:00
89 changed files with 2413 additions and 824 deletions
+1 -1
View File
@@ -1,8 +1,8 @@
*.DS_Store
*.swp
.gradle
.idea
build
local.properties
gradle.properties
infer-out
*.iml
+54
View File
@@ -1,5 +1,59 @@
# Change Log
# 1.20.1
* Fix window insets below Android Lollipop
# 1.20.0
* Scroll content below tool and system bars
* Preset file name to save with format and content
* Add encode menu item to make encoding easier when keyboard is visible
* Increase font size of barcode contents in history
* Update Hungarian translation
* Update Indonesian translation
* Fix encoding for old Androids
* Fix returning result for ZXing's SCAN intent again
* Fix transfering barcode format from history
# 1.19.0
* Allow scaling a new barcode to half its size
* Remember last selected barcode format
# 1.18.0
* Make open URL default action after decoding a barcode
* Add a setting to open contents without inspection
* Highlight codes for picking in shared/loaded images
* Improve visability of crop border
* Fix returning result for ZXing's SCAN intent
# 1.17.1
* Fix navigating back without choosing an image
# 1.17.0
* Pinch/Zoom shared/loaded images to scan just a section
* Add a load file menu item to load an image file
* Add a save file menu item to the barcode view
* Use discrete style for size seek bar
* Fix bar transparency when zooming/pinching
# 1.16.0
* Improve displaying scanned barcodes
* Show a message if a barcode cannot be shared
* Fix setting system bar transparency when double taping a barcode
* Update Dutch translation
* Update Hungarian translation
* Update Indonesian translation
* Update Italian translation
# 1.15.0
* Additionally export history as CSV file
* Color tool and system bars when a view is scrolled
* Add an intent filter for opening images
* Add Russian translation
# 1.14.0
* Add OTP support
* Update Indonesian translation
# 1.13.1
* Fix crash caused by missing support for Regex on Android < 6
-3
View File
@@ -14,9 +14,6 @@ bundle: lint
lint:
./gradlew lintDebug
sonarqube:
./gradlew sonarqube
infer: clean
infer -- ./gradlew assembleDebug
+2 -1
View File
@@ -2,4 +2,5 @@
Binary Eye does not collect any personal information.
If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me.
If you have any questions or suggestions about my Privacy Policy, do not
hesitate to contact me.
+13 -5
View File
@@ -2,12 +2,20 @@
Yet another barcode scanner for Android. As if there weren't [enough][play].
This one is totally free, open source and does use the [ZXing][zxing] barcode
scanning library.
This one is free, without any ads and completely open source.
Unlike many others, it works in portrait and landscape orientation, can read
inverted codes, comes in Material Design and doesn't do much else but decoding
a barcode.
Works in portrait and landscape orientation, can read inverted codes,
comes in Material Design and can also generate barcodes.
Uses the [ZXing][zxing] ("Zebra Crossing") barcode scanning library that
supports the following barcode formats for reading:
AZTEC, CODABAR, CODE 39, CODE 93, CODE 128, DATA MATRIX, EAN 8, EAN 13, ITF,
MAXICODE, PDF417, QR CODE, RSS 14, RSS EXPANDED, UPC A, UPC E and
UPC EAN EXTENSION.
And those barcodes formats can be generated with ZXing:
AZTEC, CODABAR, CODE 39, CODE 128, DATA MATRIX, EAN 8, EAN 13, ITF, PDF 417,
QR CODE and UPC A.
And it's written in [Kotlin][kotlin] because I wanted to explore that a
bit more.
+8 -6
View File
@@ -9,8 +9,8 @@ android {
minSdkVersion 9
targetSdkVersion sdk_version
versionCode 38
versionName '1.13.1'
versionCode 50
versionName '1.20.1'
// it's recommended to set this value to the lowest API level
// able to provide all the functionality
@@ -58,19 +58,21 @@ android {
}
dependencies {
androidTestImplementation ('com.android.support.test:runner:1.0.2') {
androidTestImplementation('com.android.support.test:runner:1.0.2') {
exclude module: 'support-annotations'
}
androidTestImplementation ('com.android.support.test:rules:1.0.2') {
androidTestImplementation('com.android.support.test:rules:1.0.2') {
exclude module: 'support-annotations'
}
testImplementation 'junit:junit:4.12'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
implementation "com.android.support:appcompat-v7:$support_version"
implementation "com.android.support:design:$support_version"
// Do NOT upgrade ZXing because newer versions won't work on API < 24
// https://github.com/zxing/zxing/issues/1170
implementation 'com.google.zxing:core:3.3.3'
implementation 'com.github.markusfisch:CameraView:1.8.4'
implementation 'com.github.markusfisch:ScalingImageView:1.1.3'
implementation 'com.github.markusfisch:ScalingImageView:1.2.0'
}
+2 -2
View File
@@ -3,10 +3,10 @@
<!-- Lint can't understand Kotlin sources and reports all resources
as unused -->
<issue id="UnusedResources">
<ignore path="src/main/res/"/>
<ignore path="src/main/res/" />
</issue>
<!-- Ignore RenderScript output -->
<issue id="UnusedResources">
<ignore path="*/res/rs/debug/raw/bc*"/>
<ignore path="*/res/rs/debug/raw/bc*" />
</issue>
</lint>
@@ -21,22 +21,21 @@ class PreprocessorTest {
val pattern = Pattern.compile(
"[0-9]+-([0-9]+)x([0-9]+)-([0-9]+)deg.yuv"
)
for (file in assets.list("yuv")) {
val files = assets.list("yuv") ?: return
for (file in files) {
val m = pattern.matcher(file)
if (!m.find() || m.groupCount() < 3) {
continue
}
val frameWidth = m.group(1).toInt()
val frameHeight = m.group(2).toInt()
val frameOrientation = m.group(3).toInt()
val frameWidth = m.group(1) ?: return
val frameHeight = m.group(2) ?: return
val frameOrientation = m.group(3) ?: return
val frameData = assets.open("yuv/$file").readBytes()
val preprocessor = Preprocessor(
InstrumentationRegistry.getTargetContext(),
frameWidth,
frameHeight,
frameOrientation
frameWidth.toInt(),
frameHeight.toInt(),
frameOrientation.toInt()
)
preprocessor.process(frameData)
val result = zxing.decode(
+15 -5
View File
@@ -18,6 +18,7 @@
android:allowBackup="true"
android:fullBackupContent="true"
android:supportsRtl="true"
android:resizeableActivity="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
@@ -38,11 +39,6 @@
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
<intent-filter android:label="@string/decode_barcode">
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
</intent-filter>
<intent-filter>
<action android:name="com.google.zxing.client.android.SCAN"/>
<category android:name="android.intent.category.DEFAULT"/>
@@ -57,6 +53,20 @@
android:scheme="http"/>
</intent-filter>
</activity>
<activity
android:name=".activity.PickActivity"
android:label="@string/pick_code_to_scan">
<intent-filter android:label="@string/decode_barcode">
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
</intent-filter>
<intent-filter android:label="@string/decode_barcode">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*" android:scheme="file"/>
</intent-filter>
</activity>
<activity
android:name=".activity.MainActivity"
android:label="@string/app_name"/>
@@ -1,18 +1,30 @@
package de.markusfisch.android.binaryeye.actions
import de.markusfisch.android.binaryeye.actions.mail.MailAction
import de.markusfisch.android.binaryeye.actions.otpauth.OtpauthAction
import de.markusfisch.android.binaryeye.actions.search.OpenOrSearchAction
import de.markusfisch.android.binaryeye.actions.sms.SmsAction
import de.markusfisch.android.binaryeye.actions.tel.TelAction
import de.markusfisch.android.binaryeye.actions.vtype.vcard.VCardAction
import de.markusfisch.android.binaryeye.actions.vtype.vevent.VEventAction
import de.markusfisch.android.binaryeye.actions.web.WebAction
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
object ActionRegistry {
val DEFAULT_ACTION: IAction = OpenOrSearchAction
val REGISTRY: Set<IAction> = setOf(
MailAction, SmsAction, TelAction, VCardAction, VEventAction, WifiAction
MailAction,
OtpauthAction,
SmsAction,
TelAction,
VCardAction,
VEventAction,
WebAction,
WifiAction
)
fun getAction(data: ByteArray): IAction? = REGISTRY.find {
fun getAction(data: ByteArray): IAction = REGISTRY.find {
it.canExecuteOn(data)
}
} ?: DEFAULT_ACTION
}
@@ -4,21 +4,22 @@ import android.content.Context
import android.content.Intent
import android.widget.Toast
import de.markusfisch.android.binaryeye.app.execShareIntent
import de.markusfisch.android.binaryeye.app.parseAndNormalizeUri
interface IAction {
val iconResId: Int
val titleResId: Int
fun canExecuteOn(data: ByteArray): Boolean
fun execute(context: Context, data: ByteArray)
suspend fun execute(context: Context, data: ByteArray)
}
abstract class SimpleIntentIAction : IAction {
abstract class IntentAction : IAction {
abstract val errorMsg: Int
final override fun execute(context: Context, data: ByteArray) {
final override suspend fun execute(context: Context, data: ByteArray) {
val intent =
executeForIntent(context, data) ?: return Toast.makeText(
createIntent(context, data) ?: return Toast.makeText(
context,
errorMsg,
Toast.LENGTH_LONG
@@ -26,11 +27,25 @@ abstract class SimpleIntentIAction : IAction {
execShareIntent(context, intent)
}
abstract fun executeForIntent(context: Context, data: ByteArray): Intent?
abstract suspend fun createIntent(context: Context, data: ByteArray): Intent?
}
fun IAction?.validateOrGetNew(data: ByteArray): IAction? {
return this?.takeIf {
canExecuteOn(data)
} ?: ActionRegistry.getAction(data)
abstract class SchemeAction : IAction {
abstract val scheme: String
open val intentAction: String = Intent.ACTION_VIEW
open val buildRegex: Boolean = false
final override fun canExecuteOn(data: ByteArray): Boolean {
val content = String(data)
return if (buildRegex) {
content.matches("""^$scheme://[\w\W]+$""".toRegex(RegexOption.IGNORE_CASE))
} else {
content.startsWith("$scheme://", ignoreCase = true)
}
}
final override suspend fun execute(context: Context, data: ByteArray) {
val uri = parseAndNormalizeUri(String(data))
execShareIntent(context, Intent(intentAction, uri))
}
}
@@ -4,9 +4,9 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
import de.markusfisch.android.binaryeye.actions.IntentAction
object MailAction : SimpleIntentIAction() {
object MailAction : IntentAction() {
private val mailRegex =
"""^mail(?:to)?:([\w.%+-]+@[A-Za-z\d.-]+\.[A-Za-z]{2,6}(?:[?&](?:subject|body)=[\S\s]*?){0,2})$""".toRegex()
@@ -18,7 +18,7 @@ object MailAction : SimpleIntentIAction() {
return String(data).matches(mailRegex)
}
override fun executeForIntent(context: Context, data: ByteArray): Intent? {
override suspend fun createIntent(context: Context, data: ByteArray): Intent? {
val mailWithMessage = mailRegex.matchEntire(
String(data)
)?.groupValues?.get(1) ?: return null
@@ -0,0 +1,10 @@
package de.markusfisch.android.binaryeye.actions.otpauth
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SchemeAction
object OtpauthAction : SchemeAction() {
override val iconResId: Int = R.drawable.ic_action_otpauth
override val titleResId: Int = R.string.otpauth_add
override val scheme: String = "otpauth"
}
@@ -0,0 +1,59 @@
package de.markusfisch.android.binaryeye.actions.search
import android.content.Context
import android.content.Intent
import android.widget.Toast
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.IAction
import de.markusfisch.android.binaryeye.app.alertDialog
import de.markusfisch.android.binaryeye.app.execShareIntent
import de.markusfisch.android.binaryeye.app.parseAndNormalizeUri
import de.markusfisch.android.binaryeye.app.prefs
import java.net.URLEncoder
object OpenOrSearchAction : IAction {
override val iconResId: Int = R.drawable.ic_action_search
override val titleResId: Int = R.string.search_web
override fun canExecuteOn(data: ByteArray): Boolean = false
override suspend fun execute(context: Context, data: ByteArray) {
val intent = openUri(context, String(data)) ?: return
execShareIntent(context, intent)
}
private suspend fun openUri(context: Context, data: String, search: Boolean = true): Intent? {
val uri = parseAndNormalizeUri(data)
val intent = Intent(Intent.ACTION_VIEW, uri)
when {
intent.resolveActivity(context.packageManager) != null -> return intent
search -> return getSearchIntent(context, data)
else -> Toast.makeText(
context,
R.string.cannot_resolve_action,
Toast.LENGTH_SHORT
).show()
}
return null
}
private suspend fun getSearchIntent(context: Context, query: String): Intent? {
val names = context.resources.getStringArray(
R.array.search_engines_names
).toMutableList()
val urls = context.resources.getStringArray(
R.array.search_engines_values
).toMutableList()
if (prefs.openWithUrl.isNotEmpty()) {
names.add(prefs.openWithUrl)
urls.add(prefs.openWithUrl)
}
val queryUri = alertDialog<String>(context) { resume ->
setTitle(R.string.pick_search_engine)
setItems(names.toTypedArray()) { _, which ->
resume(urls[which] + URLEncoder.encode(query, "utf-8"))
}
} ?: return null
return openUri(context, queryUri, false)
}
}
@@ -4,9 +4,9 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
import de.markusfisch.android.binaryeye.actions.IntentAction
object SmsAction : SimpleIntentIAction() {
object SmsAction : IntentAction() {
private val smsRegex =
"""^sms(?:to)?:(\+?[0-9]+)(?::([\S\s]*))?$""".toRegex(RegexOption.IGNORE_CASE)
@@ -18,7 +18,7 @@ object SmsAction : SimpleIntentIAction() {
return String(data).matches(smsRegex)
}
override fun executeForIntent(context: Context, data: ByteArray): Intent? {
override suspend fun createIntent(context: Context, data: ByteArray): Intent? {
val (number: String, message: String) = smsRegex.matchEntire(
String(data)
)?.let {
@@ -2,11 +2,11 @@ package de.markusfisch.android.binaryeye.actions.tel
import android.content.Context
import android.content.Intent
import android.net.Uri
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
import de.markusfisch.android.binaryeye.actions.IntentAction
import de.markusfisch.android.binaryeye.app.parseAndNormalizeUri
object TelAction : SimpleIntentIAction() {
object TelAction : IntentAction() {
private val telRegex = """^tel:(\+?[0-9]+)$""".toRegex(RegexOption.IGNORE_CASE)
override val iconResId: Int = R.drawable.ic_action_tel
@@ -17,7 +17,7 @@ object TelAction : SimpleIntentIAction() {
return String(data).matches(telRegex)
}
override fun executeForIntent(context: Context, data: ByteArray): Intent {
return Intent(Intent.ACTION_DIAL, Uri.parse(String(data)))
override suspend fun createIntent(context: Context, data: ByteArray): Intent {
return Intent(Intent.ACTION_DIAL, parseAndNormalizeUri(String(data)))
}
}
@@ -1,19 +1,24 @@
package de.markusfisch.android.binaryeye.actions.vtype
import java.util.*
object VTypeParser {
private const val BACKSLASH_R_LEGACY = "(?:\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])"
private const val BACKSLASH_R_LEGACY =
"(?:\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])"
private val vTypeRegex =
"""^BEGIN:(VCARD|VEVENT)(?:$BACKSLASH_R_LEGACY.+?:[\s\S]+?)+?${BACKSLASH_R_LEGACY}END:\1$BACKSLASH_R_LEGACY?$""".toRegex(RegexOption.IGNORE_CASE)
"""^BEGIN:(VCARD|VEVENT)(?:$BACKSLASH_R_LEGACY.+?:[\s\S]+?)+?${BACKSLASH_R_LEGACY}END:\1$BACKSLASH_R_LEGACY?$""".toRegex(
RegexOption.IGNORE_CASE
)
private val propertyRegex = """^(.+?):([\s\S]*?)$""".toRegex(RegexOption.MULTILINE)
fun parseVType(data: String): String? =
vTypeRegex.matchEntire(data)?.groupValues?.get(1)?.toUpperCase()
vTypeRegex.matchEntire(data)?.groupValues?.get(1)?.toUpperCase(Locale.US)
fun parseMap(data: String): Map<String, List<VTypeProperty>> = propertyRegex.findAll(data).map {
it.groupValues[1].split(';').let { typeAndInfo ->
typeAndInfo[0] to VTypeProperty(typeAndInfo.drop(1), it.groupValues[2])
}
}.groupBy({ it.first.toUpperCase() }) { it.second }
}.groupBy({ it.first.toUpperCase(Locale.US) }) { it.second }
}
data class VTypeProperty(
@@ -6,10 +6,12 @@ import android.content.Intent
import android.os.Build
import android.provider.ContactsContract
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
import de.markusfisch.android.binaryeye.actions.IntentAction
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
import java.util.*
import kotlin.collections.ArrayList
object VCardAction : SimpleIntentIAction() {
object VCardAction : IntentAction() {
override val iconResId: Int
get() = R.drawable.ic_action_vcard
override val titleResId: Int
@@ -21,7 +23,7 @@ object VCardAction : SimpleIntentIAction() {
return VTypeParser.parseVType(String(data)) == "VCARD"
}
override fun executeForIntent(context: Context, data: ByteArray): Intent? {
override suspend fun createIntent(context: Context, data: ByteArray): Intent? {
val info = VTypeParser.parseMap(String(data))
return Intent(Intent.ACTION_INSERT_OR_EDIT).apply {
@@ -46,7 +48,10 @@ object VCardAction : SimpleIntentIAction() {
putExtra(extraPhoneType, phoneType)
} ?: putExtra(extraPhoneType, it)
}
putExtra(extraPhone, phoneProperty.value.toLowerCase().removePrefix("tel:"))
putExtra(
extraPhone,
phoneProperty.value.toLowerCase(Locale.US).removePrefix("tel:")
)
}
info["EMAIL"]?.forEachIndexed { index, mailProperty ->
val (extraMailType, extraMail) = when (index) {
@@ -135,7 +140,7 @@ object VCardAction : SimpleIntentIAction() {
} ?: this
private val String.mailType: Int?
get() = when (this.toUpperCase()) {
get() = when (this.toUpperCase(Locale.US)) {
"HOME" -> ContactsContract.CommonDataKinds.Email.TYPE_HOME
"WORK" -> ContactsContract.CommonDataKinds.Email.TYPE_WORK
"OTHER" -> ContactsContract.CommonDataKinds.Email.TYPE_OTHER
@@ -144,7 +149,7 @@ object VCardAction : SimpleIntentIAction() {
}
private val String.phoneType: Int?
get() = when (this.toUpperCase()) {
get() = when (this.toUpperCase(Locale.US)) {
"HOME" -> ContactsContract.CommonDataKinds.Phone.TYPE_HOME
"MOBILE", "CELL" -> ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE
"WORK" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK
@@ -169,7 +174,7 @@ object VCardAction : SimpleIntentIAction() {
}
private val String.addressType: Int?
get() = when (this.toUpperCase()) {
get() = when (this.toUpperCase(Locale.US)) {
"HOME" -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME
"WORK" -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK
"OTHER" -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER
@@ -7,12 +7,12 @@ import android.os.Build
import android.provider.CalendarContract
import android.support.annotation.RequiresApi
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
import de.markusfisch.android.binaryeye.actions.IntentAction
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
import java.text.SimpleDateFormat
import java.util.*
object VEventAction : SimpleIntentIAction() {
object VEventAction : IntentAction() {
override val iconResId: Int
get() = R.drawable.ic_action_vevent
override val titleResId: Int
@@ -26,7 +26,7 @@ object VEventAction : SimpleIntentIAction() {
}
@RequiresApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
override fun executeForIntent(context: Context, data: ByteArray): Intent? {
override suspend fun createIntent(context: Context, data: ByteArray): Intent? {
val info = VTypeParser.parseMap(String(data))
return Intent(Intent.ACTION_EDIT).apply {
@@ -0,0 +1,11 @@
package de.markusfisch.android.binaryeye.actions.web
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SchemeAction
object WebAction : SchemeAction() {
override val iconResId: Int = R.drawable.ic_action_open
override val titleResId: Int = R.string.open_url
override val scheme: String = "https?"
override val buildRegex: Boolean = true
}
@@ -7,7 +7,10 @@ import android.net.wifi.WifiManager
import android.widget.Toast
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.IAction
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
object WifiAction : IAction {
override val iconResId = R.drawable.ic_action_wifi
@@ -16,31 +19,29 @@ object WifiAction : IAction {
override fun canExecuteOn(data: ByteArray): Boolean =
WifiConfigurationFactory.parse(String(data)) != null
override fun execute(context: Context, data: ByteArray) {
CoroutineScope(Dispatchers.IO).launch {
val wifiConfig = WifiConfigurationFactory.parse(
String(data)
) ?: return@launch
val wifiManager = context.applicationContext.getSystemService(
WIFI_SERVICE
) as WifiManager
override suspend fun execute(context: Context, data: ByteArray) = withContext(Dispatchers.IO) {
val wifiConfig = WifiConfigurationFactory.parse(
String(data)
) ?: return@withContext
val wifiManager = context.applicationContext.getSystemService(
WIFI_SERVICE
) as WifiManager
wifiManager.enableWifi(context)
wifiManager.mayRemoveOldNetwork(wifiConfig)
wifiManager.enableNewNetwork(wifiConfig)
withContext(Dispatchers.Main) {
Toast.makeText(
context,
R.string.wifi_added,
Toast.LENGTH_LONG
).show()
}
wifiManager.enableWifi(context)
wifiManager.mayRemoveOldNetwork(wifiConfig)
wifiManager.enableNewNetwork(wifiConfig)
withContext(Dispatchers.Main) {
Toast.makeText(
context,
R.string.wifi_added,
Toast.LENGTH_LONG
).show()
}
}
private suspend fun WifiManager.enableWifi(context: Context): Boolean {
if (!this.isWifiEnabled) {
if (!this.setWifiEnabled(true)) {
if (!isWifiEnabled) {
if (!setWifiEnabled(true)) {
withContext(Dispatchers.Main) {
Toast.makeText(
context,
@@ -50,20 +51,18 @@ object WifiAction : IAction {
}
return false
}
var i = 0
while (!this.isWifiEnabled) {
if (i >= 10) {
withContext(Dispatchers.Main) {
Toast.makeText(
context,
R.string.wifi_config_failed,
Toast.LENGTH_LONG
).show()
}
return false
// wait for wifi to really be enabled, takes some time
withTimeoutOrNull(10000) {
while (!isWifiEnabled) delay(500)
} ?: if (!isWifiEnabled) {
withContext(Dispatchers.Main) {
Toast.makeText(
context,
R.string.wifi_config_failed,
Toast.LENGTH_LONG
).show()
}
delay(1000)
i++
return false
}
}
return true
@@ -3,6 +3,7 @@ package de.markusfisch.android.binaryeye.actions.wifi
import android.net.wifi.WifiConfiguration
import android.net.wifi.WifiEnterpriseConfig
import android.os.Build
import java.util.*
/**
* Normal:
@@ -47,7 +48,7 @@ object WifiConfigurationFactory {
string
)?.groupValues?.get(1)?.let { pairs ->
pairRegex.findAll(pairs).map { pair ->
pair.groupValues[1].toUpperCase() to pair.groupValues[2]
pair.groupValues[1].toUpperCase(Locale.US) to pair.groupValues[2]
}.toMap()
}
}
@@ -1,15 +1,13 @@
package de.markusfisch.android.binaryeye.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.hardware.Camera
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.os.Vibrator
import android.provider.MediaStore
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.Menu
@@ -29,7 +27,6 @@ import de.markusfisch.android.binaryeye.zxing.Zxing
import de.markusfisch.android.cameraview.widget.CameraView
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.IOException
class CameraActivity : AppCompatActivity() {
private val zxing = Zxing()
@@ -65,6 +62,23 @@ class CameraActivity : AppCompatActivity() {
}
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
resultData: Intent?
) {
when (requestCode) {
PICK_FILE_RESULT_CODE -> {
if (resultCode == Activity.RESULT_OK && resultData != null) {
val pick = Intent(this, PickActivity::class.java)
pick.action = Intent.ACTION_VIEW
pick.setDataAndType(resultData.data, "image/*")
startActivity(pick)
}
}
}
}
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setContentView(R.layout.activity_camera)
@@ -83,12 +97,10 @@ class CameraActivity : AppCompatActivity() {
initZoomBar()
restoreZoom()
if (intent?.action == Intent.ACTION_SEND) {
if (intent.type == "text/plain") {
handleSendText(intent)
} else if (intent.type?.startsWith("image/") == true) {
handleSendImage(intent)
}
if (intent?.action == Intent.ACTION_SEND &&
intent.type == "text/plain"
) {
handleSendText(intent)
}
}
@@ -156,6 +168,18 @@ class CameraActivity : AppCompatActivity() {
startActivity(MainActivity.getHistoryIntent(this))
true
}
R.id.pick_file -> {
val chooseFile = Intent(Intent.ACTION_GET_CONTENT)
chooseFile.type = "image/*"
startActivityForResult(
Intent.createChooser(
chooseFile,
getString(R.string.pick_file)
),
PICK_FILE_RESULT_CODE
)
true
}
R.id.switch_camera -> {
switchCamera()
true
@@ -196,35 +220,6 @@ class CameraActivity : AppCompatActivity() {
}
}
private fun handleSendImage(intent: Intent) {
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri ?: return
val bitmap = try {
MediaStore.Images.Media.getBitmap(contentResolver, uri)
} catch (e: IOException) {
null
}
if (bitmap == null) {
Toast.makeText(
this,
R.string.error_no_content,
Toast.LENGTH_SHORT
).show()
return
}
val result = zxing.decodePositiveNegative(downsizeIfBigger(bitmap, 1024))
if (result != null) {
showResult(result)
finish()
return
}
Toast.makeText(
this,
R.string.no_barcode_found,
Toast.LENGTH_SHORT
).show()
finish()
}
private fun initCameraView() {
cameraView.setUseOrientationListener(true)
cameraView.setTapToFocus()
@@ -281,7 +276,11 @@ class CameraActivity : AppCompatActivity() {
result?.let {
cameraView.post {
vibrator.vibrate(100)
showResult(result)
showResult(
this@CameraActivity,
result,
returnResult
)
}
decoding = false
}
@@ -393,61 +392,42 @@ class CameraActivity : AppCompatActivity() {
)
}
private fun showResult(result: Result) {
val rawBytes = getRawBytes(result)
if (prefs.useHistory) {
GlobalScope.launch {
db.insertScan(
System.currentTimeMillis(),
result.text,
rawBytes,
result.barcodeFormat.toString()
)
}
}
if (returnResult) {
setResult(RESULT_OK, getReturnIntent(result))
finish()
return
}
startActivity(
MainActivity.getDecodeIntent(
this,
result.text,
result.barcodeFormat,
rawBytes
)
)
}
companion object {
private const val REQUEST_CAMERA = 1
private const val PICK_FILE_RESULT_CODE = 1
private const val ZOOM_MAX = "zoom_max"
private const val ZOOM_LEVEL = "zoom_level"
}
}
fun downsizeIfBigger(bitmap: Bitmap, max: Int): Bitmap {
return if (Math.max(bitmap.width, bitmap.height) > max) {
var width: Int = max
var height: Int = max
if (bitmap.width > bitmap.height) {
height = Math.round(
width.toFloat() / bitmap.width.toFloat() *
bitmap.height.toFloat()
)
} else {
width = Math.round(
height.toFloat() / bitmap.height.toFloat() *
bitmap.width.toFloat()
fun showResult(
activity: Activity,
result: Result,
isResult: Boolean = false
) {
val rawBytes = getRawBytes(result)
if (prefs.useHistory) {
GlobalScope.launch {
db.insertScan(
System.currentTimeMillis(),
result.text,
rawBytes,
result.barcodeFormat.toString()
)
}
Bitmap.createScaledBitmap(bitmap, width, height, true)
}
if (isResult) {
activity.setResult(Activity.RESULT_OK, getReturnIntent(result))
activity.finish()
} else {
bitmap
activity.startActivity(
MainActivity.getDecodeIntent(
activity,
result.text,
result.barcodeFormat,
rawBytes
)
)
}
}
@@ -8,8 +8,10 @@ import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.colorSystemAndToolBars
import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.setFragment
import de.markusfisch.android.binaryeye.app.setupInsets
import de.markusfisch.android.binaryeye.fragment.DecodeFragment
import de.markusfisch.android.binaryeye.fragment.EncodeFragment
import de.markusfisch.android.binaryeye.fragment.HistoryFragment
@@ -29,11 +31,17 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setContentView(R.layout.activity_main)
initSystemBars(this)
setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)
initSystemBars(this)
val toolbar = findViewById(R.id.toolbar) as Toolbar
setupInsets(findViewById(android.R.id.content), toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportFragmentManager.addOnBackStackChangedListener {
colorSystemAndToolBars(this@MainActivity)
}
if (state == null) {
setFragment(
supportFragmentManager, when {
@@ -43,11 +51,11 @@ class MainActivity : AppCompatActivity() {
HistoryFragment()
intent?.hasExtra(ENCODE) == true ->
EncodeFragment.newInstance(
intent.getStringExtra(ENCODE)
intent.getStringExtra(ENCODE) ?: ""
)
intent?.hasExtra(DECODED_TEXT) == true ->
DecodeFragment.newInstance(
intent.getStringExtra(DECODED_TEXT),
intent.getStringExtra(DECODED_TEXT) ?: "",
intent.getSerializableExtra(
DECODED_FORMAT
) as BarcodeFormat,
@@ -0,0 +1,175 @@
package de.markusfisch.android.binaryeye.activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.provider.MediaStore
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import com.google.zxing.Result
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.colorSystemAndToolBars
import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.setWindowInsetListener
import de.markusfisch.android.binaryeye.app.setupInsets
import de.markusfisch.android.binaryeye.graphics.crop
import de.markusfisch.android.binaryeye.graphics.downsizeIfBigger
import de.markusfisch.android.binaryeye.widget.CropImageView
import de.markusfisch.android.binaryeye.zxing.Zxing
import java.io.IOException
import kotlin.math.max
import kotlin.math.min
class PickActivity : AppCompatActivity() {
private val zxing = Zxing()
private lateinit var cropImageView: CropImageView
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setContentView(R.layout.activity_pick)
initSystemBars(this)
val toolbar = findViewById(R.id.toolbar) as Toolbar
setupInsets(findViewById(android.R.id.content), toolbar)
setSupportActionBar(toolbar)
supportFragmentManager.addOnBackStackChangedListener {
colorSystemAndToolBars(this@PickActivity)
}
val bitmap = if (
intent?.action == Intent.ACTION_SEND &&
intent.type?.startsWith("image/") == true
) {
loadSentImage(intent)
} else if (
intent?.action == Intent.ACTION_VIEW &&
intent.type?.startsWith("image/") == true
) {
loadImageToOpen(intent)
} else {
null
}
if (bitmap == null) {
Toast.makeText(
applicationContext,
R.string.error_no_content,
Toast.LENGTH_SHORT
).show()
finish()
return
}
val scannedRect = Rect()
val scanBounds: () -> Result? = {
var result: Result? = null
crop(
bitmap,
cropImageView.normalizedRectInBounds,
cropImageView.imageRotation
)?.also {
scannedRect.set(0, 0, it.width, it.height)
result = zxing.decodePositiveNegative(it)
}
result
}
cropImageView = findViewById(R.id.image) as CropImageView
cropImageView.setImageBitmap(bitmap)
cropImageView.onScan = {
val rect = Rect()
scanBounds()?.also {
rect.left = Int.MAX_VALUE
rect.top = Int.MAX_VALUE
for (rp in it.resultPoints) {
val x = rp.x.toInt()
val y = rp.y.toInt()
rect.left = min(rect.left, x)
rect.right = max(rect.right, x)
rect.top = min(rect.top, y)
rect.bottom = max(rect.bottom, y)
}
// map result points onto view
val bounds = cropImageView.getBoundsRect()
val fx = bounds.width() / scannedRect.width().toFloat()
val fy = bounds.height() / scannedRect.height().toFloat()
rect.set(
(rect.left * fx).toInt(),
(rect.top * fy).toInt(),
(rect.right * fx).toInt(),
(rect.bottom * fx).toInt()
)
rect.offset(bounds.left.toInt(), bounds.top.toInt())
}
rect
}
setWindowInsetListener { insets ->
cropImageView.windowInsets.set(insets)
}
findViewById(R.id.scan).setOnClickListener {
scanImage(scanBounds())
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.activity_pick, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.rotate -> {
rotateClockwise()
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun rotateClockwise() {
cropImageView.imageRotation = cropImageView.imageRotation + 90 % 360
}
private fun loadSentImage(intent: Intent): Bitmap? {
val uri = intent.getParcelableExtra<Parcelable>(
Intent.EXTRA_STREAM
) as? Uri ?: return null
return loadImageUri(uri)
}
private fun loadImageToOpen(intent: Intent): Bitmap? {
val uri = intent.data ?: return null
return loadImageUri(uri)
}
private fun loadImageUri(uri: Uri): Bitmap? = try {
downsizeIfBigger(
MediaStore.Images.Media.getBitmap(contentResolver, uri),
1024
)
} catch (e: IOException) {
null
}
private fun scanImage(result: Result?) {
if (result != null) {
showResult(this, result)
finish()
return
}
Toast.makeText(
applicationContext,
R.string.no_barcode_found,
Toast.LENGTH_SHORT
).show()
}
}
@@ -2,10 +2,10 @@ package de.markusfisch.android.binaryeye.app
import android.app.Application
import android.support.v8.renderscript.RenderScript
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.preference.Preferences
import de.markusfisch.android.binaryeye.repository.DatabaseRepository
val db = Database()
val db = DatabaseRepository()
val prefs = Preferences()
class BinaryEyeApp : Application() {
@@ -0,0 +1,62 @@
package de.markusfisch.android.binaryeye.app
import android.annotation.SuppressLint
import android.content.Context
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v7.app.AlertDialog
import android.view.View
import de.markusfisch.android.binaryeye.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
fun setFragment(fm: FragmentManager?, fragment: Fragment) {
fm?.let { getTransaction(fm, fragment).commit() }
}
fun addFragment(fm: FragmentManager?, fragment: Fragment) {
fm?.let { getTransaction(fm, fragment).addToBackStack(null).commit() }
}
@SuppressLint("CommitTransaction")
private fun getTransaction(
fm: FragmentManager,
fragment: Fragment
) = fm.beginTransaction().replace(R.id.content_frame, fragment)
suspend inline fun View.useVisibility(
whileExecuting: Int = View.VISIBLE,
otherwise: Int = View.GONE,
crossinline block: suspend () -> Unit
) {
if (visibility == whileExecuting) {
return
}
withContext(Dispatchers.Main) {
visibility = whileExecuting
}
try {
block()
} finally {
withContext(Dispatchers.Main) {
visibility = otherwise
}
}
}
suspend inline fun <T : Any> alertDialog(
context: Context,
crossinline build: AlertDialog.Builder.(resume: (T?) -> Unit) -> Unit
): T? = withContext(Dispatchers.Main) {
suspendCoroutine<T?> { continuation ->
AlertDialog.Builder(context).apply {
setOnCancelListener {
continuation.resume(null)
}
build(continuation::resume)
show()
}
}
}
@@ -1,23 +0,0 @@
package de.markusfisch.android.binaryeye.app
import android.annotation.SuppressLint
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentTransaction
import de.markusfisch.android.binaryeye.R
fun setFragment(fm: FragmentManager?, fragment: Fragment) {
fm?.let { getTransaction(fm, fragment).commit() }
}
fun addFragment(fm: FragmentManager?, fragment: Fragment) {
fm?.let { getTransaction(fm, fragment).addToBackStack(null).commit() }
}
@SuppressLint("CommitTransaction")
private fun getTransaction(
fm: FragmentManager,
fragment: Fragment
): FragmentTransaction {
return fm.beginTransaction().replace(R.id.content_frame, fragment)
}
@@ -1,7 +1,12 @@
package de.markusfisch.android.binaryeye.app
import java.util.regex.Pattern
import android.net.Uri
import android.os.Build
private val nonPrintable = Pattern.compile("[\\x00-\\x08\\x0e-\\x1f]")
private val nonPrintable = "[\\x00-\\x08\\x0e-\\x1f]".toRegex()
fun hasNonPrintableCharacters(s: String) = nonPrintable.matcher(s).find()
fun hasNonPrintableCharacters(s: String) = nonPrintable.containsMatchIn(s)
fun parseAndNormalizeUri(input: String): Uri = Uri.parse(input).let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) it.normalizeScheme() else it
}
@@ -0,0 +1,57 @@
package de.markusfisch.android.binaryeye.app
import android.app.Activity
import android.content.Context
import android.os.Environment
import android.support.annotation.MainThread
import android.widget.EditText
import de.markusfisch.android.binaryeye.R
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import java.io.File
import java.io.IOException
fun addSuffixIfNotGiven(name: String, suffix: String): String {
val trimmed = name.trim()
return if (suffix.isNotEmpty() && !trimmed.endsWith(suffix)) {
"$trimmed$suffix"
} else {
trimmed
}
}
// Dialogs doesn't have any root layout, need to be inflated with null root
@Suppress("InflateParams")
@MainThread
suspend fun Activity.askForFileName(suffix: String = ""): String? {
val view = layoutInflater.inflate(R.layout.dialog_save_file, null)
val editText = view.findViewById<EditText>(R.id.file_name)
return alertDialog<String>(this as Context) { resume ->
setView(view)
setPositiveButton(android.R.string.ok) { _, _ ->
resume(editText.text.toString())
}
}?.let { name ->
addSuffixIfNotGiven(name, suffix)
}
}
suspend fun Flow<ByteArray>.writeToFile(name: String): Int {
return try {
val file = File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS
),
name
)
if (file.exists()) {
return R.string.error_file_exists
}
collect { bytes ->
file.appendBytes(bytes)
}
R.string.saved_in_downloads
} catch (e: IOException) {
R.string.error_saving_binary_data
}
}
@@ -0,0 +1,23 @@
package de.markusfisch.android.binaryeye.app
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.scanReduce
fun Flow<ByteArray>.addDelimiter(delimiter: String) = addDelimiter(
delimiter.toByteArray(),
ByteArray::plus
)
inline fun <T, R> Flow<T>.addDelimiter(
delimiter: R,
crossinline append: R.(T) -> T
): Flow<T> = scanReduce { _, content ->
delimiter.append(content)
}
infix fun <T> T.asFlowWith(other: Flow<T>) = flow<T> {
emit(this@asFlowWith)
emitAll(other)
}
@@ -1,45 +1,113 @@
package de.markusfisch.android.binaryeye.app
import android.content.Context
import android.content.ContextWrapper
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.View
import android.view.Window
import android.widget.AbsListView
import de.markusfisch.android.binaryeye.R
fun initSystemBars(activity: AppCompatActivity?) {
val view = activity?.findViewById(R.id.main_layout) ?: return
setWindowInsetListener(view)
setSystemBarColor(
activity.window,
ContextCompat.getColor(activity, android.R.color.transparent)
)
}
fun setWindowInsetListener(view: View) {
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
if (insets.hasSystemWindowInsets()) {
view.setPadding(
insets.getSystemWindowInsetLeft(),
insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom()
)
val systemBarScrollListener = object : AbsListView.OnScrollListener {
override fun onScroll(
view: AbsListView,
firstVisibleItem: Int,
visibleItemCount: Int,
totalItemCount: Int
) {
// give Android some time to settle down before running this;
// not putting it on the queue makes it only work sometimes
view.post {
val scrolled = firstVisibleItem > 0 ||
(totalItemCount > 0 && view.getChildAt(0).top < 0)
val scrollable = if (scrolled) true else totalItemCount > 0 &&
view.getChildAt(view.lastVisiblePosition).bottom >= view.height
colorSystemAndToolBars(view.context, scrolled, scrollable)
}
insets.consumeSystemWindowInsets()
}
override fun onScrollStateChanged(
view: AbsListView,
scrollState: Int
) {
}
}
fun setSystemBarColor(window: Window, color: Int): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return false
fun initSystemBars(activity: AppCompatActivity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
colorSystemAndToolBars(activity)
}
fun colorSystemAndToolBars(
context: Context,
scrolled: Boolean = false,
scrollable: Boolean = false
) {
val translucentColor = getTranslucentPrimaryColor(context)
val topColor = if (scrolled) translucentColor else 0
val bottomColor = if (scrolled || scrollable) translucentColor else 0
val activity = getAppCompatActivity(context) ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window = activity.window
window.statusBarColor = topColor
window.navigationBarColor = bottomColor
}
activity.supportActionBar?.setBackgroundDrawable(ColorDrawable(topColor))
}
private fun getTranslucentPrimaryColor(context: Context) = ContextCompat.getColor(
context,
R.color.primary
) and 0xffffff or 0xd8000000.toInt()
private fun getAppCompatActivity(context: Context): AppCompatActivity? {
var ctx = context
while (ctx is ContextWrapper) {
if (ctx is AppCompatActivity) {
return ctx
}
ctx = ctx.baseContext ?: break
}
return null
}
private var windowInsetsListener: ((insets: Rect) -> Unit)? = null
fun setWindowInsetListener(listener: (insets: Rect) -> Unit) {
if (windowInsets.top > 0) {
listener(windowInsets)
} else {
windowInsetsListener = listener
}
}
private val windowInsets = Rect()
fun setupInsets(view: View, toolbar: Toolbar) {
val toolBarHeight = toolbar.layoutParams.height
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
windowInsets.set(0, toolBarHeight, 0, 0)
windowInsetsListener?.also { it(windowInsets) }
return
}
ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets ->
if (insets.hasSystemWindowInsets()) {
windowInsets.set(
insets.systemWindowInsetLeft,
insets.systemWindowInsetTop + toolBarHeight,
insets.systemWindowInsetRight,
insets.systemWindowInsetBottom
)
windowInsetsListener?.also { it(windowInsets) }
}
insets
}
window.statusBarColor = color
window.navigationBarColor = color
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
return true
}
@@ -37,6 +37,15 @@ class Database {
""", arrayOf("$id")
)
fun hasBinaryData(): Cursor? = db.rawQuery(
"""SELECT
1
FROM $SCANS
WHERE $SCANS_RAW IS NOT NULL
LIMIT 1
""", null
)
fun insertScan(
timestamp: Long,
content: String,
@@ -0,0 +1,80 @@
package de.markusfisch.android.binaryeye.data.csv
import de.markusfisch.android.binaryeye.app.addDelimiter
import de.markusfisch.android.binaryeye.app.asFlowWith
import kotlinx.coroutines.flow.*
fun <T> csvBuilder(config: CSVBuilder<T>.() -> Unit): CSVBuilder<T> {
return CSVBuilder<T>().apply(config)
}
class CSVBuilder<T> {
private val columns = mutableListOf<ColumnBuilder>()
fun column(config: ColumnBuilder.() -> Unit): ColumnBuilder {
return ColumnBuilder().apply(config).also {
columns.add(it)
}
}
inner class ColumnBuilder {
var isBinary = false
var name: String? = null
internal var gettingBy: ((T) -> ByteArray)? = null
fun gettingBy(getter: (T) -> ByteArray) {
gettingBy = getter
}
fun gettingByString(getter: (T) -> String): Unit = gettingBy {
getter(it).toByteArray()
}
}
fun buildWith(inputs: Flow<T>, delimiter: String): Flow<ByteArray> = flow {
val columnsFlow = columns.asFlow()
val header = columnsFlow.buildHeader(delimiter)
val contentLines = inputs.buildContent(columnsFlow, delimiter)
emitAll(header)
emit("\n".toByteArray())
emitAll(contentLines)
}
private fun Flow<ColumnBuilder>.buildHeader(delimiter: String) = map {
it.name?.escaped(delimiter)?.toByteArray()
?: throw IllegalStateException("You need to provide a name for csv column")
}.addDelimiter(delimiter)
private fun Flow<T>.buildContent(
columnsFlow: Flow<ColumnBuilder>,
delimiter: String
): Flow<ByteArray> = map { input ->
input.getLine(columnsFlow, delimiter)
}.addDelimiter("\n".toByteArray()) { other: Flow<ByteArray> ->
// this: ByteArray
this asFlowWith other
}.flattenConcat()
private fun T.getLine(columnsFlow: Flow<ColumnBuilder>, delimiter: String): Flow<ByteArray> =
columnsFlow.map { column ->
column.gettingBy?.run {
var result = invoke(this@getLine)
if (!column.isBinary) {
result = String(result).escaped(delimiter).toByteArray()
}
return@run result
} ?: throw IllegalStateException("You need to provide a getter for the value")
}.addDelimiter(delimiter)
private fun Any.escaped(delimiter: String): String {
val any = this.toString()
return when {
any.run { contains('"') || contains('\n') || contains(delimiter) } ->
"\"${any.replace("\"", "\"\"").replace(delimiter, "\"$delimiter")}\""
else -> any
}
}
}
@@ -1,16 +1,22 @@
package de.markusfisch.android.binaryeye.fragment
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.graphics.Bitmap
import android.os.Bundle
import android.os.Environment
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.view.*
import android.widget.EditText
import android.widget.Toast
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.addSuffixIfNotGiven
import de.markusfisch.android.binaryeye.app.hasWritePermission
import de.markusfisch.android.binaryeye.app.setWindowInsetListener
import de.markusfisch.android.binaryeye.app.shareFile
import de.markusfisch.android.binaryeye.view.setPadding
import de.markusfisch.android.binaryeye.widget.ConfinedScalingImageView
import de.markusfisch.android.binaryeye.zxing.Zxing
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
@@ -18,14 +24,24 @@ import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.*
class BarcodeFragment : Fragment() {
private var barcode: Bitmap? = null
private var content: String = ""
private var format: BarcodeFormat? = null
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?
): View? {
activity.setTitle(R.string.view_barcode)
activity?.setTitle(R.string.view_barcode)
val view = inflater.inflate(
R.layout.fragment_barcode,
@@ -33,68 +49,138 @@ class BarcodeFragment : Fragment() {
false
)
val args = arguments
args?.let {
val content = args.getString(CONTENT)
val format = args.getSerializable(FORMAT) as BarcodeFormat?
if (content != null && format != null) {
val size = args.getInt(SIZE)
val bitmap: Bitmap?
try {
bitmap = Zxing.encodeAsBitmap(
content,
format,
size,
size
)
} catch (e: Exception) {
var message = e.message
if (message == null || message.isEmpty()) {
message = getString(R.string.error_encoding_barcode)
}
Toast.makeText(
activity,
message,
Toast.LENGTH_SHORT
).show()
fragmentManager.popBackStack()
return null
}
view.findViewById<ImageView>(R.id.barcode).setImageBitmap(bitmap)
view.findViewById<View>(R.id.share).setOnClickListener {
bitmap?.let {
share(bitmap)
}
}
val args = arguments ?: return view
val content = args.getString(CONTENT) ?: return view
val format = args.getSerializable(FORMAT) as BarcodeFormat? ?: return view
val size = args.getInt(SIZE)
try {
barcode = Zxing.encodeAsBitmap(content, format, size, size)
} catch (e: Exception) {
var message = e.message
if (message == null || message.isEmpty()) {
message = getString(R.string.error_encoding_barcode)
}
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
fragmentManager.popBackStack()
return null
}
this.content = content
this.format = format
val imageView = view.findViewById<ConfinedScalingImageView>(
R.id.barcode
)
imageView.setImageBitmap(barcode)
imageView.post {
// make sure to invoke this after ScalingImageView.onLayout()
imageView.minWidth /= 2f
}
view.findViewById<View>(R.id.share).setOnClickListener {
val bitmap = barcode
bitmap?.let {
share(bitmap)
}
}
setWindowInsetListener { insets ->
(view.findViewById(R.id.inset_layout) as View).setPadding(insets)
imageView.insets.set(insets)
}
return view
}
private fun share(bitmap: Bitmap) {
GlobalScope.launch {
val file = saveBitmap(bitmap)
GlobalScope.launch(Main) {
file?.let {
shareFile(context, file, "image/png")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.fragment_barcode, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.save -> {
askForFileNameAndSave()
true
}
else -> super.onOptionsItemSelected(item)
}
}
// dialogs do not have a parent view
@SuppressLint("InflateParams")
private fun askForFileNameAndSave() {
val ac = activity ?: return
val view = ac.layoutInflater.inflate(R.layout.dialog_save_file, null)
val editText = view.findViewById<EditText>(R.id.file_name)
editText.setText(encodeFileName("${format.toString()}_$content"))
AlertDialog.Builder(ac)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
val bitmap = barcode
bitmap?.let {
saveAsFile(
bitmap,
addSuffixIfNotGiven(
editText.text.toString(),
".png"
)
)
}
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
}
.show()
}
private fun saveAsFile(bitmap: Bitmap, path: String) {
val ac = activity ?: return
if (!hasWritePermission(ac)) {
return
}
GlobalScope.launch {
val success = saveBitmap(
bitmap,
File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS
),
path
)
)
GlobalScope.launch(Main) {
Toast.makeText(
ac,
if (success) {
R.string.saved_in_downloads
} else {
R.string.error_saving_binary_data
},
Toast.LENGTH_LONG
).show()
}
}
}
private fun saveBitmap(bitmap: Bitmap): File? {
return try {
private fun share(bitmap: Bitmap) {
GlobalScope.launch {
val file = File(
context.externalCacheDir,
"shared_barcode.png"
)
val fos = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos)
fos.close()
file
} catch (e: IOException) {
null
val success = saveBitmap(bitmap, file)
GlobalScope.launch(Main) {
if (success) {
shareFile(context, file, "image/png")
} else {
val ac = activity
ac?.let {
Toast.makeText(
ac,
R.string.error_saving_binary_data,
Toast.LENGTH_LONG
).show()
}
}
}
}
}
@@ -118,3 +204,18 @@ class BarcodeFragment : Fragment() {
}
}
}
private fun saveBitmap(bitmap: Bitmap, file: File): Boolean {
return try {
val fos = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos)
fos.close()
true
} catch (e: IOException) {
false
}
}
private val fileNameCharacters = "[^A-Za-z0-9]".toRegex()
private fun encodeFileName(name: String): String =
fileNameCharacters.replace(name, "_").take(16).trim('_').toLowerCase(Locale.getDefault())
@@ -1,43 +1,43 @@
package de.markusfisch.android.binaryeye.fragment
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.support.design.widget.FloatingActionButton
import android.support.v4.app.Fragment
import android.text.ClipboardManager
import android.text.Editable
import android.text.TextWatcher
import android.view.*
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.IAction
import de.markusfisch.android.binaryeye.actions.validateOrGetNew
import de.markusfisch.android.binaryeye.actions.ActionRegistry
import de.markusfisch.android.binaryeye.app.*
import java.io.File
import java.io.IOException
import java.net.URLEncoder
import de.markusfisch.android.binaryeye.view.setPadding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
class DecodeFragment : Fragment() {
private lateinit var contentView: EditText
private lateinit var formatView: TextView
private lateinit var hexView: TextView
private lateinit var format: BarcodeFormat
private lateinit var actionMenuItem: MenuItem
private lateinit var fab: FloatingActionButton
private var action: IAction? = null
private var action = ActionRegistry.DEFAULT_ACTION
private var isBinary = false
private val content: String
get() = contentView.text.toString()
private val parentJob = Job()
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main + parentJob)
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setHasOptionsMenu(true)
@@ -48,7 +48,7 @@ class DecodeFragment : Fragment() {
container: ViewGroup?,
state: Bundle?
): View {
activity.setTitle(R.string.content)
activity?.setTitle(R.string.content)
val view = inflater.inflate(
R.layout.fragment_decode,
@@ -62,7 +62,7 @@ class DecodeFragment : Fragment() {
format = arguments?.getSerializable(FORMAT) as BarcodeFormat? ?: BarcodeFormat.QR_CODE
contentView = view.findViewById(R.id.content)
val shareFab = view.findViewById<ImageView>(R.id.share)
fab = view.findViewById(R.id.open)
if (!isBinary) {
contentView.setText(inputContent)
@@ -87,14 +87,17 @@ class DecodeFragment : Fragment() {
) {
}
})
shareFab.setOnClickListener { v ->
shareText(v.context, content)
fab.setOnClickListener {
executeAction(content.toByteArray())
}
if (prefs.openImmediately) {
executeAction(content.toByteArray())
}
} else {
contentView.setText(R.string.binary_data)
contentView.isEnabled = false
shareFab.setImageResource(R.drawable.ic_action_save)
shareFab.setOnClickListener {
fab.setImageResource(R.drawable.ic_action_save)
fab.setOnClickListener {
askForFileNameAndSave(raw)
}
}
@@ -104,11 +107,19 @@ class DecodeFragment : Fragment() {
updateViewsAndAction(raw)
setWindowInsetListener { insets ->
(view.findViewById(R.id.inset_layout) as View).setPadding(insets)
(view.findViewById(R.id.scroll_view) as View).setPadding(insets)
}
return view
}
private fun updateViewsAndAction(bytes: ByteArray) {
action = action.validateOrGetNew(bytes)
val prevAction = action
if (!prevAction.canExecuteOn(bytes)) {
action = ActionRegistry.getAction(bytes)
}
formatView.text = resources.getQuantityString(
R.plurals.barcode_info,
bytes.size,
@@ -116,22 +127,27 @@ class DecodeFragment : Fragment() {
bytes.size
)
hexView.text = hexDump(bytes, 33)
if (::actionMenuItem.isInitialized) {
actionMenuItem.setIcon(action?.iconResId ?: R.drawable.ic_action_open)
actionMenuItem.setTitle(action?.titleResId ?: R.string.open_url)
if (prevAction !== action) {
fab.setImageResource(action.iconResId)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
fab.setOnLongClickListener { v ->
Toast.makeText(
v.context,
action.titleResId,
Toast.LENGTH_SHORT
).show()
true
}
} else {
fab.tooltipText = getString(action.titleResId)
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.fragment_decode, menu)
actionMenuItem = menu.findItem(R.id.open_url)
action?.also { action ->
actionMenuItem.setIcon(action.iconResId)
actionMenuItem.setTitle(action.titleResId)
}
if (isBinary) {
menu.findItem(R.id.copy_to_clipboard).isVisible = false
menu.findItem(R.id.open_url).isVisible = false
menu.findItem(R.id.create).isVisible = false
}
}
@@ -142,8 +158,8 @@ class DecodeFragment : Fragment() {
copyToClipboard(content)
true
}
R.id.open_url -> {
openUrl(content)
R.id.share -> {
context?.also { shareText(it, content) }
true
}
R.id.create -> {
@@ -171,82 +187,29 @@ class DecodeFragment : Fragment() {
).show()
}
private fun openUrl(
url: String,
executeCustomAction: Boolean = true,
searchIfNoUrl: Boolean = true
) {
if (activity == null || url.isEmpty()) {
return
private fun executeAction(content: ByteArray) {
if (activity != null && content.isNotEmpty()) scope.launch {
action.execute(activity, content)
}
var uri = Uri.parse(url)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
uri = uri.normalizeScheme()
}
val intent = Intent(Intent.ACTION_VIEW, uri)
when {
executeCustomAction && action != null -> action?.also { action ->
action.execute(activity, url.toByteArray())
}
intent.resolveActivity(activity.packageManager) != null -> {
startActivity(intent)
}
searchIfNoUrl -> pickSearchEngineAndSearch(activity, url)
else -> Toast.makeText(
activity,
R.string.cannot_resolve_action,
}
private fun askForFileNameAndSave(raw: ByteArray) {
val ac = activity ?: return
if (!hasWritePermission(ac)) return
scope.launch(Dispatchers.Main) {
val name = ac.askForFileName() ?: return@launch
val message = flowOf(raw).writeToFile(name)
Toast.makeText(
ac,
message,
Toast.LENGTH_SHORT
).show()
}
}
private fun pickSearchEngineAndSearch(context: Context, query: String) {
val names = context.resources.getStringArray(
R.array.search_engines_names
).toMutableList()
val urls = context.resources.getStringArray(
R.array.search_engines_values
).toMutableList()
if (prefs.openWithUrl.isNotEmpty()) {
names.add(prefs.openWithUrl)
urls.add(prefs.openWithUrl)
}
AlertDialog.Builder(context)
.setTitle(R.string.pick_search_engine)
.setItems(names.toTypedArray()) { _, which ->
openUrl(
urls[which] + URLEncoder.encode(query, "utf-8"),
executeCustomAction = false,
searchIfNoUrl = false
)
}
.show()
}
// dialogs don't have a parent layout and must therefore be
// inflated with a null root layout
@SuppressLint("InflateParams")
private fun askForFileNameAndSave(raw: ByteArray) {
val ac = activity ?: return
val view = ac.layoutInflater.inflate(R.layout.dialog_save_file, null)
val editText = view.findViewById<EditText>(R.id.file_name)
AlertDialog.Builder(ac)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
if (hasWritePermission(ac)) {
val messageId = saveByteArray(
editText.text.toString(),
raw
)
if (messageId > 0) {
Toast.makeText(
ac, messageId,
Toast.LENGTH_SHORT
).show()
}
}
}
.show()
override fun onDestroyView() {
parentJob.cancel()
super.onDestroyView()
}
companion object {
@@ -312,21 +275,3 @@ private fun hexDump(bytes: ByteArray, charsPerLine: Int): String {
}
return dump.toString()
}
fun saveByteArray(name: String, raw: ByteArray): Int {
return try {
val file = File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS
),
name
)
if (file.exists()) {
return R.string.error_file_exists
}
file.writeBytes(raw)
0
} catch (e: IOException) {
R.string.error_saving_binary_data
}
}
@@ -4,6 +4,9 @@ import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
@@ -11,11 +14,16 @@ import android.widget.*
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.app.setWindowInsetListener
import de.markusfisch.android.binaryeye.view.setPadding
class EncodeFragment : Fragment() {
private lateinit var formatView: Spinner
private lateinit var sizeView: TextView
private lateinit var sizeBarView: SeekBar
private lateinit var contentView: EditText
private val writers = arrayListOf(
BarcodeFormat.AZTEC,
BarcodeFormat.CODABAR,
@@ -30,12 +38,18 @@ class EncodeFragment : Fragment() {
BarcodeFormat.UPC_A
)
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?
): View {
activity.setTitle(R.string.compose_barcode)
): View? {
val ac = activity ?: return null
ac.setTitle(R.string.compose_barcode)
val view = inflater.inflate(
R.layout.fragment_encode,
@@ -45,7 +59,7 @@ class EncodeFragment : Fragment() {
formatView = view.findViewById(R.id.format)
formatView.adapter = ArrayAdapter<String>(
activity,
ac,
android.R.layout.simple_list_item_1,
writers.map { it.name }
)
@@ -54,43 +68,72 @@ class EncodeFragment : Fragment() {
sizeBarView = view.findViewById(R.id.size_bar)
initSizeBar()
val contentView = view.findViewById<EditText>(R.id.content)
contentView = view.findViewById<EditText>(R.id.content)
val args = arguments
args?.let {
contentView.setText(args.getString(CONTENT))
formatView.setSelection(
writers.indexOf(
args.getSerializable(FORMAT) as BarcodeFormat?
)
)
args?.also {
contentView.setText(it.getString(CONTENT))
}
view.findViewById<View>(R.id.encode).setOnClickListener { v ->
val format = writers[formatView.selectedItemPosition]
val size = getSize(sizeBarView.progress)
val content = contentView.text.toString()
if (content.isEmpty()) {
Toast.makeText(
v.context,
R.string.error_no_content,
Toast.LENGTH_SHORT
).show()
} else {
hideSoftKeyboard(contentView)
addFragment(
fragmentManager, BarcodeFragment.newInstance(
content,
format,
size
)
)
val barcodeFormat = args?.getSerializable(FORMAT) as BarcodeFormat?
if (barcodeFormat != null) {
formatView.setSelection(writers.indexOf(barcodeFormat))
} else if (state == null) {
formatView.post {
formatView.setSelection(prefs.indexOfLastSelectedFormat)
}
}
view.findViewById<View>(R.id.encode).setOnClickListener { v ->
encode()
}
setWindowInsetListener { insets ->
(view.findViewById(R.id.inset_layout) as View).setPadding(insets)
(view.findViewById(R.id.scroll_view) as View).setPadding(insets)
}
return view
}
override fun onPause() {
super.onPause()
prefs.indexOfLastSelectedFormat = formatView.selectedItemPosition
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.fragment_encode, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.next -> {
encode()
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun encode() {
val format = writers[formatView.selectedItemPosition]
val size = getSize(sizeBarView.progress)
val content = contentView.text.toString()
if (content.isEmpty()) {
Toast.makeText(
context,
R.string.error_no_content,
Toast.LENGTH_SHORT
).show()
} else {
hideSoftKeyboard(contentView)
addFragment(
fragmentManager,
BarcodeFragment.newInstance(content, format, size)
)
}
}
private fun initSizeBar() {
updateSize(sizeBarView.progress)
sizeBarView.setOnSeekBarChangeListener(
@@ -131,11 +174,13 @@ class EncodeFragment : Fragment() {
fun newInstance(
content: String,
format: BarcodeFormat = BarcodeFormat.AZTEC
format: BarcodeFormat? = null
): Fragment {
val args = Bundle()
args.putString(CONTENT, content)
args.putSerializable(FORMAT, format)
format?.let {
args.putSerializable(FORMAT, format)
}
val fragment = EncodeFragment()
fragment.arguments = args
return fragment
@@ -3,27 +3,34 @@ package de.markusfisch.android.binaryeye.fragment
import android.app.AlertDialog
import android.content.Context
import android.os.Bundle
import android.support.annotation.WorkerThread
import android.support.v4.app.Fragment
import android.support.v7.widget.SwitchCompat
import android.view.*
import android.widget.AbsListView
import android.widget.ListView
import android.widget.Toast
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.adapter.ScansAdapter
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.app.db
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.app.shareText
import de.markusfisch.android.binaryeye.data.Database
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import de.markusfisch.android.binaryeye.app.*
import de.markusfisch.android.binaryeye.data.csv.csvBuilder
import de.markusfisch.android.binaryeye.repository.DatabaseRepository
import de.markusfisch.android.binaryeye.view.setPadding
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.mapNotNull
class HistoryFragment : Fragment() {
private lateinit var listView: ListView
private lateinit var fab: View
private lateinit var progressView: View
private var scansAdapter: ScansAdapter? = null
private val parentJob = Job()
private val scope = CoroutineScope(Dispatchers.IO + parentJob)
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setHasOptionsMenu(true)
@@ -34,7 +41,7 @@ class HistoryFragment : Fragment() {
container: ViewGroup?,
state: Bundle?
): View {
activity.setTitle(R.string.history)
activity?.setTitle(R.string.history)
val view = inflater.inflate(
R.layout.fragment_history,
@@ -47,7 +54,17 @@ class HistoryFragment : Fragment() {
) as SwitchCompat
initHistorySwitch(useHistorySwitch)
val headView = View(context)
val footView = View(context)
setWindowInsetListener { insets ->
(view.findViewById(R.id.inset_layout) as View).setPadding(insets)
headView.layoutParams = AbsListView.LayoutParams(0, insets.top)
footView.layoutParams = AbsListView.LayoutParams(0, insets.bottom)
}
listView = view.findViewById(R.id.scans)
listView.addHeaderView(headView, null, false)
listView.addFooterView(footView, null, false)
listView.emptyView = useHistorySwitch
listView.setOnItemClickListener { _, _, _, id ->
showScan(id)
@@ -56,6 +73,7 @@ class HistoryFragment : Fragment() {
askToRemoveScan(v.context, id)
true
}
listView.setOnScrollListener(systemBarScrollListener)
fab = view.findViewById(R.id.share)
fab.setOnClickListener { v ->
@@ -69,7 +87,8 @@ class HistoryFragment : Fragment() {
override fun onDestroy() {
super.onDestroy()
(listView.adapter as ScansAdapter?)?.changeCursor(null)
scansAdapter?.changeCursor(null)
parentJob.cancel()
}
override fun onResume() {
@@ -87,6 +106,10 @@ class HistoryFragment : Fragment() {
askToRemoveAllScans(context)
true
}
R.id.export_history -> {
askToExportToFile(context)
true
}
else -> super.onOptionsItemSelected(item)
}
}
@@ -101,47 +124,37 @@ class HistoryFragment : Fragment() {
}
private fun update(context: Context) {
GlobalScope.launch {
val cursor = db.getScans()
GlobalScope.launch(Main) {
scope.launch {
val cursor = db.getScansCursor()
withContext(Dispatchers.Main) {
fab.visibility = if (cursor != null && cursor.count > 0) {
View.VISIBLE
} else {
View.GONE
}
cursor?.let {
if (listView.adapter == null) {
listView.adapter = ScansAdapter(context, cursor)
} else {
val adapter = listView.adapter as ScansAdapter
adapter.changeCursor(cursor)
adapter.notifyDataSetChanged()
}
// close previous cursor
scansAdapter?.also { it.changeCursor(null) }
scansAdapter = ScansAdapter(context, it)
listView.adapter = scansAdapter
}
}
}
}
private fun showScan(id: Long) {
val cursor = db.getScan(id)
if (cursor != null && cursor.moveToFirst()) {
val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
val rawIndex = cursor.getColumnIndex(Database.SCANS_RAW)
val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
try {
addFragment(
fragmentManager,
DecodeFragment.newInstance(
cursor.getString(contentIndex),
BarcodeFormat.valueOf(cursor.getString(formatIndex)),
cursor.getBlob(rawIndex)
)
private fun showScan(id: Long) = db.getScan(id)?.also { scan ->
try {
addFragment(
fragmentManager,
DecodeFragment.newInstance(
scan.content,
BarcodeFormat.valueOf(scan.format),
scan.raw
)
} catch (e: IllegalArgumentException) {
// shouldn't ever happen
}
)
} catch (e: IllegalArgumentException) {
// shouldn't ever happen
}
cursor?.close()
}
private fun askToRemoveScan(context: Context, id: Long) {
@@ -168,6 +181,75 @@ class HistoryFragment : Fragment() {
.show()
}
private fun askToExportToFile(context: Context) = scope.launch {
progressView.useVisibility {
if (!hasWritePermission(activity)) {
return@useVisibility
}
val (getBinaries, scans) = getScans {
alertDialog(context) { resume ->
setTitle(R.string.csv_allow_binary)
setMessage(R.string.csv_allow_binary_message)
setPositiveButton(R.string.no) { _, _ ->
resume(false)
}
setNegativeButton(android.R.string.yes) { _, _ ->
resume(true)
}
setNeutralButton(android.R.string.cancel) { _, _ ->
resume(null)
}
}
} ?: return@useVisibility
val delimiters = context.resources.getStringArray(
R.array.csv_delimiters_values
)
val delimiter = alertDialog<String>(context) { resume ->
setTitle(R.string.csv_delimiter)
setItems(R.array.csv_delimiters_names) { _, which ->
resume(delimiters[which])
}
} ?: return@useVisibility
val name = withContext(Dispatchers.Main) {
activity.askForFileName(suffix = "csv")
} ?: return@useVisibility
val csv = scans.toCSV(delimiter, getBinaries)
val toastMessage = csv.writeToFile(name)
withContext(Dispatchers.Main) {
Toast.makeText(
context,
toastMessage,
Toast.LENGTH_SHORT
).show()
}
}
}
private fun Flow<DatabaseRepository.Scan>.toCSV(
delimiter: String,
allowBinary: Boolean
): Flow<ByteArray> {
return csvBuilder<DatabaseRepository.Scan> {
column {
name = "DATE"
gettingByString { it.timestamp }
}
column {
name = "TYPE"
gettingByString { it.format }
}
column {
name = "CONTENT"
gettingByString { it.content }
}
if (allowBinary) column {
isBinary = true
name = "BINARY_CONTENT"
gettingBy { it.raw ?: ByteArray(0) }
}
}.buildWith(this, delimiter)
}
private fun pickListSeparatorAndShare(context: Context) {
val separators = context.resources.getStringArray(
R.array.list_separators_values
@@ -181,32 +263,32 @@ class HistoryFragment : Fragment() {
}
private fun shareScans(separator: String) {
if (progressView.visibility == View.VISIBLE) {
return
}
progressView.visibility = View.VISIBLE
GlobalScope.launch {
val cursor = db.getScans()
cursor?.let {
scope.launch {
progressView.useVisibility {
val sb = StringBuilder()
if (cursor.moveToFirst()) {
val contentIndex = cursor.getColumnIndex(
Database.SCANS_CONTENT
)
do {
sb.append(cursor.getString(contentIndex))
sb.append(separator)
} while (cursor.moveToNext())
getScans { false }?.second?.collect {
sb.append(it.content)
sb.append(separator)
}
cursor.close()
val text = sb.toString()
if (text.isNotEmpty()) {
GlobalScope.launch(Main) {
progressView.visibility = View.GONE
shareText(context, text)
}
if (text.isNotEmpty()) withContext(Dispatchers.Main) {
shareText(context, text)
}
}
}
}
@WorkerThread
private suspend inline fun getScans(
crossinline binaryData: suspend () -> Boolean?
): Pair<Boolean, Flow<DatabaseRepository.Scan>>? {
val getBinaries: Boolean = db.hasBinaryData() && binaryData() ?: return null
return getBinaries to db.getScans().mapNotNull {
when {
it.content.isNotEmpty() -> db.getScan(it.id)
getBinaries -> db.getScan(it.id)
else -> null
}
}
}
}
@@ -9,8 +9,11 @@ import android.view.ViewGroup
import android.widget.EditText
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.app.setWindowInsetListener
import de.markusfisch.android.binaryeye.view.setPadding
class PreferencesFragment : Fragment() {
private lateinit var openImmediatelySwitch: SwitchCompat
private lateinit var useHistorySwitch: SwitchCompat
private lateinit var openWithUrlInput: EditText
@@ -19,7 +22,7 @@ class PreferencesFragment : Fragment() {
container: ViewGroup?,
state: Bundle?
): View? {
activity.setTitle(R.string.preferences)
activity?.setTitle(R.string.preferences)
val view = inflater.inflate(
R.layout.fragment_preferences,
@@ -27,20 +30,30 @@ class PreferencesFragment : Fragment() {
false
)
useHistorySwitch = view.findViewById<SwitchCompat>(R.id.use_history)
openImmediatelySwitch = view.findViewById(R.id.open_immediately)
if (prefs.openImmediately) {
openImmediatelySwitch.toggle()
}
useHistorySwitch = view.findViewById(R.id.use_history)
if (prefs.useHistory) {
useHistorySwitch.toggle()
}
openWithUrlInput = view.findViewById<EditText>(R.id.open_with_url)
openWithUrlInput = view.findViewById(R.id.open_with_url)
openWithUrlInput.setText(prefs.openWithUrl)
setWindowInsetListener { insets ->
(view.findViewById(R.id.scroll_view) as View).setPadding(insets)
}
return view
}
override fun onPause() {
super.onPause()
prefs.useHistory = useHistorySwitch.isChecked()
prefs.openImmediately = openImmediatelySwitch.isChecked
prefs.useHistory = useHistorySwitch.isChecked
prefs.openWithUrl = openWithUrlInput.text.toString()
}
}
@@ -0,0 +1,61 @@
package de.markusfisch.android.binaryeye.graphics
import android.graphics.Bitmap
import android.graphics.Matrix
import android.graphics.RectF
import kotlin.math.max
import kotlin.math.roundToInt
fun downsizeIfBigger(bitmap: Bitmap, maxSize: Int): Bitmap = if (
max(bitmap.width, bitmap.height) > maxSize
) {
val srcWidth = bitmap.width
val srcHeight = bitmap.height
var newWidth = maxSize
var newHeight = maxSize
if (srcWidth > srcHeight) {
newHeight = (newWidth.toFloat() / srcWidth.toFloat() *
srcHeight.toFloat()).roundToInt()
} else {
newWidth = (newHeight.toFloat() / srcHeight.toFloat() *
srcWidth.toFloat()).roundToInt()
}
Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
} else {
bitmap
}
fun crop(bitmap: Bitmap, rect: RectF, rotation: Float) = try {
val erected = erect(bitmap, rotation)
val w = erected.width
val h = erected.height
Bitmap.createBitmap(
erected,
(rect.left * w).roundToInt(),
(rect.top * h).roundToInt(),
(rect.width() * w).roundToInt(),
(rect.height() * h).roundToInt()
)
} catch (e: OutOfMemoryError) {
null
} catch (e: IllegalArgumentException) {
null
}
private fun erect(bitmap: Bitmap, rotation: Float): Bitmap = if (
rotation % 360f != 0f
) {
val matrix = Matrix()
matrix.setRotate(rotation)
Bitmap.createBitmap(
bitmap,
0,
0,
bitmap.width,
bitmap.height,
matrix,
true
)
} else {
bitmap
}
@@ -7,6 +7,11 @@ import android.preference.PreferenceManager
class Preferences {
lateinit var preferences: SharedPreferences
var openImmediately = false
set(value) {
setBoolean(OPEN_IMMEDIATELY, value)
field = value
}
var useHistory = false
set(value) {
setBoolean(USE_HISTORY, value)
@@ -17,6 +22,11 @@ class Preferences {
setString(OPEN_WITH_URL, value)
field = value
}
var indexOfLastSelectedFormat: Int = 0
set(value) {
setInt(INDEX_OF_LAST_SELECTED_FORMAT, value)
field = value
}
fun init(context: Context) {
preferences = PreferenceManager.getDefaultSharedPreferences(context)
@@ -24,8 +34,18 @@ class Preferences {
}
fun update() {
openImmediately = preferences.getBoolean(
OPEN_IMMEDIATELY,
openImmediately
)
useHistory = preferences.getBoolean(USE_HISTORY, useHistory)
openWithUrl = preferences.getString(OPEN_WITH_URL, openWithUrl)
indexOfLastSelectedFormat = preferences.getInt(
INDEX_OF_LAST_SELECTED_FORMAT,
indexOfLastSelectedFormat
)
preferences.getString(OPEN_WITH_URL, openWithUrl)?.also {
openWithUrl = it
}
}
private fun setBoolean(label: String, value: Boolean) {
@@ -40,8 +60,16 @@ class Preferences {
editor.apply()
}
private fun setInt(label: String, value: Int) {
val editor = preferences.edit()
editor.putInt(label, value)
editor.apply()
}
companion object {
const val OPEN_IMMEDIATELY = "open_immediately"
const val USE_HISTORY = "use_history"
const val OPEN_WITH_URL = "open_with_url"
const val INDEX_OF_LAST_SELECTED_FORMAT = "index_of_last_selected_format"
}
}
@@ -0,0 +1,128 @@
package de.markusfisch.android.binaryeye.repository
import android.content.Context
import android.database.Cursor
import de.markusfisch.android.binaryeye.data.Database
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
class DatabaseRepository {
private val db = Database()
fun open(applicationContext: Context) {
db.open(applicationContext)
}
fun getScan(id: Long): Scan? = getScan(id) { _, cursor ->
val timeIndex = cursor.getColumnIndex(Database.SCANS_DATETIME)
val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
val rawIndex = cursor.getColumnIndex(Database.SCANS_RAW)
val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
Scan(
id = id,
timestamp = cursor.getString(timeIndex),
content = cursor.getString(contentIndex),
raw = cursor.getBlob(rawIndex),
format = cursor.getString(formatIndex)
)
}
fun <T> getScan(id: Long, map: (id: Long, Cursor) -> T): T? =
db.getScan(id)?.use { map(id, it.apply { moveToFirst() }) }
fun getScans(): Flow<SimpleScan> = getScans { cursor ->
val idIndex = cursor.getColumnIndex(Database.SCANS_ID)
val timeIndex = cursor.getColumnIndex(Database.SCANS_DATETIME)
val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
SimpleScan(
id = cursor.getLong(idIndex),
timestamp = cursor.getString(timeIndex),
content = cursor.getString(contentIndex),
format = cursor.getString(formatIndex)
)
}
fun <T> getScans(map: suspend (Cursor) -> T): Flow<T> = flow {
db.getScans()?.use { results ->
results.asIterable.forEach {
emit(it)
}
}
}.map(map).flowOn(Dispatchers.IO)
fun getScansCursor(): Cursor? = db.getScans()
fun hasBinaryData() = db.hasBinaryData()?.use { it.count > 0 } ?: false
fun insertScan(
timestamp: Long,
content: String,
raw: ByteArray?,
format: String
): Long = db.insertScan(timestamp, content, raw, format)
fun removeScan(id: Long) = db.removeScan(id)
fun removeScans() = db.removeScans()
data class SimpleScan(
val id: Long,
val timestamp: String,
val content: String,
val format: String
)
data class Scan(
val id: Long,
val timestamp: String,
val content: String,
val raw: ByteArray?,
val format: String
) {
// Needed to be overwritten manually, as ByteArray is an array and this isn't handled well by Kotlin
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Scan
if (id != other.id) return false
if (timestamp != other.timestamp) return false
if (content != other.content) return false
if (raw != null) {
if (other.raw == null) return false
if (!raw.contentEquals(other.raw)) return false
} else if (other.raw != null) return false
if (format != other.format) return false
return true
}
// Needs to be overwritten manually, as ByteArray is an array and this
// isn't handled well by Kotlin
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + timestamp.hashCode()
result = 31 * result + content.hashCode()
result = 31 * result + (raw?.contentHashCode() ?: 0)
result = 31 * result + format.hashCode()
return result
}
}
}
private val Cursor.asIterable: Iterable<Cursor>
get() = object : Iterable<Cursor> {
override fun iterator(): Iterator<Cursor> = object : Iterator<Cursor> {
override fun hasNext(): Boolean = position + 1 < count
override fun next(): Cursor = if (moveToNext()) {
this@asIterable
} else {
throw IllegalStateException("You can't access more items, then there are")
}
}
}
@@ -3,6 +3,7 @@ package de.markusfisch.android.binaryeye.rs
import android.content.Context
import android.support.v8.renderscript.*
import de.markusfisch.android.binaryeye.renderscript.ScriptC_rotator
import kotlin.math.roundToInt
class Preprocessor(
context: Context,
@@ -38,8 +39,8 @@ class Preprocessor(
)
val f = .75f
var w = Math.round(width * f)
var h = Math.round(height * f)
var w = (width * f).roundToInt()
var h = (height * f).roundToInt()
resizedType = Type.createXY(
rs,
@@ -0,0 +1,13 @@
package de.markusfisch.android.binaryeye.view
import android.graphics.Rect
import android.view.View
fun View.setPadding(rect: Rect) {
this.setPadding(
rect.left,
rect.top,
rect.right,
rect.bottom
)
}
@@ -0,0 +1,41 @@
package de.markusfisch.android.binaryeye.widget
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.util.AttributeSet
import de.markusfisch.android.binaryeye.app.colorSystemAndToolBars
import de.markusfisch.android.scalingimageview.widget.ScalingImageView
open class ConfinedScalingImageView : ScalingImageView {
val insets = Rect()
constructor(context: Context, attrs: AttributeSet, defStyle: Int) :
super(context, attrs, defStyle)
constructor(context: Context, attrs: AttributeSet) :
this(context, attrs, 0)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
colorSystemAndToolBars(context, !bounds.contains(mappedRect))
}
override fun layoutImage(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
) {
if (changed) {
setBounds(
(left + insets.left).toFloat(),
(top + insets.top).toFloat(),
(right - insets.right).toFloat(),
(bottom - insets.bottom).toFloat()
)
}
centerRemap()
}
}
@@ -0,0 +1,45 @@
package de.markusfisch.android.binaryeye.widget
import android.content.Context
import android.util.AttributeSet
import android.widget.ScrollView
import de.markusfisch.android.binaryeye.app.colorSystemAndToolBars
class ConfinedScrollView : ScrollView {
private var scrollable = false
constructor(context: Context, attrs: AttributeSet, defStyle: Int) :
super(context, attrs, defStyle)
constructor(context: Context, attrs: AttributeSet) :
this(context, attrs, 0)
override fun onLayout(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
) {
super.onLayout(changed, left, top, right, bottom)
if (changed) {
// give Android some time to settle down before running this;
// not putting it on the queue makes it only work sometimes
post {
getChildAt(0)?.also { child ->
scrollable = height < child.height + paddingTop + paddingBottom
colorSystemAndToolBars(
context,
scrollY > 0,
scrollable
)
}
}
}
}
override fun onScrollChanged(x: Int, y: Int, oldx: Int, oldy: Int) {
super.onScrollChanged(x, y, oldx, oldy)
colorSystemAndToolBars(context, y > 0, scrollable)
}
}
@@ -0,0 +1,99 @@
package de.markusfisch.android.binaryeye.widget
import android.content.Context
import android.graphics.*
import android.support.v4.content.ContextCompat
import android.util.AttributeSet
import de.markusfisch.android.binaryeye.R
import kotlin.math.roundToInt
class CropImageView(context: Context, attr: AttributeSet) :
ConfinedScalingImageView(context, attr) {
val windowInsets = Rect()
var onScan: (() -> Rect)? = null
private val boundsPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val candidatePaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val lastMappedRect = RectF()
private val dp = context.resources.displayMetrics.density
private val padding: Int = (dp * 24f).roundToInt()
private val onScanRunnable = Runnable {
onScan?.invoke()?.also {
candidateRect = it
invalidate()
}
}
private var candidateRect: Rect? = null
init {
boundsPaint.color = ContextCompat.getColor(context, R.color.crop_bound)
boundsPaint.style = Paint.Style.STROKE
boundsPaint.strokeWidth = dp * 2f
boundsPaint.pathEffect = DashPathEffect(floatArrayOf(dp * 10f, dp * 10f), 0f)
candidatePaint.color = ContextCompat.getColor(context, R.color.candidate)
candidatePaint.style = Paint.Style.FILL_AND_STROKE
candidatePaint.strokeWidth = dp * 3f
scaleType = ScaleType.CENTER_CROP
}
override fun onLayout(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
) {
super.onLayout(changed, left, top, right, bottom)
setBoundsWithPadding(
left + windowInsets.left,
top + windowInsets.top,
right - windowInsets.right,
bottom - windowInsets.bottom
)
}
fun getBoundsRect(): RectF {
return bounds
}
private fun setBoundsWithPadding(
left: Int,
top: Int,
right: Int,
bottom: Int
) {
val width = right - left
val height = bottom - top
val size = if (width < height)
width - padding * 2
else
height - padding * 2
val hpad = (width - size) / 2
val vpad = (height - size) / 2
setBounds(
left.toFloat() + hpad,
top.toFloat() + vpad,
right.toFloat() - hpad,
bottom.toFloat() - vpad
)
center(bounds)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawRect(bounds, boundsPaint)
val rc = candidateRect
if (rc != null && rc.width() > 0) {
canvas.drawRect(rc, candidatePaint)
candidateRect = null
}
val mr = mappedRect ?: return
if (mr != lastMappedRect) {
removeCallbacks(onScanRunnable)
postDelayed(onScanRunnable, 500)
lastMappedRect.set(mr)
}
}
}
@@ -14,7 +14,7 @@ class Zxing {
)
decodeFormats.addAll(
EnumSet.copyOf(
Arrays.asList(
listOf(
BarcodeFormat.AZTEC,
BarcodeFormat.CODABAR,
BarcodeFormat.CODE_39,
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp">
<path android:fillColor="#FF000000"
android:pathData="M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2L2,14v2z"/>
</vector>
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z"/>
</vector>
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M15.55,5.55L11,1v3.07C7.06,4.56 4,7.92 4,12s3.05,7.44 7,7.93v-2.02c-2.84,-0.48 -5,-2.94 -5,-5.91s2.16,-5.43 5,-5.91L11,10l4.55,-4.45zM19.93,11c-0.17,-1.39 -0.72,-2.73 -1.62,-3.89l-1.42,1.42c0.54,0.75 0.88,1.6 1.02,2.47h2.02zM13,17.9v2.02c1.39,-0.17 2.74,-0.71 3.9,-1.61l-1.44,-1.44c-0.75,0.54 -1.59,0.89 -2.46,1.03zM16.89,15.48l1.42,1.41c0.9,-1.16 1.45,-2.5 1.62,-3.89h-2.02c-0.14,0.87 -0.48,1.72 -1.02,2.48z"/>
</vector>
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>
@@ -0,0 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp">
<path android:fillColor="#FF000000"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>
+6 -19
View File
@@ -1,39 +1,26 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<de.markusfisch.android.cameraview.widget.CameraView
android:id="@+id/camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<RelativeLayout
android:id="@+id/main_layout"
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<include layout="@layout/toolbar"/>
<SeekBar
android:id="@+id/zoom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom|start"
android:layout_marginBottom="8dp"
android:layout_marginRight="80dp"
android:layout_marginEnd="80dp"
android:padding="16dp"/>
<android.support.design.widget.FloatingActionButton
tools:ignore="UnusedAttribute"
style="@style/FAB"
android:id="@+id/flash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:contentDescription="@string/toggle_flash"
android:src="@drawable/ic_action_flash"
app:elevation="4dp"/>
</RelativeLayout>
android:src="@drawable/ic_action_flash"/>
</android.support.design.widget.CoordinatorLayout>
</merge>
+6 -8
View File
@@ -1,14 +1,12 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:id="@+id/main_layout"
<android.support.design.widget.CoordinatorLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<include layout="@layout/toolbar"/>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</FrameLayout>
</merge>
+18
View File
@@ -0,0 +1,18 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<de.markusfisch.android.binaryeye.widget.CropImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/view_barcode"/>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<include layout="@layout/toolbar"/>
<android.support.design.widget.FloatingActionButton
style="@style/FAB"
android:id="@+id/scan"
android:contentDescription="@string/scan_code"
android:src="@drawable/ic_action_scan"/>
</android.support.design.widget.CoordinatorLayout>
</merge>
+13 -17
View File
@@ -1,24 +1,20 @@
<RelativeLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<de.markusfisch.android.scalingimageview.widget.ScalingImageView
<de.markusfisch.android.binaryeye.widget.ConfinedScalingImageView
android:id="@+id/barcode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/view_barcode"/>
<android.support.design.widget.FloatingActionButton
tools:ignore="UnusedAttribute"
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:contentDescription="@string/share"
android:src="@drawable/ic_action_share"
app:elevation="4dp"/>
</RelativeLayout>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/inset_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.FloatingActionButton
style="@style/FAB"
android:id="@+id/share"
android:contentDescription="@string/share"
android:src="@drawable/ic_action_share"/>
</android.support.design.widget.CoordinatorLayout>
</FrameLayout>
+23 -23
View File
@@ -1,21 +1,25 @@
<RelativeLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
<de.markusfisch.android.binaryeye.widget.ConfinedScrollView
android:id="@+id/scroll_view"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:gravity="start|top"
android:hint="@string/content"
android:imeOptions="flagNoExtractUi"
@@ -23,8 +27,8 @@
android:inputType="textMultiLine"
android:typeface="monospace"
tools:ignore="UnusedAttribute"/>
<TextView
style="@style/SecondaryText"
android:id="@+id/format"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -32,8 +36,7 @@
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:gravity="end|top"
android:textSize="14sp" />
android:gravity="end|top" />
<TextView
android:id="@+id/hex"
android:layout_width="match_parent"
@@ -47,19 +50,16 @@
android:typeface="monospace"
android:textSize="12sp"
tools:text="54 65 73 74 20 51 52 20 Test QR\n43 6F 64 65 Code"/>
</RelativeLayout>
</ScrollView>
<android.support.design.widget.FloatingActionButton
tools:ignore="UnusedAttribute"
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:contentDescription="@string/share"
android:src="@drawable/ic_action_share"
app:elevation="4dp"/>
</RelativeLayout>
</de.markusfisch.android.binaryeye.widget.ConfinedScrollView>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/inset_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.FloatingActionButton
style="@style/FAB"
android:id="@+id/open"
android:contentDescription="@string/open_url"
android:src="@drawable/ic_action_open"/>
</android.support.design.widget.CoordinatorLayout>
</FrameLayout>
+66 -55
View File
@@ -1,63 +1,74 @@
<ScrollView
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
<de.markusfisch.android.binaryeye.widget.ConfinedScrollView
android:id="@+id/scroll_view"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/format"/>
<Spinner
android:id="@+id/format"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/size"/>
<TextView
android:id="@+id/size_display"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center"/>
<SeekBar
android:id="@+id/size_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:progress="4"
android:max="7"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/content"/>
<EditText
tools:targetApi="o"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="start|top"
android:typeface="monospace"
android:imeOptions="flagNoExtractUi"
android:inputType="textMultiLine"
android:hint="@string/input_content_here"
android:importantForAutofill="no"/>
<Button
style="@style/Button"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/format"/>
<Spinner
android:id="@+id/format"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/size"/>
<TextView
android:id="@+id/size_display"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center"/>
<SeekBar
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:progress="4"
android:max="7"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/content"/>
<EditText
tools:targetApi="o"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="start|top"
android:typeface="monospace"
android:imeOptions="flagNoExtractUi"
android:inputType="textMultiLine"
android:hint="@string/input_content_here"
android:importantForAutofill="no"/>
</LinearLayout>
</de.markusfisch.android.binaryeye.widget.ConfinedScrollView>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/inset_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.FloatingActionButton
style="@style/FAB"
android:id="@+id/encode"
android:layout_width="match_parent"
android:text="@string/encode"/>
</LinearLayout>
</ScrollView>
android:contentDescription="@string/encode"
android:src="@drawable/ic_action_next"/>
</android.support.design.widget.CoordinatorLayout>
</FrameLayout>
+20 -24
View File
@@ -1,7 +1,5 @@
<RelativeLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
@@ -11,26 +9,24 @@
android:layout_height="match_parent"
android:divider="@color/separator"
android:dividerHeight="1px"/>
<android.support.v7.widget.SwitchCompat
android:visibility="gone"
android:id="@+id/use_history"
<android.support.design.widget.CoordinatorLayout
android:id="@+id/inset_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:padding="16dp"
android:text="@string/use_history"/>
<android.support.design.widget.FloatingActionButton
tools:ignore="UnusedAttribute"
android:visibility="gone"
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:contentDescription="@string/share"
android:src="@drawable/ic_action_share"
app:elevation="4dp"/>
android:layout_height="match_parent">
<android.support.v7.widget.SwitchCompat
android:visibility="gone"
android:id="@+id/use_history"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:padding="16dp"
android:text="@string/use_history"/>
<android.support.design.widget.FloatingActionButton
style="@style/FAB"
android:visibility="gone"
android:id="@+id/share"
android:contentDescription="@string/share"
android:src="@drawable/ic_action_share"/>
</android.support.design.widget.CoordinatorLayout>
<include layout="@layout/progress_view"/>
</RelativeLayout>
</FrameLayout>
@@ -1,30 +1,42 @@
<LinearLayout
<de.markusfisch.android.binaryeye.widget.ConfinedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/scroll_view"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:orientation="vertical">
<android.support.v7.widget.SwitchCompat
android:id="@+id/use_history"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/use_history"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/open_with_url"/>
<EditText
tools:targetApi="o"
android:id="@+id/open_with_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:gravity="start|top"
android:typeface="monospace"
android:imeOptions="flagNoExtractUi"
android:inputType="textUri"
android:hint="@string/open_with_url_hint"
android:importantForAutofill="no"/>
</LinearLayout>
android:layout_height="match_parent"
android:padding="16dp"
android:orientation="vertical">
<android.support.v7.widget.SwitchCompat
android:id="@+id/open_immediately"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/open_immediately"/>
<android.support.v7.widget.SwitchCompat
android:id="@+id/use_history"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/use_history"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/open_with_url"/>
<EditText
tools:targetApi="o"
android:id="@+id/open_with_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:gravity="start|top"
android:typeface="monospace"
android:imeOptions="flagNoExtractUi"
android:inputType="textUri"
android:hint="@string/open_with_url_hint"
android:importantForAutofill="no"/>
</LinearLayout>
</de.markusfisch.android.binaryeye.widget.ConfinedScrollView>
+4
View File
@@ -4,6 +4,7 @@
android:layout_height="match_parent"
android:padding="16dp">
<TextView
style="@style/SecondaryText"
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -11,6 +12,7 @@
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"/>
<TextView
style="@style/SecondaryText"
android:id="@+id/format"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -26,5 +28,7 @@
android:layout_height="wrap_content"
android:layout_below="@+id/time"
android:layout_marginTop="4dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:typeface="monospace"/>
</RelativeLayout>
@@ -11,6 +11,11 @@
android:title="@string/compose_barcode"
android:icon="@drawable/ic_action_create"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/pick_file"
android:title="@string/pick_file"
android:icon="@drawable/ic_action_pick_file"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/switch_camera"
android:title="@string/switch_camera"
+9
View File
@@ -0,0 +1,9 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:material="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/rotate"
android:title="@string/rotate_image_cw"
android:icon="@drawable/ic_action_rotate_clockwise"
material:showAsAction="ifRoom"/>
</menu>
@@ -0,0 +1,9 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:material="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/save"
android:title="@string/export_to_file"
android:icon="@drawable/ic_action_save"
material:showAsAction="ifRoom"/>
</menu>
+3 -3
View File
@@ -7,9 +7,9 @@
android:icon="@drawable/ic_action_copy"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/open_url"
android:title="@string/open_url"
android:icon="@drawable/ic_action_open"
android:id="@+id/share"
android:title="@string/share"
android:icon="@drawable/ic_action_share"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/create"
@@ -0,0 +1,9 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:material="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/next"
android:title="@string/encode"
android:icon="@drawable/ic_action_next"
material:showAsAction="ifRoom"/>
</menu>
+9 -4
View File
@@ -2,8 +2,13 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:material="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/clear"
android:title="@string/clear_history"
android:icon="@drawable/ic_action_clear"
material:showAsAction="ifRoom"/>
android:id="@+id/export_history"
android:title="@string/export_to_file"
android:icon="@drawable/ic_action_save"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/clear"
android:title="@string/clear_history"
android:icon="@drawable/ic_action_clear"
material:showAsAction="ifRoom"/>
</menu>
+18 -3
View File
@@ -7,8 +7,8 @@
<string name="content">Inhalt</string>
<string name="binary_data">(Binäre Daten)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d Zeichen</item>
<item quantity="other">%1$s\n%2$d Zeichen</item>
<item quantity="one">%2$d Zeichen, %1$s</item>
<item quantity="other">%2$d Zeichen, %1$s</item>
</plurals>
<string name="toggle_flash">Licht an/ausschalten</string>
<string name="share">Teilen</string>
@@ -29,6 +29,7 @@
<string name="switch_camera">Kamera wechseln</string>
<string name="history">Gespeicherte Codes</string>
<string name="preferences">Einstellungen</string>
<string name="open_immediately">Inspektion überspringen und Inhalte sofort öffnen</string>
<string name="use_history">Codes speichern</string>
<string name="open_with_url">URL zum Öffnen unbekannter Daten</string>
<string name="really_remove_scan">Code wirklich entfernen?</string>
@@ -38,7 +39,7 @@
<string name="pick_list_separator">Wie sollen die Einträge getrennt werden?</string>
<string name="separator_line_break">Zeilenumbruch</string>
<string name="separator_ruler">Trennstrich</string>
<string name="save_as_file_name">Speichern als?</string>
<string name="save_as_file_name">Speichern in Downloads als?</string>
<string name="file_name">Dateiname</string>
<string name="error_saving_binary_data">Datei konnte nicht gespeichert werden</string>
<string name="error_file_exists">Datei existiert bereits</string>
@@ -55,4 +56,18 @@
<string name="vcard_failed">Konnte nicht zu den Kontakten hinzufügen</string>
<string name="vevent_add">Zum Kalender hinzufügen</string>
<string name="vevent_failed">Konnte nicht zum Kalender hinzufügen</string>
<string name="otpauth_add">2FA hinzufügen</string>
<string name="otpauth_error">Konnte 2FA nicht hinzufügen</string>
<string name="search_web">Suche im Internet</string>
<string name="export_to_file">In Datei exportieren</string>
<string name="csv_delimiter">Welches Trennzeichen soll genutzt werden (CSV-Datei)?</string>
<string name="delimiter_comma">Komma</string>
<string name="delimiter_semicolon">Semikolon</string>
<string name="csv_allow_binary">Binäre Daten in Ausgabedatei erlauben?</string>
<string name="no">Nein</string>
<string name="csv_allow_binary_message">Dies sollte wahrscheinlich nicht erlabubt weden, da dies das Dateiformat bricht und damit die Datei wahrscheinlich nicht mehr von Textverarbeitungsprogrammen geöffnet werden kann.</string>
<string name="saved_in_downloads">In den Downloads gespeichert</string>
<string name="rotate_image_cw">Bild im Uhrzeigersinn rotieren</string>
<string name="pick_file">Bilddatei auswählen</string>
<string name="pick_code_to_scan">Code im Bild auswählen</string>
</resources>
+18 -3
View File
@@ -7,8 +7,8 @@
<string name="content">Contenu</string>
<string name="binary_data">(binary data)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d character</item>
<item quantity="other">%1$s\n%2$d characters</item>
<item quantity="one">%2$d characters, %1$s</item>
<item quantity="other">%2$d characters, %1$s</item>
</plurals>
<string name="toggle_flash">Toggle flash</string>
<string name="share">Share</string>
@@ -29,6 +29,7 @@
<string name="switch_camera">Changer de camera</string>
<string name="history">Historique</string>
<string name="preferences">Preferences</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="use_history">Sauvegarder l\'histoire des scans</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="really_remove_scan">Supprimer le scan ?</string>
@@ -38,7 +39,7 @@
<string name="pick_list_separator">How to separate list items?</string>
<string name="separator_line_break">Line break</string>
<string name="separator_ruler">Ruler</string>
<string name="save_as_file_name">Save as file?</string>
<string name="save_as_file_name">Save in Downloads as file?</string>
<string name="file_name">File name</string>
<string name="error_saving_binary_data">Cannot save file</string>
<string name="error_file_exists">File already exists</string>
@@ -55,4 +56,18 @@
<string name="vcard_failed">Could not add to contacts</string>
<string name="vevent_add">Add to calendar</string>
<string name="vevent_failed">Could not add to calendar</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
<string name="search_web">Search the web</string>
<string name="export_to_file">Export to file</string>
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
<string name="delimiter_comma">Comma</string>
<string name="delimiter_semicolon">Semicolon</string>
<string name="csv_allow_binary">Allow binary data in output file?</string>
<string name="no">No</string>
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
<string name="saved_in_downloads">Saved in downloads</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
</resources>
+24 -9
View File
@@ -7,8 +7,8 @@
<string name="content">Tartalom</string>
<string name="binary_data">(bináris adat)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d karakter</item>
<item quantity="other">%1$s\n%2$d karakter</item>
<item quantity="one">%2$d karakter, %1$s</item>
<item quantity="other">%2$d karakter, %1$s</item>
</plurals>
<string name="toggle_flash">Vaku be- vagy kikapcsolása</string>
<string name="share">Megosztás</string>
@@ -16,7 +16,7 @@
<string name="put_into_clipboard">Vágólapra helyezés</string>
<string name="open_url">URL megnyitása</string>
<string name="cannot_resolve_action">Nincs alkalmazás, amely meg tudná nyitni azt</string>
<string name="pick_search_engine">Válasszon egy keresőmotort</string>
<string name="pick_search_engine">Válasszon keresőmotort</string>
<string name="format">Formátum</string>
<string name="size">Méret képpontban</string>
<string name="width_by_height">%1$d×%2$d</string>
@@ -28,9 +28,10 @@
<string name="info">Információ</string>
<string name="switch_camera">Kamera átkapcsolása</string>
<string name="history">Előzmények</string>
<string name="preferences">Preferences</string>
<string name="preferences">Beállítások</string>
<string name="open_immediately">Vizsgálat kihagyása és a tartalom azonnali megnyitása</string>
<string name="use_history">Beolvasási előzmények mentése</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="open_with_url">Ismeretlen adat megnyitása URL-lel</string>
<string name="really_remove_scan">Valóban eltávolítja a beolvasást?</string>
<string name="really_remove_all_scans">Valóban eltávolítja az összes beolvasását?</string>
<string name="clear_history">Előzmények törlése</string>
@@ -51,8 +52,22 @@
<string name="tel_error">Nem sikerült tárcsázni a számot</string>
<string name="mail_send">Levél küldése</string>
<string name="mail_error">Nem sikerült elküldeni a levelet</string>
<string name="vcard_add">Add to contacts</string>
<string name="vcard_failed">Could not add to contacts</string>
<string name="vevent_add">Add to calendar</string>
<string name="vevent_failed">Could not add to calendar</string>
<string name="vcard_add">Hozzáadás a címjegyzékhez</string>
<string name="vcard_failed">Nem sikerült hozzáadni a címjegyzékhez</string>
<string name="vevent_add">Hozzáadás a naptárhoz</string>
<string name="vevent_failed">Nem sikerült hozzáadni a naptárhoz</string>
<string name="otpauth_add">Kétlépcsős hitelesítés hozzáadása</string>
<string name="otpauth_error">Nem sikerült hozzáadni a kétlépcsős hitelesítést</string>
<string name="search_web">Keresés a weben</string>
<string name="export_to_file">Exportálás fájlba</string>
<string name="csv_delimiter">Milyen mezőelválasztót kell használni (CSV-fájl)?</string>
<string name="delimiter_comma">Vessző</string>
<string name="delimiter_semicolon">Pontosvessző</string>
<string name="csv_allow_binary">Engedélyezi a bináris adatot a kimeneti fájlban?</string>
<string name="no">Nem</string>
<string name="csv_allow_binary_message">Valószínűleg ezt nem szeretné engedélyezni, mivel tönkre fogja tenni a fájlformátumot, és a szövegszerkesztők talán nem fogják tudni többé olvasni.</string>
<string name="saved_in_downloads">Elmentve a letöltésekbe</string>
<string name="rotate_image_cw">Kép forgatása jobbra</string>
<string name="pick_file">Képfájl kiválasztása</string>
<string name="pick_code_to_scan">Kód kiválasztása a beolvasáshoz</string>
</resources>
+35 -20
View File
@@ -1,14 +1,14 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="no_camera_no_fun">Aplikasi ini tidak akan bisa bekerja tanpa akses kamera. Selamat tinggal.</string>
<resources>
<string name="no_camera_no_fun">Aplikasi tidak akan bisa bekerja tanpa akses kamera. Selamat tinggal.</string>
<string name="camera_error">Tidak bisa mengakses kamera, silakan coba lagi</string>
<string name="scan_code">Pindai kode</string>
<string name="compose_barcode">Buat barcode</string>
<string name="decode_barcode">Dekode barcode</string>
<string name="content">Konten</string>
<string name="binary_data">(data binari)</string>
<plurals name="barcode_info" tools:ignore="UnusedQuantity">
<item quantity="one">%1$s\n%2$d karakter</item>
<item quantity="other">%1$s\n%2$d karakter</item>
<plurals name="barcode_info">
<item quantity="one">%2$d karakter, %1$s</item>
<item quantity="other">%2$d karakter, %1$s</item>
</plurals>
<string name="toggle_flash">Nyala/Matikan blitz</string>
<string name="share">Bagikan</string>
@@ -28,9 +28,10 @@
<string name="info">Info</string>
<string name="switch_camera">Ganti kamera</string>
<string name="history">Riwayat</string>
<string name="preferences">Preferences</string>
<string name="preferences">Preferensi</string>
<string name="open_immediately">Lewati inspeksi dan segera buka konten</string>
<string name="use_history">Simpan riwayat pemindaian</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="open_with_url">Buka data tak dikenal dengan URL</string>
<string name="really_remove_scan">Yakin menghapus pemindaian?</string>
<string name="really_remove_all_scans">Yakin menghapus semua pemindaian?</string>
<string name="clear_history">Hapus riwayat</string>
@@ -42,17 +43,31 @@
<string name="file_name">Nama berkas</string>
<string name="error_saving_binary_data">Tidak bisa menyimpan berkas</string>
<string name="error_file_exists">Berkas sudah ada</string>
<string name="connect_to_wifi">Connect to WiFi</string>
<string name="wifi_config_failed">Could not configure WiFi</string>
<string name="wifi_added">WiFi added</string>
<string name="sms_send">Send SMS</string>
<string name="sms_error">Could not send SMS</string>
<string name="tel_dial">Dial number</string>
<string name="tel_error">Could not dial number</string>
<string name="mail_send">Send mail</string>
<string name="mail_error">Could not send mail</string>
<string name="vcard_add">Add to contacts</string>
<string name="vcard_failed">Could not add to contacts</string>
<string name="vevent_add">Add to calendar</string>
<string name="vevent_failed">Could not add to calendar</string>
<string name="connect_to_wifi">Sambungkan ke WiFi</string>
<string name="wifi_config_failed">Tidak bisa mengelola WiFi</string>
<string name="wifi_added">WiFi ditambahkan</string>
<string name="sms_send">Kirim SMS</string>
<string name="sms_error">Tidak bisa mengirim SMS</string>
<string name="tel_dial">Panggil nomor</string>
<string name="tel_error">Tidak bisa memanggil nomor</string>
<string name="mail_send">Kirim surel</string>
<string name="mail_error">Tidak bisa mengirim surel</string>
<string name="vcard_add">Tambahkan ke kontak</string>
<string name="vcard_failed">Tidak bisa menambahkan ke kontak</string>
<string name="vevent_add">Tambahkan ke kalender</string>
<string name="vevent_failed">Tidak bisa menambahkan ke kalender</string>
<string name="otpauth_add">Tambahkan 2FA</string>
<string name="otpauth_error">Tidak bisa menambahkan 2FA</string>
<string name="search_web">Cari secara daring</string>
<string name="export_to_file">Ekspor ke berkas</string>
<string name="csv_delimiter">Karakter pemisah apa yang harus digunakan (Berkas-CSV)?</string>
<string name="delimiter_comma">Koma</string>
<string name="delimiter_semicolon">Titik koma</string>
<string name="csv_allow_binary">Izinkan data binari di dalam berkas keluaran?</string>
<string name="no">Tidak</string>
<string name="csv_allow_binary_message">Anda sebaiknya tidak mengizinkannya, karena akan merusak format berkas dan kemungkinan tidak bisa lagi dibaca oleh aplikasi penyunting teks.</string>
<string name="saved_in_downloads">Simpan di dalam unduhan</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
</resources>
+38 -23
View File
@@ -5,10 +5,10 @@
<string name="compose_barcode">Crea codice a barre</string>
<string name="decode_barcode">Decodifica codice a barre</string>
<string name="content">Contenuto</string>
<string name="binary_data">(binary data)</string>
<string name="binary_data">(dati binari)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d character</item>
<item quantity="other">%1$s\n%2$d characters</item>
<item quantity="one">%2$d carattere, %1$s</item>
<item quantity="other">%2$d caratteri, %1$s</item>
</plurals>
<string name="toggle_flash">Flash</string>
<string name="share">Condividi</string>
@@ -16,21 +16,22 @@
<string name="put_into_clipboard">Metti negli appunti</string>
<string name="open_url">Apri URL</string>
<string name="cannot_resolve_action">Nessuna app può aprirlo</string>
<string name="pick_search_engine">Pick a search engine</string>
<string name="pick_search_engine">Scegli un motore di ricerca</string>
<string name="format">Formato</string>
<string name="size">Dimensioni in pixel</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Contenuto input qui</string>
<string name="encode">CODIFICA</string>
<string name="error_no_content">Contenuto mancante</string>
<string name="error_encoding_barcode">Barcode cannot be generated</string>
<string name="error_encoding_barcode">Il codice a barre non può essere generato</string>
<string name="view_barcode">Vedi codice a barre</string>
<string name="info">Info</string>
<string name="switch_camera">Cambia camera</string>
<string name="history">Cronologia</string>
<string name="preferences">Preferences</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="use_history">Salva cronologia delle scansioni</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="open_with_url">Apri dati sconosciuti con URL</string>
<string name="really_remove_scan">Vuoi veramente rimuovere la scansione?</string>
<string name="really_remove_all_scans">Vuoi veramente rimuovere tutte le scansioni?</string>
<string name="clear_history">Pulisci cronologia</string>
@@ -38,21 +39,35 @@
<string name="pick_list_separator">Come separare elementi di una lista?</string>
<string name="separator_line_break">Interruzione di linea</string>
<string name="separator_ruler">Righello</string>
<string name="save_as_file_name">Save as file?</string>
<string name="file_name">File name</string>
<string name="error_saving_binary_data">Cannot save file</string>
<string name="error_file_exists">File already exists</string>
<string name="connect_to_wifi">Connect to WiFi</string>
<string name="wifi_config_failed">Could not configure WiFi</string>
<string name="wifi_added">WiFi added</string>
<string name="sms_send">Send SMS</string>
<string name="sms_error">Could not send SMS</string>
<string name="tel_dial">Dial number</string>
<string name="tel_error">Could not dial number</string>
<string name="mail_send">Send mail</string>
<string name="mail_error">Could not send mail</string>
<string name="vcard_add">Add to contacts</string>
<string name="vcard_failed">Could not add to contacts</string>
<string name="vevent_add">Add to calendar</string>
<string name="vevent_failed">Could not add to calendar</string>
<string name="save_as_file_name">Salvare come file?</string>
<string name="file_name">Nome del file</string>
<string name="error_saving_binary_data">Impossibile salvare il file</string>
<string name="error_file_exists">Il File esiste già</string>
<string name="connect_to_wifi">Connecti al WiFi</string>
<string name="wifi_config_failed">Impossibile configurare il WiFi</string>
<string name="wifi_added">WiFi aggiunto</string>
<string name="sms_send">Invia SMS</string>
<string name="sms_error">Impossibile inviare SMS</string>
<string name="tel_dial">Componi numero</string>
<string name="tel_error">Impossibile comporre il numero</string>
<string name="mail_send">Invia mail</string>
<string name="mail_error">Impossibile inviare la mail</string>
<string name="vcard_add">Aggiungi ai contatti</string>
<string name="vcard_failed">Impossibile aggiungere ai contatti</string>
<string name="vevent_add">Aggiungi al calendario</string>
<string name="vevent_failed">Impossibile aggiungere al calendario</string>
<string name="otpauth_add">Aggiungi 2FA</string>
<string name="otpauth_error">Impossibile aggiungre il 2FA</string>
<string name="search_web">Cerca nel web</string>
<string name="export_to_file">Esporta su file</string>
<string name="csv_delimiter">Quale delimitatore usare (File-CSV)?</string>
<string name="delimiter_comma">Virgola</string>
<string name="delimiter_semicolon">Punto e virgola</string>
<string name="csv_allow_binary">Permetti dati binari nel file di output?</string>
<string name="no">No</string>
<string name="csv_allow_binary_message">È consigliato non permetterlo, perché rompe il formato del file e probabilmente lo renderà non più leggibile dagli editor di testo.</string>
<string name="saved_in_downloads">Salvato nei download</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
</resources>
+52 -37
View File
@@ -1,58 +1,73 @@
<resources>
<string name="no_camera_no_fun">Deze app kan niet werken zonder cameratoegang. Tot ziens.</string>
<string name="camera_error">Kan camera niet openen; probeer het opnieuw.</string>
<string name="scan_code">Scan een scode</string>
<string name="scan_code">Scan een code</string>
<string name="compose_barcode">Scan een barcode</string>
<string name="decode_barcode">Decode barcode</string>
<string name="decode_barcode">Barcode decoderen</string>
<string name="content">Inhoud</string>
<string name="binary_data">(binary data)</string>
<string name="binary_data">(binaire data)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d character</item>
<item quantity="other">%1$s\n%2$d characters</item>
<item quantity="one">%2$d character, %1$s</item>
<item quantity="other">%2$d characters, %1$s</item>
</plurals>
<string name="toggle_flash">Toggle flash</string>
<string name="share">Share</string>
<string name="toggle_flash">Flitslicht schakelen</string>
<string name="share">Delen</string>
<string name="copy_to_clipboard">Kopiëren naar klembord</string>
<string name="put_into_clipboard">Kopieer de inhoud naar het klembord</string>
<string name="open_url">URL openen</string>
<string name="cannot_resolve_action">Er is geen app aangetroffen die dit kan openen</string>
<string name="pick_search_engine">Pick a search engine</string>
<string name="pick_search_engine">Een zoekmachine kiezen</string>
<string name="format">Formaat</string>
<string name="size">Grootte, in pixels</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Voer hier de inhoud in</string>
<string name="encode">CODEREN</string>
<string name="error_no_content">Inhoud ontbreekt</string>
<string name="error_encoding_barcode">Barcode cannot be generated</string>
<string name="error_encoding_barcode">Barcode kan niet gegenereerd worden</string>
<string name="view_barcode">Barcode bekijken</string>
<string name="info">Informatie</string>
<string name="switch_camera">Camera wisselen</string>
<string name="history">History</string>
<string name="preferences">Preferences</string>
<string name="use_history">Save scan history</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="really_remove_scan">Really remove scan?</string>
<string name="really_remove_all_scans">Really remove all scans?</string>
<string name="clear_history">Clear history</string>
<string name="no_barcode_found">No barcode found</string>
<string name="pick_list_separator">How to separate list items?</string>
<string name="separator_line_break">Line break</string>
<string name="separator_ruler">Ruler</string>
<string name="save_as_file_name">Save as file?</string>
<string name="file_name">File name</string>
<string name="error_saving_binary_data">Cannot save file</string>
<string name="error_file_exists">File already exists</string>
<string name="connect_to_wifi">Connect to WiFi</string>
<string name="wifi_config_failed">Could not configure WiFi</string>
<string name="wifi_added">WiFi added</string>
<string name="sms_send">Send SMS</string>
<string name="sms_error">Could not send SMS</string>
<string name="tel_dial">Dial number</string>
<string name="tel_error">Could not dial number</string>
<string name="mail_send">Send mail</string>
<string name="mail_error">Could not send mail</string>
<string name="vcard_add">Add to contacts</string>
<string name="vcard_failed">Could not add to contacts</string>
<string name="vevent_add">Add to calendar</string>
<string name="vevent_failed">Could not add to calendar</string>
<string name="history">Geschiedenis</string>
<string name="preferences">Voorkeuren</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="use_history">Scan-geschiedenis behouden</string>
<string name="really_remove_scan">Scan echt verwijderen?</string>
<string name="really_remove_all_scans">Echt alle scans verwijderen?</string>
<string name="clear_history">Geschiedenis wissen</string>
<string name="no_barcode_found">Geen barcode gevonden</string>
<string name="separator_line_break">Nieuwe regel</string>
<string name="separator_ruler">Horizontale streep</string>
<string name="save_as_file_name">Als bestand opslaan?</string>
<string name="file_name">Naam van het bestand</string>
<string name="error_file_exists">Bestand bestaat al</string>
<string name="connect_to_wifi">Met WiFi verbinden</string>
<string name="wifi_config_failed">Kon WiFi niet configureren</string>
<string name="wifi_added">WiFi toegevoegd</string>
<string name="sms_send">SMS versturen</string>
<string name="sms_error">Kon SMS niet versturen</string>
<string name="tel_dial">Nummer bellen</string>
<string name="tel_error">Kon nummer niet bellen</string>
<string name="mail_send">Mail versturen</string>
<string name="mail_error">Kon mail niet versturen</string>
<string name="vcard_add">Aan contacten toevoegen</string>
<string name="vcard_failed">Kon niet aan contacten toevoegen</string>
<string name="vevent_add">Aan kalender toevoegen</string>
<string name="otpauth_add">2FA toevoegen</string>
<string name="otpauth_error">2FA kon niet toegevoegd worden</string>
<string name="search_web">Het net doorzoeken</string>
<string name="export_to_file">Naar bestand exporteren</string>
<string name="csv_delimiter">Wat voor separator wil je gebruiken (CSV-bestand)?</string>
<string name="delimiter_comma">Komma</string>
<string name="delimiter_semicolon">Kommapunt</string>
<string name="csv_allow_binary">Binaire data in outputbestand toestaan?</string>
<string name="no">Nee</string>
<string name="csv_allow_binary_message">Waarschijnlijk wil je dit niet toestaan omdat daardoor het bestandsformaat breekt en misschien niet meer toor teksteditoren leesbaar is.</string>
<string name="error_saving_binary_data">Kon bestand niet opslaan</string>
<string name="open_with_url">Niet herkende data met URL openen</string>
<string name="pick_list_separator">Hoe de lijstonderdelen te separeren?</string>
<string name="saved_in_downloads">In downloads opgeslagen</string>
<string name="vevent_failed">Kon niet aan kalender toevoegen</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
</resources>
+17 -2
View File
@@ -7,8 +7,8 @@
<string name="content">Conteúdo</string>
<string name="binary_data">(binários)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d cacractere</item>
<item quantity="other">%1$s\n%2$d caracteres</item>
<item quantity="one">%2$d caractere, %1$s</item>
<item quantity="other">%2$d caracteres, %1$s</item>
</plurals>
<string name="toggle_flash">Ativar lanterna</string>
<string name="share">Compartilhar</string>
@@ -29,6 +29,7 @@
<string name="switch_camera">Trocar câmera</string>
<string name="history">Histórico</string>
<string name="preferences">Preferências</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="use_history">Salvar histórico de escaneamento</string>
<string name="open_with_url">Abrir dados desconhecidos com URL</string>
<string name="really_remove_scan">Realmente deseja remover o escaneamento?</string>
@@ -55,4 +56,18 @@
<string name="vcard_failed">NNão foi possível adicionar para os contatos</string>
<string name="vevent_add">Adicionar ao calendário</string>
<string name="vevent_failed">Não foi possível adicionar ao calendário</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
<string name="search_web">Search the web</string>
<string name="export_to_file">Export to file</string>
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
<string name="delimiter_comma">Comma</string>
<string name="delimiter_semicolon">Semicolon</string>
<string name="csv_allow_binary">Allow binary data in output file?</string>
<string name="no">No</string>
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
<string name="saved_in_downloads">Saved in downloads</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
</resources>
@@ -0,0 +1,69 @@
<resources>
<string name="no_camera_no_fun">Это приложение не имеет никакого смысла без доступа к камере</string>
<string name="camera_error">Не удается получить доступ к камере, пожалуйста, попробуйте еще раз</string>
<string name="scan_code">Сканирование кода</string>
<string name="compose_barcode">Создать штрих-код</string>
<string name="decode_barcode">Декодировать штрих-код</string>
<string name="content">Текст</string>
<string name="binary_data">(двоичные данные)</string>
<string name="toggle_flash">Включить вспышку</string>
<string name="share">Отправить</string>
<string name="copy_to_clipboard">Скопировать в буфер обмена</string>
<string name="put_into_clipboard">Положить в буфер обмена</string>
<string name="open_url">Открыть URL</string>
<string name="cannot_resolve_action">Ни одно приложение не может открыть это</string>
<string name="pick_search_engine">Выберите поисковую систему</string>
<string name="format">Формат</string>
<string name="size">Размер в пикселях</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Введите текст здесь</string>
<string name="encode">Создать</string>
<string name="error_no_content">Нет данных</string>
<string name="error_encoding_barcode">Штрих-код не может быть создан</string>
<string name="view_barcode">Просмотреть штрих-код</string>
<string name="info">Информация</string>
<string name="switch_camera">Переключить камеру</string>
<string name="history">История</string>
<string name="preferences">Настройки</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="use_history">Сохранить историю сканирования</string>
<string name="open_with_url">Открыть неизвестные данные с помощью URL</string>
<string name="really_remove_scan">Действительно удалить сканирование?</string>
<string name="really_remove_all_scans">Действительно удалить всю историю сканирования?</string>
<string name="clear_history">Очистить историю</string>
<string name="no_barcode_found">Штрих-код не найден</string>
<string name="pick_list_separator">Как разделить элементы списка?</string>
<string name="separator_line_break">Разрыв строки</string>
<string name="separator_ruler">Линейка</string>
<string name="save_as_file_name">Сохранить как файл?</string>
<string name="file_name">Имя файла</string>
<string name="error_saving_binary_data">Не удается сохранить файл</string>
<string name="error_file_exists">Файл уже существует</string>
<string name="connect_to_wifi">Подключиться к WiFi</string>
<string name="wifi_config_failed">Не удалось настроить WiFi</string>
<string name="wifi_added">WiFi добавлен</string>
<string name="sms_send">Отправить СМС</string>
<string name="sms_error">Не удалось отправить СМС</string>
<string name="tel_dial">Наберите номер</string>
<string name="tel_error">Не удалось набрать номер</string>
<string name="mail_send">Отправить письмо</string>
<string name="mail_error">Не удалось отправить почту</string>
<string name="vcard_add">Добавить в контакты</string>
<string name="vcard_failed">Не удалось добавить в контакты</string>
<string name="vevent_add">Добавить в календарь</string>
<string name="vevent_failed">Не удалось добавить в календарь</string>
<string name="otpauth_add">Добавить 2FA</string>
<string name="otpauth_error">Не удалось добавить 2FA</string>
<string name="search_web">Search the web</string>
<string name="export_to_file">Export to file</string>
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
<string name="delimiter_comma">Comma</string>
<string name="delimiter_semicolon">Semicolon</string>
<string name="csv_allow_binary">Allow binary data in output file?</string>
<string name="no">No</string>
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
<string name="saved_in_downloads">Saved in downloads</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
</resources>
+17 -2
View File
@@ -7,8 +7,8 @@
<string name="content">内容</string>
<string name="binary_data">(二进制数据)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d 字符</item>
<item quantity="other">%1$s\n%2$d 字符</item>
<item quantity="one">%2$d 字符, %1$s</item>
<item quantity="other">%2$d 字符, %1$s</item>
</plurals>
<string name="toggle_flash">切换闪光灯</string>
<string name="share">分享</string>
@@ -29,6 +29,7 @@
<string name="switch_camera">切换摄像头</string>
<string name="history">历史</string>
<string name="preferences">首选项</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="use_history">保存扫描历史</string>
<string name="open_with_url">使用URL打开未知数据</string>
<string name="really_remove_scan">确定删除记录?</string>
@@ -55,4 +56,18 @@
<string name="vcard_failed">无法添加到通讯录</string>
<string name="vevent_add">添加到日历</string>
<string name="vevent_failed">无法添加到日历</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
<string name="search_web">Search the web</string>
<string name="export_to_file">Export to file</string>
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
<string name="delimiter_comma">Comma</string>
<string name="delimiter_semicolon">Semicolon</string>
<string name="csv_allow_binary">Allow binary data in output file?</string>
<string name="no">No</string>
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
<string name="saved_in_downloads">Saved in downloads</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
</resources>
+19 -2
View File
@@ -7,8 +7,8 @@
<string name="content">内容</string>
<string name="binary_data">(2進制碼)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d 個字符</item>
<item quantity="other">%1$s\n%2$d 個字符</item>
<item quantity="one">%2$d 個字符, %1$s</item>
<item quantity="other">%2$d 個字符, %1$s</item>
</plurals>
<string name="toggle_flash">閃光燈</string>
<string name="share">分享</string>
@@ -28,6 +28,7 @@
<string name="info">資訊</string>
<string name="switch_camera">切換相機</string>
<string name="history">歷史</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="use_history">儲存掃描歷史</string>
<string name="really_remove_scan">真的要移除掃描記錄?</string>
<string name="really_remove_all_scans">真的要移除所有掃描記錄?</string>
@@ -53,4 +54,20 @@
<string name="vcard_failed">不能添加至聯絡人</string>
<string name="vevent_add">添加至行事曆</string>
<string name="vevent_failed">不能添加至行事曆</string>
<string name="preferences">Preferences</string>
<string name="open_with_url">Preferences</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
<string name="search_web">Search the web</string>
<string name="export_to_file">Export to file</string>
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
<string name="delimiter_comma">Comma</string>
<string name="delimiter_semicolon">Semicolon</string>
<string name="csv_allow_binary">Allow binary data in output file?</string>
<string name="no">No</string>
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
<string name="saved_in_downloads">Saved in downloads</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
</resources>
+2 -1
View File
@@ -1,10 +1,11 @@
<resources>
<color name="primary">#222</color>
<color name="primary_dark">#111</color>
<color name="accent">#b6d46f</color>
<color name="accent_dark">#a6c45f</color>
<color name="background_color">#111</color>
<color name="background_progress">#8000</color>
<color name="button_text">#fff</color>
<color name="separator">#333</color>
<color name="crop_bound">#fff</color>
<color name="candidate">#80b6d46f</color>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="csv_delimiters_names">
<item>@string/delimiter_comma</item>
<item>@string/delimiter_semicolon</item>
</string-array>
<string-array name="csv_delimiters_values">
<item>,</item>
<item>;</item>
</string-array>
</resources>
+18 -3
View File
@@ -7,8 +7,8 @@
<string name="content">Content</string>
<string name="binary_data">(binary data)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d character</item>
<item quantity="other">%1$s\n%2$d characters</item>
<item quantity="one">%2$d character, %1$s</item>
<item quantity="other">%2$d characters, %1$s</item>
</plurals>
<string name="toggle_flash">Toggle flash</string>
<string name="share">Share</string>
@@ -29,6 +29,7 @@
<string name="switch_camera">Switch camera</string>
<string name="history">History</string>
<string name="preferences">Preferences</string>
<string name="open_immediately">Skip inspection and open contents immediately</string>
<string name="use_history">Save scan history</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="really_remove_scan">Really remove scan?</string>
@@ -38,7 +39,7 @@
<string name="pick_list_separator">How to separate list items?</string>
<string name="separator_line_break">Line break</string>
<string name="separator_ruler">Ruler</string>
<string name="save_as_file_name">Save as file?</string>
<string name="save_as_file_name">Save as in Downloads file?</string>
<string name="file_name">File name</string>
<string name="error_saving_binary_data">Cannot save file</string>
<string name="error_file_exists">File already exists</string>
@@ -55,4 +56,18 @@
<string name="vcard_failed">Could not add to contacts</string>
<string name="vevent_add">Add to calendar</string>
<string name="vevent_failed">Could not add to calendar</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
<string name="search_web">Search the web</string>
<string name="export_to_file">Export to file</string>
<string name="csv_delimiter">Which delimiter should be used (CSV-File)?</string>
<string name="delimiter_comma">Comma</string>
<string name="delimiter_semicolon">Semicolon</string>
<string name="csv_allow_binary">Allow binary data in output file?</string>
<string name="no">No</string>
<string name="csv_allow_binary_message">You probably don\'t want to allow that, as it will break the file format and maybe isn\'t readable by text editors anymore.</string>
<string name="saved_in_downloads">Saved in downloads</string>
<string name="rotate_image_cw">Rotate image clockwise</string>
<string name="pick_file">Pick image file</string>
<string name="pick_code_to_scan">Pick code to scan</string>
</resources>
+16 -1
View File
@@ -1,13 +1,19 @@
<resources>
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorPrimaryDark">@android:color/transparent</item>
<item name="colorAccent">@color/accent</item>
<item name="android:windowBackground">@drawable/background_window</item>
</style>
<style name="SplashTheme" parent="@style/AppTheme">
<item name="android:windowBackground">@drawable/background_splash</item>
</style>
<style
name="SecondaryText"
parent="@android:style/Widget.TextView">
<item name="android:textSize">12sp</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style
name="Button"
parent="@android:style/Widget.Button">
@@ -21,4 +27,13 @@
<item name="android:paddingRight">@dimen/button_padding</item>
<item name="android:maxLines">1</item>
</style>
<style
name="FAB"
parent="Widget.Design.FloatingActionButton">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_margin">20dp</item>
<item name="android:layout_gravity">bottom|end</item>
<item name="elevation">4dp</item>
</style>
</resources>
@@ -0,0 +1,22 @@
package de.markusfisch.android.binaryeye
import junit.framework.TestCase.fail
fun simpleFail(message: String = "Unknown reason, but failed"): Nothing {
fail(message)
throw IllegalStateException("You should never have reached this point in the code, but anyways: $message")
}
inline fun <reified E : Throwable> assertThrows(
message: String = "Unknown reason, but didn't failed even though should fail",
block: () -> Unit
) {
try {
block()
fail(message)
} catch (e: Throwable) {
if (e !is E) {
fail("Got unexpected other Throwable: ${e::class.java.simpleName}\n$e")
}
}
}
@@ -1,10 +1,10 @@
package de.markusfisch.android.binaryeye.actions.wifi
import de.markusfisch.android.binaryeye.simpleFail
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNull
import junit.framework.TestCase.assertTrue
import junit.framework.TestCase.fail
import org.junit.Test
class WifiConfigurationFactoryTest {
@@ -84,15 +84,10 @@ class WifiConfigurationFactoryTest {
assertFalse(info.hidden)
}
private fun simpleFail(message: String = "Unknown reason, but failed"): Nothing {
fail(message)
throw IllegalStateException("You should never have reached this point in the code, but anyways: $message")
}
private fun simpleDataAccessor(wifiString: String): WifiConfigurationFactory.SimpleDataAccessor {
val map = WifiConfigurationFactory.parseMap(wifiString)
?: simpleFail("parsing map of valid string fails ($wifiString)")
?: simpleFail("parsing map of valid string fails ($wifiString)")
return WifiConfigurationFactory.SimpleDataAccessor.of(map)
?: simpleFail("could not create SimpleDataAccessor of (potentially) valid map ($map of $wifiString)")
?: simpleFail("could not create SimpleDataAccessor of (potentially) valid map ($map of $wifiString)")
}
}
+3 -7
View File
@@ -1,8 +1,8 @@
buildscript {
ext.kotlin_version = '1.3.31'
ext.tools_version = '3.4.2'
ext.kotlin_version = '1.3.61'
ext.tools_version = '3.5.3'
ext.build_tools_version = '28.0.3'
ext.sdk_version = 28
ext.sdk_version = 29
ext.support_version = '25.3.1'
repositories {
@@ -16,10 +16,6 @@ buildscript {
}
}
plugins {
id 'org.sonarqube' version '2.6.2'
}
allprojects {
repositories {
google()
+2
View File
@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx1536m
kotlin.code.style=official
+1 -1
View File
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip
-2
View File
@@ -111,8 +111,6 @@ svg/ic_launcher.svg 48
EOF
update app/src/main/res/drawable << EOF
svg/debug/ic_launcher_foreground.svg 108
svg/wallpaper.svg 384
svg/ic_launcher_foreground.svg 108
svg/logo.svg 80
EOF