Compare commits

...
50 Commits
Author SHA1 Message Date
Markus Fisch ecccf34418 Advance version number to 1.68.7 2026-03-20 21:56:20 +01:00
Markus Fisch 4027ce2cc6 Reformat new code 2026-03-20 21:56:07 +01:00
Markus Fisch 209e6c5c30 Add icons to preferences screen
Assign existing action icons to preference rows in the settings
screen so related options are easier to scan.
2026-03-20 21:55:37 +01:00
Markus Fisch 5e29f3fbe9 Update kotlin version 2026-03-20 21:26:05 +01:00
Markus Fisch ba3d63277a Update gradle wrapper 2026-03-20 21:15:56 +01:00
Markus Fisch fe34b7209d Fix quiet zone for recreated barcodes
Older history entries can miss the stored symbol matrix and
therefore fall back to re-encoding the barcode content.

After the ZXingCpp 3 migration, that fallback used a zero margin,
so recreated barcodes from those records lost their quiet zone.
2026-03-20 21:08:20 +01:00
Markus Fisch 0efdca7b4a Remove unnecessary null checking of error message
Because it's always non-null now.
2026-03-20 15:46:38 +01:00
Markus Fisch 827fbedc1f Fix automatic screen brightening
And remove lateinit from `brightenScreenItem` to check
if it's null or not.

Also delay automatic brightening to give Android a bit
more time to sort things out.
2026-03-20 15:46:38 +01:00
Markus Fisch 107b5bafd4 Update history listing on resume
To immediately show label changes, for example.
2026-03-20 15:46:38 +01:00
Markus Fisch 19a71480fa Update tools version 2026-03-20 15:46:38 +01:00
Markus Fisch 909e97a591 Map zoom bar to zoom ratio instead of linear zoom
Linear zoom 0 switches to the ultra-wide lens on devices
that support it, causing blurry images. Mapping to zoom ratios
from 1.0x to maxZoomRatio keeps the main lens in use.
2026-03-17 21:54:34 +01:00
Markus Fisch bc2be96871 Simplify updating the flash FAB
It's much simpler if we invert the availability parameter.
2026-03-20 10:53:39 +01:00
Markus Fisch d834621d99 Reset stored zoom level and always start at 0
Because the previous zoom levels were for the legacy camera API
and they don't carry over to CameraX.

With CameraX, it's also better to start with no zoom.
2026-03-20 10:53:39 +01:00
Markus Fisch ad83be1a60 Use ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY
Which greatly improves the initial sharpness of the camera image.

Without this, despite autofocus, the initial camera frame can be
a bit blurry on some devices.
2026-03-20 10:53:39 +01:00
Markus Fisch d65ea02b49 Focus on taps but not on swipes 2026-03-20 10:53:32 +01:00
Markus Fisch c8d1357ceb Use CONTINUOUS_PICTURE AF
CameraX defaults to CONTINUOUS_VIDEO autofocus, which trades
focus accuracy for smooth transitions. For barcode scanning we
need the sharpest possible lock, so switch to CONTINUOUS_PICTURE.

But this still isn't enough to have the same quick autofocus the
legacy API had.
2026-03-20 10:52:44 +01:00
Markus Fisch 4c05d046e6 Set CONTROL_SCENE_MODE_BARCODE
To configure the camera for barcode reading (macro focus range,
disabled stabilisation). Same way the legacy Camera API's
SCENE_MODE_BARCODE did.

Unfortunately, this mode isn't supported by some camera drivers
and it doesn't really improve focusing near barcodes on my test
devices.
2026-03-20 10:52:44 +01:00
Markus Fisch 4423ee62c8 Migrate to CameraX
Since the app is on minSDK 21 now, and because some camera
drivers apparently don't support the torch feature through
the legacy API.
2026-03-20 10:52:21 +01:00
Markus Fisch bc69f99f8b Advance version number to 1.68.6 2026-03-14 14:53:30 +01:00
Markus Fisch 6b088031fd Remove unused plural quantities
Delete plural forms that Android never selects for the affected
locales, based on the lint report.
2026-03-13 22:43:31 +01:00
Markus Fisch b27d03e9c0 Ask for confirmation before deleting a scan
From the decode view.
2026-03-13 22:37:43 +01:00
Markus Fisch 9a13838f28 Fix RTL hardcoded layout attributes
Remove redundant left/right layout attributes and keep the
start/end variants that match RTL-aware behavior.
2026-03-13 22:34:08 +01:00
Markus Fisch cce2a460f6 Update material library 2026-03-12 23:34:05 +01:00
Markus Fisch 36352ecde2 Apply KTX extensions from lint
Replace lint-flagged platform calls with their Kotlin extension
equivalents and add the core-ktx dependency they rely on.
2026-03-13 16:33:54 +01:00
Markus Fisch 9a0bc324cb Migrate support libraries to AndroidX
Replace legacy support dependencies and namespaces with AndroidX and
Material equivalents so the app builds on the modern stack again.

Update Kotlin imports, XML widget class names, and manifest provider
references, then fix AndroidX nullability fallout and missing lifecycle
super calls reported by lint.
2026-03-12 23:19:02 +01:00
Markus Fisch 6dabafd6a3 Replace fragments with standalone activities
Remove MainActivity as a single fragment host and convert each
fragment (Barcode, Decode, Encode, History, Profiles, Automated
Actions, Network Suggestions) into its own Activity.

Update CameraActivity to launch each Activity directly instead of
routing through MainActivity intent factories.
2026-03-12 23:10:18 +01:00
Markus Fisch dde4b1e0bd Update gradle wrapper 2026-03-12 22:58:58 +01:00
Markus Fisch 8a8c7bd1ff Remove values-v21 for minSDK is 21 now
No longer required. Merged the Button style with
`res/values/styles.xml`.
2026-03-12 22:16:02 +01:00
Markus Fisch a2aed42e25 Remove Scan parcel compatibility code
Stop transporting Scan as a Parcelable and keep only the
bundle-based handoff for decoded results.

This removes version-sensitive parcel decoding entirely and makes
scan navigation stable across app updates.
2026-03-12 21:51:55 +01:00
Markus Fisch a38b7b647f Use bundles for decoded scan handoff
Replace Scan parcelables in the decode navigation flow with plain
Bundle data.

This avoids BadParcelableException when Android lazily unparcels a
Scan written with a different layout or legacy ZXingCpp format names.
A bundle of primitives is stable across app updates and does not depend
on custom parcel compatibility.
2026-03-12 21:44:57 +01:00
Markus Fisch 76e82ed842 Migrate to ZXingCpp 3.x and raise minSdk to 21
Which is required (to build and run) version 3 of ZXingCpp,
because ZXingCpp requires C++20, which in turn requires an
NDK with minSdk 21.

So to use the new version, we need to raise the minSdk
and cut off old devices just because C++17 didn't cut the
mustard anymore. Well, I guess this had to happen at some
point, and because SDK 21 is 12 years old now, it's probably
even reasonable to raise the bar.
2026-03-12 20:53:23 +01:00
Markus Fisch fd54bc604e Advance version number to 1.68.5 2026-03-02 19:07:06 +01:00
Markus Fisch 59b62ecbc4 Revert ZXingC++ to v2.3.0.4
Because 3.x requires C++20, what requires minSdk 21.

While ZXingC++ 3.x compiles and runs on some devices (with the
current minSdk), the library does crash on other devices.

So updating to ZXingC++ 3.x requires more work, unfortunately.

This reverts commit
a90bdad117
4b2eb80612
7a59126312
2026-03-02 18:59:41 +01:00
Markus Fisch de6a2863f3 Advance version number to 1.68.4 2026-02-24 23:57:27 +01:00
Markus Fisch 4b2eb80612 Fix margin for 1D barcodes
Since ZXingCpp v3.0.0+ changed the default from -1 to 0.

Using -1 results in an exception in `BitMatrix::setRegion()`
because of invalid top/left coordinates.
2026-02-24 23:52:04 +01:00
Markus Fisch 2de40d2277 Advance version number to 1.68.3 2026-02-23 23:13:10 +01:00
Markus Fisch 7673b7cb0d Fallback to getParcelableExtra(name)
When getting the DECODE extra.

`getParcelableExtra(name, Class)` can throw on some versions
of Android when the ClassLoader associated with the Bundle's
lazy-decoded value is null in certain scenarios (e.g., after
an activity restart where the system re-delivers the intent).
Simply fallback to the "deprecated" `getParcelableExtra(name)`
which works in this case, because it doesn't verify the
parcelable's class against Scan::class.java.
2026-02-23 21:00:30 +01:00
Markus Fisch 097c5e4453 Improve getting fragment for MainActivity
And properly finish the MainActivity for malformed intents.
2026-02-23 17:21:30 +01:00
Markus Fisch 1fab30089e Advance version number to 1.68.2 2026-02-22 21:15:25 +01:00
Markus Fisch f9bf16dd47 Update ZXingCpp 2026-02-22 21:03:20 +01:00
Markus Fisch 7a59126312 Use 1 as the new default for margin/quiet zone
Since ZXingCpp v3.0.0+ changed the default from -1 to 0.

Using -1 results in an exception in `BitMatrix::setRegion()`
because of invalid top/left coordinates.
2026-02-22 20:58:24 +01:00
Markus Fisch 694c854650 Advance version number to 1.68.1 2026-02-21 21:50:51 +01:00
Markus Fisch b1cd12ec77 Validate frame data before giving it to ZXing
And refactor preview callback to use an early exit for clarity.

According to Vitals, `frameData` can apparently be `null` on
some devices sometimes.
2026-02-21 21:40:38 +01:00
Markus Fisch 0c27b15025 Check if fragment is still added
Before setting brightness, which may happen later because
it's called from the queue.
2026-02-21 21:20:31 +01:00
Markus Fisch 5de950bdbf Update tools version 2026-02-21 19:14:54 +01:00
Markus Fisch ae9e304ef6 Handle bad decoded intent parcelables safely
Guard reading the `decoded` Scan extra in MainActivity against
framework unparcel exceptions. Some external or malformed intents can
trigger a NullPointerException in Parcel before app-level checks run.

Return null when unparceling fails so the app falls back to the default
DecodeFragment path instead of crashing on launch.
2026-02-21 19:13:47 +01:00
Dominik StadlerandMarkus Fisch c4cc14554b Clear search-term on empty input, not on "Cancel"
Clearing search-term on cancel and not on empty input is
unexpected. "Cancel" usually does not change the state.

Rather clear the searchTerm on empty input and do not
handle the "Cancel"-button at all.
2026-02-20 13:52:50 +01:00
Dominik StadlerandMarkus Fisch d6afccf562 Apply some IDE suggestions 2026-02-20 13:52:50 +01:00
Markus Fisch a90bdad117 Update ZXingCpp
And remove the now missing `returnCodabarStartEnd` feature.
2026-02-13 19:05:35 +01:00
solokotandGitHub 52d4f082f2 Update Russian translation
Small Improvements
2026-02-12 19:45:38 +01:00
90 changed files with 1529 additions and 1539 deletions
+29
View File
@@ -1,5 +1,34 @@
# Change Log
## 1.68.7
* Add icons to preferences screen
* Update history listing on resume
* Fix quiet zone for recreated barcodes
* Fix automatic screen brightening
* Migrate to CameraX
## 1.68.6
* Update ZXingCpp
* Ask for confirmation before deleting a scan from the result screen
## 1.68.5
* Downgrade ZXingCpp for compatibilty
## 1.68.4
* Fix generating 1D barcodes
## 1.68.3
* Stability improvement
## 1.68.2
* Fix barcode regeneration
## 1.68.1
* Improve UI of search barcode dialog
* Stability improvements
* Update ZXingCpp
* Update Russian translation
## 1.68.0
* Add automated actions
* Improve managing profiles
+13 -10
View File
@@ -5,11 +5,11 @@ android {
compileSdk sdk_version
defaultConfig {
minSdk 9
minSdk 21
targetSdk sdk_version
versionCode 154
versionName '1.68.0'
versionCode 161
versionName '1.68.7'
}
signingConfigs {
@@ -68,17 +68,20 @@ dependencies {
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2"
implementation "androidx.core:core-ktx:1.17.0"
// Support
implementation "com.android.support:appcompat-v7:$support_version"
implementation "com.android.support:design:$support_version"
implementation "com.android.support:preference-v7:$support_version"
implementation "com.android.support:preference-v14:$support_version"
implementation "androidx.appcompat:appcompat:1.7.1"
implementation "com.google.android.material:material:1.13.0"
implementation "androidx.preference:preference:1.2.1"
// External
implementation 'com.github.markusfisch:CameraView:1.10.0'
implementation 'com.github.markusfisch:ScalingImageView:1.4.1'
implementation 'com.github.markusfisch:zxing-cpp:v2.3.0.4'
implementation 'androidx.camera:camera-core:1.4.2'
implementation 'androidx.camera:camera-camera2:1.4.2'
implementation 'androidx.camera:camera-lifecycle:1.4.2'
implementation 'androidx.camera:camera-view:1.4.2'
implementation 'com.github.markusfisch:ScalingImageView:1.4.3'
implementation 'com.github.markusfisch:zxing-cpp:v3.0.2.1'
// Testing
testImplementation 'junit:junit:4.13.2'
Binary file not shown.
+24 -3
View File
@@ -21,7 +21,7 @@
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"
android:required="false" />
<uses-sdk tools:overrideLibrary="android.support.v14.preference"/>
<uses-sdk tools:overrideLibrary="androidx.preference"/>
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>
@@ -112,7 +112,13 @@
android:name=".activity.WelcomeActivity"
android:exported="true"/>
<activity
android:name=".activity.MainActivity"
android:name=".activity.PreferencesActivity"
android:exported="true"/>
<activity
android:name=".activity.HistoryActivity"
android:exported="true"/>
<activity
android:name=".activity.EncodeActivity"
android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
@@ -130,6 +136,21 @@
<data android:scheme="binaryeye" android:host="encode"/>
</intent-filter>
</activity>
<activity
android:name=".activity.DecodeActivity"
android:exported="false"/>
<activity
android:name=".activity.BarcodeActivity"
android:exported="false"/>
<activity
android:name=".activity.ProfilesActivity"
android:exported="false"/>
<activity
android:name=".activity.AutomatedActionsActivity"
android:exported="false"/>
<activity
android:name=".activity.NetworkSuggestionsActivity"
android:exported="false"/>
<service
android:name=".service.ScanTileService"
android:icon="@drawable/ic_action_scan"
@@ -142,7 +163,7 @@
</intent-filter>
</service>
<provider
android:name="android.support.v4.content.FileProvider"
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
@@ -2,7 +2,7 @@ package de.markusfisch.android.binaryeye.actions.mail
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.net.toUri
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.IntentAction
@@ -24,7 +24,7 @@ object MailAction : IntentAction() {
)?.groupValues?.get(1) ?: return null
return Intent(
Intent.ACTION_SENDTO,
Uri.parse("mailto:$mailWithMessage")
"mailto:$mailWithMessage".toUri()
)
}
}
@@ -2,7 +2,7 @@ package de.markusfisch.android.binaryeye.actions.mail
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.net.toUri
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.IntentAction
@@ -26,7 +26,7 @@ object MatMsgAction : IntentAction() {
}
return Intent(
Intent.ACTION_SENDTO,
Uri.parse("mailto:")
"mailto:".toUri()
).apply {
mm.to?.let {
putExtra(Intent.EXTRA_EMAIL, arrayOf(mm.to))
@@ -2,7 +2,7 @@ package de.markusfisch.android.binaryeye.actions.sms
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.net.toUri
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.IntentAction
@@ -27,7 +27,7 @@ object SmsAction : IntentAction() {
} ?: return null
return Intent(
Intent.ACTION_SENDTO,
Uri.parse("smsto:$number")
"smsto:$number".toUri()
).apply {
if (message.isNotEmpty()) {
putExtra("sms_body", message)
@@ -3,7 +3,6 @@ package de.markusfisch.android.binaryeye.actions.vtype.vcard
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.ContactsContract
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.IntentAction
@@ -98,38 +97,36 @@ object VCardAction : IntentAction() {
}
putExtra(ContactsContract.Intents.Insert.POSTAL, addr.value.locationFormat)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
val urls = info["URL"]?.mapNotNull {
it.value.takeIf { url -> url.isNotBlank() }?.let { url ->
ContentValues(2).apply {
put(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE
)
put(ContactsContract.CommonDataKinds.Website.URL, url)
}
val urls = info["URL"]?.mapNotNull {
it.value.takeIf { url -> url.isNotBlank() }?.let { url ->
ContentValues(2).apply {
put(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE
)
put(ContactsContract.CommonDataKinds.Website.URL, url)
}
}
val bDays = info["BDAY"]?.mapNotNull {
it.value.takeIf { day -> day.isNotBlank() }?.let { day ->
ContentValues(3).apply {
put(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE
)
put(
ContactsContract.CommonDataKinds.Event.TYPE,
ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY
)
put(ContactsContract.CommonDataKinds.Event.START_DATE, day)
}
}
}
putParcelableArrayListExtra(
ContactsContract.Intents.Insert.DATA,
ArrayList(listOfNotNull(urls, bDays).flatten())
)
}
val bDays = info["BDAY"]?.mapNotNull {
it.value.takeIf { day -> day.isNotBlank() }?.let { day ->
ContentValues(3).apply {
put(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE
)
put(
ContactsContract.CommonDataKinds.Event.TYPE,
ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY
)
put(ContactsContract.CommonDataKinds.Event.START_DATE, day)
}
}
}
putParcelableArrayListExtra(
ContactsContract.Intents.Insert.DATA,
ArrayList(listOfNotNull(urls, bDays).flatten())
)
}
}
@@ -2,9 +2,7 @@ package de.markusfisch.android.binaryeye.actions.vtype.vevent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.CalendarContract
import android.support.annotation.RequiresApi
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.IntentAction
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
@@ -21,14 +19,10 @@ object VEventAction : IntentAction() {
get() = R.string.vevent_failed
override fun canExecuteOn(data: ByteArray): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return false
}
val type = VTypeParser.parseVType(String(data))
return type == "VEVENT" || type == "VCALENDAR"
}
@RequiresApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
override suspend fun createIntent(context: Context, data: ByteArray): Intent? {
val info = VTypeParser.parseMap(String(data))
@@ -8,7 +8,7 @@ import android.net.wifi.WifiManager
import android.net.wifi.WifiNetworkSuggestion
import android.os.Build
import android.provider.Settings
import android.support.annotation.RequiresApi
import androidx.annotation.RequiresApi
import java.util.Locale
/**
@@ -113,34 +113,30 @@ object WifiConnector {
get() = inputMap["I"] ?: ""
internal val eapMethod: Int?
get() = if (inputMap["E"].isNullOrEmpty()) {
requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) {
WifiEnterpriseConfig.Eap.NONE
}
WifiEnterpriseConfig.Eap.NONE
} else when (inputMap["E"]?.uppercase()) {
"AKA" -> requireSdk(Build.VERSION_CODES.LOLLIPOP) { WifiEnterpriseConfig.Eap.AKA }
"AKA" -> WifiEnterpriseConfig.Eap.AKA
"AKA_PRIME" -> requireSdk(Build.VERSION_CODES.M) { WifiEnterpriseConfig.Eap.AKA_PRIME }
"NONE" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.NONE }
"PEAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.PEAP }
"PWD" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.PWD }
"SIM" -> requireSdk(Build.VERSION_CODES.LOLLIPOP) { WifiEnterpriseConfig.Eap.SIM }
"TLS" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.TLS }
"TTLS" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.TTLS }
"NONE" -> WifiEnterpriseConfig.Eap.NONE
"PEAP" -> WifiEnterpriseConfig.Eap.PEAP
"PWD" -> WifiEnterpriseConfig.Eap.PWD
"SIM" -> WifiEnterpriseConfig.Eap.SIM
"TLS" -> WifiEnterpriseConfig.Eap.TLS
"TTLS" -> WifiEnterpriseConfig.Eap.TTLS
"UNAUTH_TLS" -> requireSdk(Build.VERSION_CODES.N) { WifiEnterpriseConfig.Eap.UNAUTH_TLS }
else -> null
}
internal val phase2Method: Int?
get() = if (inputMap["PH2"].isNullOrEmpty()) {
requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) {
WifiEnterpriseConfig.Phase2.NONE
}
WifiEnterpriseConfig.Phase2.NONE
} else when (inputMap["PH2"]?.uppercase()) {
"AKA" -> requireSdk(Build.VERSION_CODES.O) { WifiEnterpriseConfig.Phase2.AKA }
"AKA_PRIME" -> requireSdk(Build.VERSION_CODES.O) { WifiEnterpriseConfig.Phase2.AKA_PRIME }
"GTC" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.GTC }
"MSCHAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.MSCHAP }
"MSCHAPV2" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.MSCHAPV2 }
"NONE" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.NONE }
"PAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.PAP }
"GTC" -> WifiEnterpriseConfig.Phase2.GTC
"MSCHAP" -> WifiEnterpriseConfig.Phase2.MSCHAP
"MSCHAPV2" -> WifiEnterpriseConfig.Phase2.MSCHAPV2
"NONE" -> WifiEnterpriseConfig.Phase2.NONE
"PAP" -> WifiEnterpriseConfig.Phase2.PAP
"SIM" -> requireSdk(Build.VERSION_CODES.O) { WifiEnterpriseConfig.Phase2.SIM }
else -> null
}
@@ -265,7 +261,7 @@ object WifiConnector {
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
}
"WPA2-EAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) {
"WPA2-EAP" -> {
passwordWithQuotes?.let {
preSharedKey = it
} ?: return null
@@ -285,7 +281,7 @@ object WifiConnector {
data.eapMethod ?: return null // non valid eapMethod
enterpriseConfig.phase2Method =
data.phase2Method ?: return null // non valid phase2Method
} ?: return null // api isn't high enough
}
}
return this
}
@@ -1,11 +1,9 @@
package de.markusfisch.android.binaryeye.fragment
package de.markusfisch.android.binaryeye.activity
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
@@ -19,31 +17,28 @@ import de.markusfisch.android.binaryeye.automation.AutomatedAction
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
import de.markusfisch.android.binaryeye.view.systemBarListViewScrollListener
class AutomatedActionsFragment : Fragment() {
class AutomatedActionsActivity : ScreenActivity() {
private val actions = ArrayList<AutomatedAction>()
private lateinit var adapter: ActionsAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?
): View? {
val ac = activity ?: return null
ac.setTitle(R.string.automated_actions)
val view = inflater.inflate(
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setTitle(R.string.automated_actions)
val frame = findViewById(R.id.content_frame) as ViewGroup
val view = layoutInflater.inflate(
R.layout.fragment_automated_actions,
container,
frame,
false
)
frame.addView(view)
actions.clear()
actions.addAll(prefs.automatedActions)
val listView = view.findViewById<ListView>(R.id.actions)
listView.emptyView = view.findViewById(R.id.no_actions)
adapter = ActionsAdapter(ac, actions)
adapter = ActionsAdapter(this, actions)
listView.adapter = adapter
listView.setOnScrollListener(systemBarListViewScrollListener)
listView.setOnItemClickListener { _, _, position, _ ->
@@ -60,14 +55,11 @@ class AutomatedActionsFragment : Fragment() {
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
listView.setPaddingFromWindowInsets()
return view
}
private fun editAction(position: Int?) {
val ac = activity ?: return
val action = position?.let { actions[it] }
val view = ac.layoutInflater.inflate(
val view = layoutInflater.inflate(
R.layout.dialog_automated_action,
null
)
@@ -112,7 +104,7 @@ class AutomatedActionsFragment : Fragment() {
typeGroup.check(initialTypeId)
updateTypeSections(initialTypeId)
val dialog = AlertDialog.Builder(ac)
val dialog = AlertDialog.Builder(this)
.setTitle(
if (action == null) {
R.string.automated_action_add
@@ -207,8 +199,7 @@ class AutomatedActionsFragment : Fragment() {
}
private fun confirmRemoveAction(position: Int) {
val ac = activity ?: return
AlertDialog.Builder(ac)
AlertDialog.Builder(this)
.setMessage(R.string.automated_action_remove_confirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
actions.removeAt(position)
@@ -1,14 +1,13 @@
package de.markusfisch.android.binaryeye.fragment
package de.markusfisch.android.binaryeye.activity
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Matrix
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
@@ -55,7 +54,7 @@ import java.util.Locale
import kotlin.math.max
import kotlin.math.min
class BarcodeFragment : Fragment() {
class BarcodeActivity : ScreenActivity() {
private enum class FileType {
PNG, JPG, SVG, TXT
}
@@ -65,36 +64,30 @@ class BarcodeFragment : Fragment() {
private lateinit var imageView: ConfinedScalingImageView
private lateinit var barcode: Barcode<*>
private lateinit var addToHistoryItem: MenuItem
private lateinit var brightenScreenItem: MenuItem
private var contentView: View? = null
private var addToHistoryItem: MenuItem? = null
private var brightenScreenItem: MenuItem? = null
private var currentBrightness = -1f
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?
): View? {
val ac = activity ?: return null
ac.setTitle(R.string.view_barcode)
val view = inflater.inflate(
setTitle(R.string.view_barcode)
val frame = findViewById(R.id.content_frame) as ViewGroup
val view = layoutInflater.inflate(
R.layout.fragment_barcode,
container,
frame,
false
)
frame.addView(view)
contentView = view
// Make `imageView` available for `onPause()`.
imageView = view.findViewById(R.id.barcode)
val bitmap: Bitmap
try {
barcode = arguments?.toBarcode() ?: throw IllegalArgumentException(
barcode = intent?.extras?.toBarcode() ?: throw IllegalArgumentException(
"Illegal arguments"
)
// Catch exceptions from encoding.
@@ -104,11 +97,9 @@ class BarcodeFragment : Fragment() {
if (message.isNullOrEmpty()) {
message = getString(R.string.error_encoding_barcode)
}
message?.let {
ac.toast(message)
}
fragmentManager.popBackStack()
return null
toast(message)
finish()
return
}
imageView.setImageBitmap(bitmap)
@@ -140,8 +131,6 @@ class BarcodeFragment : Fragment() {
imageView.doOnApplyWindowInsets { _, insets ->
imageView.insets.set(insets)
}
return view
}
private fun Bundle.toBarcode(): Barcode<*> {
@@ -182,9 +171,9 @@ class BarcodeFragment : Fragment() {
super.onResume()
if (prefs.brightenScreen) {
// Post to make sure brightenScreenItem is initialized.
view?.post {
contentView?.postDelayed({
brightenScreen()
}
}, 500)
}
}
@@ -198,8 +187,8 @@ class BarcodeFragment : Fragment() {
}
}
override fun onDestroyView() {
super.onDestroyView()
override fun onDestroy() {
super.onDestroy()
parentJob.cancel()
}
@@ -216,8 +205,8 @@ class BarcodeFragment : Fragment() {
barcode.bitmap(),
barcode.format
)
addToHistoryItem.isVisible = false
context.toast(R.string.added_to_history)
addToHistoryItem?.isVisible = false
toast(R.string.added_to_history)
true
}
@@ -232,7 +221,7 @@ class BarcodeFragment : Fragment() {
}
R.id.export_to_file -> {
context.pickFileType(R.string.export_as) { fileType ->
pickFileType(R.string.export_as) { fileType ->
when (fileType) {
FileType.PNG,
FileType.JPG -> askForSize { size ->
@@ -261,10 +250,8 @@ class BarcodeFragment : Fragment() {
}
private fun copyToClipboard(text: String, isSensitive: Boolean = false) {
activity?.apply {
copyToClipboard(text, isSensitive)
toast(R.string.copied_to_clipboard)
}
(this as android.content.Context).copyToClipboard(text, isSensitive)
toast(R.string.copied_to_clipboard)
}
private fun deeplinkToCopy() = createEncodeDeeplink(
@@ -288,21 +275,20 @@ class BarcodeFragment : Fragment() {
// Dialogs do not have a parent view.
@SuppressLint("InflateParams")
private fun askForFileNameAndSave(fileType: FileType, bitmapSize: Int) {
val ac = activity ?: return
// Write permission is only required before Android Q.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
!ac.hasWritePermission {
!hasWritePermission {
askForFileNameAndSave(fileType, bitmapSize)
}
) {
return
}
val view = ac.layoutInflater.inflate(R.layout.dialog_save_file, null)
val view = layoutInflater.inflate(R.layout.dialog_save_file, null)
val editText = view.findViewById<EditText>(R.id.file_name)
editText.setText(
encodeFileName("${barcode.format}_${barcode.content}")
)
AlertDialog.Builder(ac)
AlertDialog.Builder(this)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
val fileName = editText.text.toString()
@@ -344,12 +330,11 @@ class BarcodeFragment : Fragment() {
// Dialogs do not have a parent view.
@SuppressLint("InflateParams")
private fun askForSize(write: (size: Int) -> Unit) {
val ac = activity ?: return
val view = ac.layoutInflater.inflate(R.layout.dialog_size, null)
val view = layoutInflater.inflate(R.layout.dialog_size, null)
val sizeView = view.findViewById<TextView>(R.id.size_display)
val sizeBarView = view.findViewById<SeekBar>(R.id.size_bar)
sizeBarView.initSizeBar(sizeView)
AlertDialog.Builder(ac)
AlertDialog.Builder(this)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
write(getSize(sizeBarView.progress))
@@ -392,13 +377,12 @@ class BarcodeFragment : Fragment() {
mimeType: String,
write: (outputStream: OutputStream) -> Unit
) {
val ac = activity ?: return
scope.launch(Dispatchers.IO) {
val message = ac.writeExternalFile(
val message = writeExternalFile(
fileName, mimeType, write
).toSaveResult()
withContext(Dispatchers.Main) {
ac.toast(message)
toast(message)
}
}
}
@@ -449,18 +433,19 @@ class BarcodeFragment : Fragment() {
}
private fun brightenScreen() {
activity?.let {
currentBrightness = it.getScreenBrightness()
it.setScreenBrightness(1f)
brightenScreenItem.isChecked = true
if (isFinishing || isDestroyed) {
return
}
currentBrightness = getScreenBrightness()
setScreenBrightness(1f)
brightenScreenItem?.isChecked = true
}
private fun restoreScreenBrightness() {
if (currentBrightness > -1f) {
activity?.setScreenBrightness(currentBrightness)
setScreenBrightness(currentBrightness)
currentBrightness = -1f
brightenScreenItem.isChecked = false
brightenScreenItem?.isChecked = false
}
}
@@ -479,10 +464,11 @@ class BarcodeFragment : Fragment() {
private const val MIME_SVG = "image/svg+xmg"
private const val MIME_TXT = "text/plain"
fun newInstance(
fun newIntent(
context: Context,
barcode: Barcode<*>,
colors: Int = 0
): Fragment {
): Intent {
val args = Bundle()
when (barcode) {
is ContentBarcode -> args.putEcLevelMargin(
@@ -495,32 +481,31 @@ class BarcodeFragment : Fragment() {
args.putInt(BIT_MATRIX_HEIGHT, barcode.bitMatrix.height)
args.putByteArray(BIT_MATRIX_DATA, barcode.bitMatrix.data)
}
else -> throw IllegalArgumentException("unknown barcode")
}
args.putContentFormatColors(
barcode.content,
barcode.format,
colors
)
val fragment = BarcodeFragment()
fragment.arguments = args
return fragment
return Intent(context, BarcodeActivity::class.java).apply {
putExtras(args)
}
}
fun <T> newInstance(
fun <T> newIntent(
context: Context,
content: T,
format: BarcodeFormat,
ecLevel: Int = -1,
margin: Int = -1,
margin: Int = 0,
colors: Int = 0
): Fragment {
): Intent {
val args = Bundle()
args.putContentFormatColors(content, format, colors)
args.putEcLevelMargin(ecLevel, margin)
val fragment = BarcodeFragment()
fragment.arguments = args
return fragment
return Intent(context, BarcodeActivity::class.java).apply {
putExtras(args)
}
}
private fun <T> Bundle.putContentFormatColors(
@@ -8,18 +8,33 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Matrix
import android.graphics.Rect
import android.hardware.Camera
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.support.design.widget.FloatingActionButton
import android.support.v7.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.widget.EditText
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.FocusMeteringAction
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.core.TorchState
import androidx.camera.core.resolutionselector.AspectRatioStrategy
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.core.resolutionselector.ResolutionStrategy
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.net.toUri
import com.google.android.material.floatingactionbutton.FloatingActionButton
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.adapter.prettifyFormatName
import de.markusfisch.android.binaryeye.app.PERMISSION_CAMERA
@@ -49,7 +64,7 @@ import de.markusfisch.android.binaryeye.view.scanFeedback
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
import de.markusfisch.android.binaryeye.widget.DetectorView
import de.markusfisch.android.binaryeye.widget.toast
import de.markusfisch.android.cameraview.widget.CameraView
import de.markusfisch.android.binaryeye.zxingcpp.migrateBarcodeFormatName
import de.markusfisch.android.zxingcpp.ZxingCpp
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
import de.markusfisch.android.zxingcpp.ZxingCpp.Binarizer
@@ -58,19 +73,36 @@ import de.markusfisch.android.zxingcpp.ZxingCpp.Result
import de.markusfisch.android.zxingcpp.ZxingCpp.TextMode
import java.io.FileInputStream
import java.util.Scanner
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
class CameraActivity : AppCompatActivity() {
private val frameRoi = Rect()
private val previewRect = Rect()
private val viewBounds = Rect()
private val matrix = Matrix()
private val analyzerExecutor: ExecutorService =
Executors.newSingleThreadExecutor()
private val readerOptions = ReaderOptions(
tryHarder = prefs.tryHarder,
tryRotate = prefs.autoRotate,
tryInvert = true,
tryDownscale = true,
maxNumberOfSymbols = 1,
textMode = TextMode.PLAIN
)
private lateinit var cameraView: CameraView
private lateinit var cameraView: PreviewView
private lateinit var detectorView: DetectorView
private lateinit var zoomBar: SeekBar
private lateinit var flashFab: FloatingActionButton
private var cameraProvider: ProcessCameraProvider? = null
private var camera: Camera? = null
private var currentProfile = prefs.profile
private var shouldStoreSettings = true
private var formatsToRead = setOf<BarcodeFormat>()
@@ -84,19 +116,28 @@ class CameraActivity : AppCompatActivity() {
private var restrictFormat: String? = null
private var searchTerm: Regex? = null
private var ignoreNext: String? = null
private var fallbackBuffer: IntArray? = null
private var requestCameraPermission = true
private var useLocalAverage = false
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
)
when (requestCode) {
PERMISSION_CAMERA -> if (grantResults.isNotEmpty() &&
grantResults[0] != PackageManager.PERMISSION_GRANTED
) {
toast(R.string.camera_error)
PERMISSION_CAMERA -> {
if (grantResults.isNotEmpty() &&
grantResults[0] != PackageManager.PERMISSION_GRANTED
) {
toast(R.string.camera_error)
} else if (grantResults.isNotEmpty()) {
bindCameraUseCases()
}
}
}
}
@@ -106,6 +147,7 @@ class CameraActivity : AppCompatActivity() {
resultCode: Int,
resultData: Intent?
) {
super.onActivityResult(requestCode, resultCode, resultData)
when (requestCode) {
PICK_FILE_RESULT_CODE -> {
if (resultCode == RESULT_OK && resultData != null) {
@@ -133,12 +175,12 @@ class CameraActivity : AppCompatActivity() {
initBars()
cameraView = findViewById(R.id.camera_view) as CameraView
detectorView = findViewById(R.id.detector_view) as DetectorView
zoomBar = findViewById(R.id.zoom) as SeekBar
flashFab = findViewById(R.id.flash) as FloatingActionButton
cameraView = findViewById(R.id.camera_view)
detectorView = findViewById(R.id.detector_view)
zoomBar = findViewById(R.id.zoom)
flashFab = findViewById(R.id.flash)
initCameraView()
initPreviewView()
initZoomBar()
initDetectorView()
@@ -151,7 +193,7 @@ class CameraActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
fallbackBuffer = null
analyzerExecutor.shutdown()
releaseToneGenerators()
}
@@ -171,7 +213,7 @@ class CameraActivity : AppCompatActivity() {
// for this session. Otherwise ActivityCompat.requestPermissions()
// will trigger onResume() again and again.
if (hasCameraPermission(requestCameraPermission)) {
openCamera()
bindCameraUseCases()
}
requestCameraPermission = false
}
@@ -181,7 +223,9 @@ class CameraActivity : AppCompatActivity() {
if (prefs.defaultPreferences.getBoolean(welcomeShownName, false)) {
return false
}
prefs.defaultPreferences.edit().putBoolean(welcomeShownName, true).apply()
prefs.defaultPreferences.edit {
putBoolean(welcomeShownName, true)
}
val packageInfo = packageManager.getPackageInfo(packageName, 0)
val installedSince = System.currentTimeMillis() -
packageInfo.firstInstallTime
@@ -193,14 +237,9 @@ class CameraActivity : AppCompatActivity() {
return false
}
shouldStoreSettings = false
zoomBar.max = 0 // Reset zoom when there are no preferences yet.
zoomBar.progress = INITIAL_ZOOM
currentProfile = prefs.profile
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
recreate()
} else {
finish()
startActivity(Intent(this, CameraActivity::class.java))
}
recreate()
return true
}
@@ -251,30 +290,19 @@ class CameraActivity : AppCompatActivity() {
}
}
private fun openCamera() {
cameraView.openAsync(
CameraView.findCameraId(
@Suppress("DEPRECATION")
if (frontFacing) {
Camera.CameraInfo.CAMERA_FACING_FRONT
} else {
Camera.CameraInfo.CAMERA_FACING_BACK
}
)
)
}
override fun onPause() {
super.onPause()
closeCamera()
unbindCameraUseCases()
if (shouldStoreSettings) {
storeSettings();
storeSettings()
}
shouldStoreSettings = true
}
private fun closeCamera() {
cameraView.close()
private fun unbindCameraUseCases() {
decoding = false
cameraProvider?.unbindAll()
camera = null
}
private fun storeSettings() {
@@ -284,7 +312,6 @@ class CameraActivity : AppCompatActivity() {
override fun onRestoreInstanceState(savedState: Bundle) {
super.onRestoreInstanceState(savedState)
zoomBar.max = savedState.getInt(ZOOM_MAX)
zoomBar.progress = savedState.getInt(ZOOM_LEVEL)
frontFacing = savedState.getBoolean(FRONT_FACING)
bulkMode = savedState.getBoolean(BULK_MODE)
@@ -293,7 +320,6 @@ class CameraActivity : AppCompatActivity() {
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(ZOOM_MAX, zoomBar.max)
outState.putInt(ZOOM_LEVEL, zoomBar.progress)
outState.putBoolean(FRONT_FACING, frontFacing)
outState.putBoolean(BULK_MODE, bulkMode)
@@ -329,7 +355,7 @@ class CameraActivity : AppCompatActivity() {
}
R.id.history -> {
startActivity(MainActivity.getHistoryIntent(this))
startActivity(Intent(this, HistoryActivity::class.java))
true
}
@@ -369,7 +395,7 @@ class CameraActivity : AppCompatActivity() {
}
R.id.preferences -> {
startActivity(MainActivity.getPreferencesIntent(this))
startActivity(Intent(this, PreferencesActivity::class.java))
true
}
@@ -383,18 +409,19 @@ class CameraActivity : AppCompatActivity() {
true
}
else -> super.onOptionsItemSelected(item)
else -> {
super.onOptionsItemSelected(item)
}
}
}
private fun createBarcode() {
startActivity(MainActivity.getEncodeIntent(this))
startActivity(EncodeActivity.newIntent<String>(this))
}
private fun switchCamera() {
closeCamera()
frontFacing = frontFacing xor true
openCamera()
bindCameraUseCases()
}
private fun showRestrictionDialog() {
@@ -432,14 +459,14 @@ class CameraActivity : AppCompatActivity() {
val term = editText.text.toString().trim()
if (!term.isEmpty()) {
searchTerm = term.toRegex()
ignoreNext = null
} else {
searchTerm = null
}
ignoreNext = null
updateTitle()
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
searchTerm = null
ignoreNext = null
updateTitle()
// do not change anything on cancel
}
.show()
}
@@ -447,7 +474,7 @@ class CameraActivity : AppCompatActivity() {
private fun openReadme() {
val intent = Intent(
Intent.ACTION_VIEW,
Uri.parse(getString(R.string.project_url))
getString(R.string.project_url).toUri()
)
execShareIntent(intent)
}
@@ -460,8 +487,13 @@ class CameraActivity : AppCompatActivity() {
.setTitle(R.string.profile)
.setItems(profiles) { _, which ->
val newProfile = when (which) {
0 -> null
else -> profiles[which]
0 -> {
null
}
else -> {
profiles[which]
}
}
if (currentProfile != newProfile) {
storeSettings()
@@ -477,7 +509,13 @@ class CameraActivity : AppCompatActivity() {
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
if (text?.isEmpty() == false) {
startActivity(MainActivity.getEncodeIntent(this, text, true))
startActivity(EncodeActivity.newIntent(this, text).apply {
addFlags(
Intent.FLAG_ACTIVITY_NO_HISTORY or
Intent.FLAG_ACTIVITY_CLEAR_TASK or
Intent.FLAG_ACTIVITY_NEW_TASK
)
})
finish()
return
}
@@ -495,9 +533,13 @@ class CameraActivity : AppCompatActivity() {
val scn = Scanner(fs).useDelimiter("\\A")
if (scn.hasNext()) {
startActivity(
MainActivity.getEncodeIntent(
this, scn.next(), true
)
EncodeActivity.newIntent(this, scn.next()).apply {
addFlags(
Intent.FLAG_ACTIVITY_NO_HISTORY or
Intent.FLAG_ACTIVITY_CLEAR_TASK or
Intent.FLAG_ACTIVITY_NEW_TASK
)
}
)
finish()
}
@@ -506,21 +548,27 @@ class CameraActivity : AppCompatActivity() {
}
}
private fun initCameraView() {
cameraView.setUseOrientationListener(true)
private fun initPreviewView() {
cameraView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updateFrameRoiAndMappingMatrix()
}
@Suppress("ClickableViewAccessibility")
val touchSlop = ViewConfiguration.get(this).scaledTouchSlop
cameraView.setOnTouchListener(object : View.OnTouchListener {
var focus = true
var downY = -1f
var offset = -1f
var progress = 0
var hasSwiped = false
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
event ?: return false
val pos = event.y
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
downY = pos
offset = pos
progress = zoomBar.progress
hasSwiped = false
return true
}
@@ -535,133 +583,40 @@ class CameraActivity : AppCompatActivity() {
maxValue,
max(progress + change.roundToInt(), 0)
)
if (abs(pos - downY) > touchSlop) {
hasSwiped = true
}
return true
}
}
MotionEvent.ACTION_UP -> {
// Stop calling focusTo() as soon as it returns false
// to avoid throwing and catching future exceptions.
if (focus) {
focus = cameraView.focusTo(v, event.x, event.y)
if (focus) {
v?.performClick()
return true
if (!hasSwiped) {
camera?.cameraControl?.apply {
cancelFocusAndMetering()
startFocusAndMetering(
FocusMeteringAction.Builder(
cameraView.meteringPointFactory.createPoint(
event.x,
event.y
)
).build()
)
}
}
v?.performClick()
return true
}
}
return false
}
})
@Suppress("DEPRECATION")
cameraView.setOnCameraListener(object : CameraView.OnCameraListener {
override fun onConfigureParameters(
parameters: Camera.Parameters
) {
zoomBar.visibility = if (parameters.isZoomSupported) {
restoreZoomBarSettings()
val max = parameters.maxZoom
if (zoomBar.max != max) {
zoomBar.max = max
zoomBar.progress = max / 10
storeZoomBarSettings()
}
parameters.zoom = zoomBar.progress
View.VISIBLE
} else {
View.GONE
}
val sceneModes = parameters.supportedSceneModes
sceneModes?.let {
for (mode in sceneModes) {
if (mode == Camera.Parameters.SCENE_MODE_BARCODE) {
parameters.sceneMode = mode
break
}
}
}
CameraView.setAutoFocus(parameters)
updateFlashFab(parameters.flashMode == null)
}
override fun onCameraError() {
this@CameraActivity.toast(R.string.camera_error)
}
override fun onCameraReady(camera: Camera) {
frameMetrics = FrameMetrics(
cameraView.frameWidth,
cameraView.frameHeight,
cameraView.frameOrientation
)
updateFrameRoiAndMappingMatrix()
ignoreNext = null
decoding = true
// These settings can't change while the camera is open.
val options = ReaderOptions(
tryHarder = prefs.tryHarder,
tryRotate = prefs.autoRotate,
tryInvert = true,
tryDownscale = true,
returnCodabarStartEnd = true,
maxNumberOfSymbols = 1,
textMode = TextMode.PLAIN
)
var useLocalAverage = false
camera.setPreviewCallback { frameData, _ ->
if (decoding) {
useLocalAverage = useLocalAverage xor true
ZxingCpp.readByteArray(
frameData,
frameMetrics.width,
frameRoi.left, frameRoi.top,
frameRoi.width(), frameRoi.height(),
frameMetrics.orientation,
options.apply {
// By default, ZXing uses LOCAL_AVERAGE, but
// this does not work well with inverted
// barcodes on low-contrast backgrounds.
binarizer = if (useLocalAverage) {
Binarizer.LOCAL_AVERAGE
} else {
Binarizer.GLOBAL_HISTOGRAM
}
formats = formatsToRead
}
)?.let { results ->
val result = results.first()
val text = result.text
if (text == ignoreNext) {
return@let
}
val term = searchTerm
if (term != null &&
!text.matches(term) &&
!text.contains(term)
) {
ignoreNext = text
errorFeedback()
toast(R.string.does_not_match_search_term)
return@let
}
postResult(result)
decoding = false
}
}
}
}
override fun onPreviewStarted(camera: Camera) {
}
override fun onCameraStopping(camera: Camera) {
camera.setPreviewCallback(null)
}
})
}
private fun initZoomBar() {
zoomBar.max = 100
zoomBar.progress = INITIAL_ZOOM
restoreZoomBarSettings()
zoomBar.setOnSeekBarChangeListener(object :
SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(
@@ -669,35 +624,29 @@ class CameraActivity : AppCompatActivity() {
progress: Int,
fromUser: Boolean
) {
cameraView.camera?.setZoom(progress)
val maxRatio = camera?.cameraInfo?.zoomState?.value
?.maxZoomRatio ?: return
camera?.cameraControl?.setZoomRatio(
1f + progress / 100f * (maxRatio - 1f)
)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStartTrackingTouch(seekBar: SeekBar) {
}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {
storeZoomBarSettings()
}
})
}
@Suppress("DEPRECATION")
private fun Camera.setZoom(zoom: Int) {
try {
val params = parameters
params.zoom = zoom
parameters = params
} catch (_: RuntimeException) {
// Ignore. There's nothing we can do.
private fun storeZoomBarSettings() {
prefs.preferences.edit {
putInt(ZOOM_LEVEL, zoomBar.progress)
}
}
private fun storeZoomBarSettings() {
val editor = prefs.preferences.edit()
editor.putInt(ZOOM_MAX, zoomBar.max)
editor.putInt(ZOOM_LEVEL, zoomBar.progress)
editor.apply()
}
private fun restoreZoomBarSettings() {
zoomBar.max = prefs.preferences.getInt(ZOOM_MAX, zoomBar.max)
zoomBar.progress = prefs.preferences.getInt(
ZOOM_LEVEL,
zoomBar.progress
@@ -716,52 +665,229 @@ class CameraActivity : AppCompatActivity() {
detectorView.cropHandleName = "camera_crop_handle"
}
private fun bindCameraUseCases() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
if (isDestroyed) {
return@addListener
}
val provider = try {
cameraProviderFuture.get()
} catch (_: Exception) {
toast(R.string.camera_error)
return@addListener
}
cameraProvider = provider
val resolutionSelector = ResolutionSelector.Builder()
.setResolutionStrategy(
ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY
)
.setAspectRatioStrategy(
AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY
)
.setAllowedResolutionMode(
ResolutionSelector.PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE
)
.build()
val preview = Preview.Builder()
.setResolutionSelector(resolutionSelector)
.build()
val imageAnalysis = ImageAnalysis.Builder()
.setResolutionSelector(resolutionSelector)
.setBackpressureStrategy(
ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
)
.build()
imageAnalysis.setAnalyzer(analyzerExecutor) { image ->
analyzeImage(image)
}
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(
if (frontFacing) {
CameraSelector.LENS_FACING_FRONT
} else {
CameraSelector.LENS_FACING_BACK
}
)
.build()
try {
provider.unbindAll()
camera = provider.bindToLifecycle(
this,
cameraSelector,
preview,
imageAnalysis
)
preview.surfaceProvider = cameraView.surfaceProvider
ignoreNext = null
decoding = true
useLocalAverage = false
updateZoomState()
updateFlashFab(camera?.cameraInfo?.hasFlashUnit() ?: false)
updateFrameRoiAndMappingMatrix()
} catch (_: Exception) {
camera = null
toast(R.string.camera_error)
}
}, ContextCompat.getMainExecutor(this))
}
private fun analyzeImage(image: ImageProxy) {
try {
if (!decoding) {
return
}
frameMetrics = FrameMetrics(
image.width,
image.height,
image.imageInfo.rotationDegrees
)
updateFrameRoiAndMappingMatrix()
if (frameRoi.width() < 1 || frameRoi.height() < 1) {
return
}
useLocalAverage = useLocalAverage xor true
val yPlane = image.planes[0]
ZxingCpp.readYBuffer(
yPlane.buffer,
yPlane.rowStride,
frameRoi,
frameMetrics.orientation,
readerOptions.apply {
binarizer = if (useLocalAverage) {
Binarizer.LOCAL_AVERAGE
} else {
Binarizer.GLOBAL_HISTOGRAM
}
formats = formatsToRead
tryHarder = prefs.tryHarder
tryRotate = prefs.autoRotate
}
)?.firstOrNull()?.let { result ->
handleDecodeResult(result)
}
} finally {
image.close()
}
}
private fun handleDecodeResult(result: Result) {
val text = result.text
if (text == ignoreNext) {
return
}
val term = searchTerm
if (term != null &&
!text.matches(term) &&
!text.contains(term)
) {
ignoreNext = text
cameraView.post {
errorFeedback()
toast(R.string.does_not_match_search_term)
}
return
}
decoding = false
postResult(result)
}
private fun updateZoomState() {
zoomBar.visibility = if (
camera?.cameraInfo?.zoomState?.value != null
) {
View.VISIBLE
} else {
View.GONE
}
}
private fun updateFrameRoiAndMappingMatrix() {
val viewRect = cameraView.previewRect
if (viewRect.width() < 1 ||
viewRect.height() < 1 ||
viewBounds.set(0, 0, cameraView.width, cameraView.height)
if (viewBounds.width() < 1 ||
viewBounds.height() < 1 ||
!frameMetrics.isValid()
) {
return
}
previewRect.setCovered(
viewBounds.width(),
viewBounds.height(),
frameMetrics
)
val viewRoi = if (
detectorView.roi.width() < 1 ||
detectorView.roi.height() < 1
) {
viewRect
viewBounds
} else {
detectorView.roi
}
frameRoi.setFrameRoi(frameMetrics, viewRect, viewRoi)
matrix.setFrameToView(frameMetrics, viewRect, viewRoi)
frameRoi.setFrameRoi(frameMetrics, previewRect, viewRoi)
matrix.setFrameToView(frameMetrics, previewRect, viewRoi)
}
private fun updateFlashFab(unavailable: Boolean) {
if (unavailable) {
flashFab.setImageResource(R.drawable.ic_action_create)
flashFab.setOnClickListener { createBarcode() }
} else {
private fun updateFlashFab(available: Boolean) {
if (available) {
flashFab.setImageResource(R.drawable.ic_action_flash)
flashFab.setOnClickListener { toggleTorchMode() }
} else {
flashFab.setImageResource(R.drawable.ic_action_create)
flashFab.setOnClickListener { createBarcode() }
}
}
@Suppress("DEPRECATION")
private fun toggleTorchMode() {
val camera = cameraView.camera ?: return
val parameters = camera.parameters ?: return
parameters.flashMode = if (
parameters.flashMode != Camera.Parameters.FLASH_MODE_OFF
val currentState = camera?.cameraInfo?.torchState?.value ?: return
camera?.cameraControl?.enableTorch(
currentState != TorchState.ON
)
}
private fun Rect.setCovered(
viewWidth: Int,
viewHeight: Int,
frameMetrics: FrameMetrics
) {
val frameWidth: Int
val frameHeight: Int
when (frameMetrics.orientation) {
90, 270 -> {
frameWidth = frameMetrics.height
frameHeight = frameMetrics.width
}
else -> {
frameWidth = frameMetrics.width
frameHeight = frameMetrics.height
}
}
if (frameWidth < 1 || frameHeight < 1) {
set(0, 0, 0, 0)
return
}
var coveredWidth = frameWidth
var coveredHeight = frameHeight
if (viewWidth.toLong() * coveredWidth <
viewHeight.toLong() * coveredHeight
) {
Camera.Parameters.FLASH_MODE_OFF
coveredWidth = coveredWidth * viewHeight / coveredHeight
coveredHeight = viewHeight
} else {
Camera.Parameters.FLASH_MODE_TORCH
}
try {
camera.parameters = parameters
} catch (e: RuntimeException) {
toast(e.message ?: getString(R.string.error_flash))
coveredHeight = coveredHeight * viewWidth / coveredWidth
coveredWidth = viewWidth
}
val left = (viewWidth - coveredWidth) / 2
val top = (viewHeight - coveredHeight) / 2
set(
left,
top,
left + coveredWidth,
top + coveredHeight
)
}
private fun postResult(result: Result) {
@@ -785,13 +911,15 @@ class CameraActivity : AppCompatActivity() {
}
when {
returnResult -> {
setResult(Activity.RESULT_OK, getReturnIntent(result))
setResult(RESULT_OK, getReturnIntent(result))
finish()
}
returnUri != null -> execShareIntent(
Intent(Intent.ACTION_VIEW, returnUri)
)
returnUri != null -> {
execShareIntent(
Intent(Intent.ACTION_VIEW, returnUri)
)
}
else -> {
showResult(result, bulkMode)
@@ -806,10 +934,12 @@ class CameraActivity : AppCompatActivity() {
if (bulkMode) {
when (prefs.ignoreDuplicates()) {
Preferences.Companion.IgnoreDuplicates.Consecutive,
Preferences.Companion.IgnoreDuplicates.Any ->
Preferences.Companion.IgnoreDuplicates.Any -> {
ignoreNext = result.text
}
else -> Unit
else -> {
}
}
if (prefs.showToastInBulkMode) {
toast(result.text)
@@ -823,8 +953,8 @@ class CameraActivity : AppCompatActivity() {
companion object {
private const val PICK_FILE_RESULT_CODE = 1
private const val ZOOM_MAX = "zoom_max"
private const val ZOOM_LEVEL = "zoom_level"
private const val INITIAL_ZOOM = 0
private const val ZOOM_LEVEL = "zoom"
private const val FRONT_FACING = "front_facing"
private const val BULK_MODE = "bulk_mode"
private const val RESTRICT_FORMAT = "restrict_format"
@@ -833,7 +963,7 @@ class CameraActivity : AppCompatActivity() {
}
fun Set<String>.toFormatSet(): Set<BarcodeFormat> = map {
BarcodeFormat.valueOf(it)
BarcodeFormat.valueOf(it.migrateBarcodeFormatName())
}.toSet()
fun Activity.showResult(
@@ -887,7 +1017,9 @@ fun Activity.showResult(
R.string.bluetooth_send_fail
}
else -> R.string.bluetooth_send_success
else -> {
R.string.bluetooth_send_success
}
}
)
}
@@ -896,9 +1028,7 @@ fun Activity.showResult(
return
}
if (!bulkMode) {
startActivity(
MainActivity.getDecodeIntent(this, scan)
)
startActivity(DecodeActivity.newIntent(this, scan))
}
}
@@ -912,11 +1042,11 @@ private fun getReturnIntent(result: Result) = Intent().apply {
}
}
private fun completeUrl(urlTemplate: String, result: Result) = Uri.parse(
urlTemplate
.replace("{RESULT}", result.text.urlEncode())
.replace("{RESULT_BYTES}", result.rawBytes.toHexString())
.replace("{FORMAT}", result.format.name.urlEncode())
// And support {CODE} from the old ZXing app, too.
.replace("{CODE}", result.text.urlEncode())
)
private fun completeUrl(urlTemplate: String, result: Result) = (
urlTemplate
.replace("{RESULT}", result.text.urlEncode())
.replace("{RESULT_BYTES}", result.rawBytes.toHexString())
.replace("{FORMAT}", result.format.name.urlEncode())
// And support {CODE} from the old ZXing app, too.
.replace("{CODE}", result.text.urlEncode())
).toUri()
@@ -1,14 +1,14 @@
package de.markusfisch.android.binaryeye.fragment
package de.markusfisch.android.binaryeye.activity
import android.app.Activity
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.provider.DocumentsContract
import android.support.design.widget.FloatingActionButton
import android.support.v4.app.Fragment
import android.text.Editable
import android.text.Html
import android.text.Spannable
@@ -16,7 +16,6 @@ import android.text.SpannableString
import android.text.TextWatcher
import android.text.method.LinkMovementMethod
import android.text.style.TypefaceSpan
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
@@ -27,6 +26,8 @@ import android.widget.ImageView
import android.widget.TableLayout
import android.widget.TableRow
import android.widget.TextView
import androidx.core.net.toUri
import com.google.android.material.floatingactionbutton.FloatingActionButton
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.ActionRegistry
import de.markusfisch.android.binaryeye.actions.mail.MatMsg
@@ -37,9 +38,7 @@ import de.markusfisch.android.binaryeye.actions.vtype.vevent.VEventAction
import de.markusfisch.android.binaryeye.actions.web.WebAction
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
import de.markusfisch.android.binaryeye.actions.wifi.WifiConnector
import de.markusfisch.android.binaryeye.activity.MainActivity
import de.markusfisch.android.binaryeye.adapter.prettifyFormatName
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.app.db
import de.markusfisch.android.binaryeye.app.hasLocationPermission
import de.markusfisch.android.binaryeye.app.hasWritePermission
@@ -54,6 +53,8 @@ import de.markusfisch.android.binaryeye.content.toBarcode
import de.markusfisch.android.binaryeye.content.toErrorCorrectionInt
import de.markusfisch.android.binaryeye.content.wipeLastShareFile
import de.markusfisch.android.binaryeye.database.Scan
import de.markusfisch.android.binaryeye.database.toBundle
import de.markusfisch.android.binaryeye.database.toScan
import de.markusfisch.android.binaryeye.io.askForFileName
import de.markusfisch.android.binaryeye.io.toSaveResult
import de.markusfisch.android.binaryeye.io.writeExternalFile
@@ -71,7 +72,7 @@ import java.io.File
import java.security.MessageDigest
import kotlin.math.roundToInt
class DecodeFragment : Fragment() {
class DecodeActivity : ScreenActivity() {
private lateinit var contentView: EditText
private lateinit var formatView: TextView
private lateinit var dataView: TableLayout
@@ -105,11 +106,12 @@ class DecodeFragment : Fragment() {
resultCode: Int,
resultData: Intent?
) {
super.onActivityResult(requestCode, resultCode, resultData)
when (requestCode) {
OPEN_DOCUMENT -> {
if (resultCode == Activity.RESULT_OK) {
val uri = resultData?.data ?: return
activity?.openPickedFile(uri)
openPickedFile(uri)
}
}
}
@@ -117,33 +119,20 @@ class DecodeFragment : Fragment() {
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?
): View {
activity?.setTitle(R.string.content)
val view = inflater.inflate(
setTitle(R.string.content)
val frame = findViewById(R.id.content_frame) as ViewGroup
val view = layoutInflater.inflate(
R.layout.fragment_decode,
container,
frame,
false
)
frame.addView(view)
val justScanned = activity?.intent?.hasExtra(
MainActivity.DECODED
) == true
val justScanned = intent.hasExtra(DECODED) || intent.hasExtra(DECODED_SCAN)
closeAutomatically = prefs.closeAutomatically && justScanned
scan = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arguments?.getParcelable(SCAN, Scan::class.java)
} else {
@Suppress("DEPRECATION")
arguments?.getParcelable(SCAN)
} ?: throw IllegalArgumentException("DecodeFragment needs a Scan")
scan = intent?.extras?.getBundle(SCAN_BUNDLE)?.toScan()
?: throw IllegalArgumentException("DecodeActivity needs a Scan")
isBinary = scan.text.isEmpty()
originalBytes = scan.raw ?: scan.text.toByteArray()
@@ -180,8 +169,6 @@ class DecodeFragment : Fragment() {
initContentAndFab(justScanned)
updateViewsAndFab(scan.text, originalBytes)
return view
}
override fun onPause() {
@@ -304,7 +291,8 @@ class DecodeFragment : Fragment() {
ContentBarcode(
content,
scan.format,
scan.errorCorrectionLevel.toErrorCorrectionInt()
scan.errorCorrectionLevel.toErrorCorrectionInt(),
margin = 1
)
}
try {
@@ -312,8 +300,8 @@ class DecodeFragment : Fragment() {
v.setOnClickListener {
v.context.hideSoftKeyboard(contentView)
v.post {
fragmentManager?.addFragment(
BarcodeFragment.newInstance(barcode)
startActivity(
BarcodeActivity.newIntent(this, barcode)
)
}
}
@@ -375,7 +363,7 @@ class DecodeFragment : Fragment() {
is WebAction -> try {
items.putAll(
Uri.parse(text).run {
text.toUri().run {
mapOf(
R.string.scheme to scheme,
R.string.host to host,
@@ -418,7 +406,7 @@ class DecodeFragment : Fragment() {
R.string.gtin_issue_number to scan.issueNumber,
)
if (!scan.version.isNullOrBlank()) {
val versionString = if (scan.format.name == "QR_CODE") {
val versionString = if (scan.format == ZxingCpp.BarcodeFormat.QRCode) {
getString(
R.string.qr_version_and_modules,
scan.version,
@@ -433,7 +421,7 @@ class DecodeFragment : Fragment() {
)
)
}
if (scan.format == ZxingCpp.BarcodeFormat.QR_CODE &&
if (scan.format == ZxingCpp.BarcodeFormat.QRCode &&
scan.dataMask > -1
) {
items.putAll(
@@ -480,7 +468,7 @@ class DecodeFragment : Fragment() {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.fragment_decode, menu)
if (id > 0L) {
if (scan.id > 0L) {
menu.findItem(R.id.remove).isVisible = true
}
if (action is WifiAction) {
@@ -509,29 +497,26 @@ class DecodeFragment : Fragment() {
}
R.id.share -> {
context?.apply {
shareText(textOrHex())
maybeBackOrFinish()
}
shareText(textOrHex())
maybeBackOrFinish()
true
}
R.id.share_file -> {
context?.apply {
val (data, name) = if (isBinary) {
Pair(originalBytes, "barcode_content.bin")
} else {
Pair(content, "barcode_content.txt")
}
shareAsFile(data, name)
maybeBackOrFinish()
val (data, name) = if (isBinary) {
Pair(originalBytes, "barcode_content.bin")
} else {
Pair(content, "barcode_content.txt")
}
shareAsFile(data, name)
maybeBackOrFinish()
true
}
R.id.create -> {
fragmentManager?.addFragment(
EncodeFragment.newInstance(
startActivity(
EncodeActivity.newIntent(
this,
if (isBinary) originalBytes else content,
format
)
@@ -540,8 +525,7 @@ class DecodeFragment : Fragment() {
}
R.id.remove -> {
db.removeScan(scan.id)
backOrFinish()
askToRemoveScan(scan.id)
true
}
@@ -549,6 +533,18 @@ class DecodeFragment : Fragment() {
}
}
private fun askToRemoveScan(id: Long) {
AlertDialog.Builder(this)
.setMessage(R.string.really_remove_scan)
.setPositiveButton(android.R.string.ok) { _, _ ->
db.removeScan(id)
backOrFinish()
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
}
.show()
}
private fun textOrHex() = if (isBinary) {
originalBytes.toHexString()
} else {
@@ -570,12 +566,10 @@ class DecodeFragment : Fragment() {
}
private fun copyToClipboard(text: String, isSensitive: Boolean = false) {
activity?.apply {
copyToClipboard(text, isSensitive)
// There's a clipboard popup from Android 13 on.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
toast(R.string.copied_to_clipboard)
}
(this as android.content.Context).copyToClipboard(text, isSensitive)
// There's a clipboard popup from Android 13 on.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
toast(R.string.copied_to_clipboard)
}
}
@@ -586,27 +580,21 @@ class DecodeFragment : Fragment() {
}
private fun backOrFinish() {
val fm = fragmentManager
if (fm != null && fm.backStackEntryCount > 0) {
fm.popBackStack()
} else {
activity?.finish()
}
finish()
}
private fun executeAction(str: String) {
val ac = activity ?: return
if (str.isEmpty() || openLocalDocument(str)) {
return
}
if (action is WifiAction &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
!ac.hasLocationPermission { executeAction(str) }
!hasLocationPermission { executeAction(str) }
) {
return
}
scope.launch {
action.execute(ac, str.toByteArray())
action.execute(this@DecodeActivity, str.toByteArray())
}
}
@@ -642,40 +630,42 @@ class DecodeFragment : Fragment() {
}
private fun askForFileNameAndSave(raw: ByteArray) {
val ac = activity ?: return
// Write permission is only required before Android Q.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
!ac.hasWritePermission { askForFileNameAndSave(raw) }
!hasWritePermission { askForFileNameAndSave(raw) }
) {
return
}
scope.launch(Dispatchers.Main) {
val name = ac.askForFileName() ?: return@launch
val name = askForFileName() ?: return@launch
withContext(Dispatchers.IO) {
val message = ac.writeExternalFile(
val message = writeExternalFile(
name,
"application/octet-stream"
) {
it.write(raw)
}.toSaveResult()
withContext(Dispatchers.Main) {
ac.toast(message)
toast(message)
}
}
}
}
companion object {
private const val SCAN = "scan"
const val DECODED_SCAN = "decoded_scan"
const val DECODED = "decoded"
private const val SCAN_BUNDLE = "scan_bundle"
private const val OPEN_DOCUMENT = 1
private const val SCHEME_FILE = "file://"
fun newInstance(scan: Scan): Fragment {
fun newIntent(context: Context, scan: Scan): Intent {
val args = Bundle()
args.putParcelable(SCAN, scan)
val fragment = DecodeFragment()
fragment.arguments = args
return fragment
args.putBundle(SCAN_BUNDLE, scan.toBundle())
return Intent(context, DecodeActivity::class.java).apply {
putExtra(DECODED_SCAN, true)
putExtras(args)
}
}
}
}
@@ -753,7 +743,7 @@ private fun generateDpTrackingLink(bytes: ByteArray, format: String): String? {
// Check for Deutsche Post Matrixcode stamp.
var isStamp = false
var rawData = bytes
if (format == "DATA_MATRIX" &&
if (format == ZxingCpp.BarcodeFormat.DataMatrix.name &&
bytes.toString(Charsets.ISO_8859_1).startsWith("DEA5")
) {
if (bytes.size == 47) {
@@ -1,12 +1,9 @@
package de.markusfisch.android.binaryeye.fragment
package de.markusfisch.android.binaryeye.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.SwitchCompat
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
@@ -18,11 +15,12 @@ import android.widget.CheckBox
import android.widget.EditText
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.widget.SwitchCompat
import androidx.core.net.toUri
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.adapter.prettifyFormatName
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.app.setFragment
import de.markusfisch.android.binaryeye.net.isEncodeDeeplink
import de.markusfisch.android.binaryeye.text.unescape
import de.markusfisch.android.binaryeye.view.hideSoftKeyboard
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
@@ -31,7 +29,8 @@ import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
import java.io.InputStream
import kotlin.math.min
class EncodeFragment : Fragment() {
class EncodeActivity : ScreenActivity() {
private lateinit var formatView: Spinner
private lateinit var ecLabel: TextView
private lateinit var ecSpinner: Spinner
@@ -41,18 +40,33 @@ class EncodeFragment : Fragment() {
private lateinit var contentView: EditText
private lateinit var unescapeCheckBox: CheckBox
private val formats = arrayListOf(
BarcodeFormat.AZTEC,
BarcodeFormat.CODABAR,
BarcodeFormat.CODE_39,
BarcodeFormat.CODE_128,
BarcodeFormat.DATA_MATRIX,
BarcodeFormat.EAN_8,
BarcodeFormat.EAN_13,
private val writeableFormats = arrayListOf(
BarcodeFormat.Aztec,
BarcodeFormat.Codabar,
BarcodeFormat.Code39,
BarcodeFormat.Code39Ext,
BarcodeFormat.Code32,
BarcodeFormat.PZN,
BarcodeFormat.Code93,
BarcodeFormat.Code128,
BarcodeFormat.DataBar,
BarcodeFormat.DataBarOmni,
BarcodeFormat.DataBarStk,
BarcodeFormat.DataBarLtd,
BarcodeFormat.DataBarExp,
BarcodeFormat.DataBarExpStk,
BarcodeFormat.DataMatrix,
BarcodeFormat.DXFilmEdge,
BarcodeFormat.EAN8,
BarcodeFormat.EAN13,
BarcodeFormat.ITF,
BarcodeFormat.PDF_417,
BarcodeFormat.QR_CODE,
BarcodeFormat.UPC_A
BarcodeFormat.MaxiCode,
BarcodeFormat.PDF417,
BarcodeFormat.QRCode,
BarcodeFormat.MicroQRCode,
BarcodeFormat.RMQRCode,
BarcodeFormat.UPCA,
BarcodeFormat.UPCE,
)
private var bytes: ByteArray? = null
@@ -62,16 +76,16 @@ class EncodeFragment : Fragment() {
resultCode: Int,
resultData: Intent?
) {
super.onActivityResult(requestCode, resultCode, resultData)
when (requestCode) {
PICK_FILE_RESULT_CODE -> {
if (resultCode == Activity.RESULT_OK &&
resultData != null &&
resultData.data != null
) {
val ac = activity ?: return
ac.hideSoftKeyboard(contentView)
hideSoftKeyboard(contentView)
val uri = resultData.data ?: return
bytes = ac.contentResolver?.openInputStream(uri)?.use {
bytes = contentResolver?.openInputStream(uri)?.use {
it.readBytesMax(4096)
} ?: return
setEncodeByteArray()
@@ -82,28 +96,20 @@ class EncodeFragment : Fragment() {
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?
): View? {
val ac = activity ?: return null
ac.setTitle(R.string.compose_barcode)
val view = inflater.inflate(
setTitle(R.string.compose_barcode)
val frame = findViewById(R.id.content_frame) as ViewGroup
val view = layoutInflater.inflate(
R.layout.fragment_encode,
container,
frame,
false
)
frame.addView(view)
formatView = view.findViewById(R.id.format)
val formatAdapter = ArrayAdapter(
ac,
this,
android.R.layout.simple_spinner_item,
formats.map { it.name.prettifyFormatName() }
writeableFormats.map { it.name.prettifyFormatName() }
)
formatAdapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item
@@ -116,11 +122,13 @@ class EncodeFragment : Fragment() {
position: Int,
id: Long
) {
val format = formats[position]
val format = writeableFormats[position]
val arrayId = when (format) {
BarcodeFormat.AZTEC -> R.array.aztec_error_correction_levels
BarcodeFormat.QR_CODE -> R.array.qr_error_correction_levels
BarcodeFormat.PDF_417 -> R.array.pdf417_error_correction_levels
BarcodeFormat.Aztec -> R.array.aztec_error_correction_levels
BarcodeFormat.QRCode -> R.array.qr_error_correction_levels
BarcodeFormat.MicroQRCode -> R.array.mqr_error_correction_levels
BarcodeFormat.RMQRCode -> R.array.rmqr_error_correction_levels
BarcodeFormat.PDF417 -> R.array.pdf417_error_correction_levels
else -> 0
}
if (arrayId > 0) {
@@ -138,14 +146,6 @@ class EncodeFragment : Fragment() {
format.canBeInverted().setVisibilityFor(
colorsLabel, colorsSpinner
)
when (format) {
BarcodeFormat.AZTEC,
BarcodeFormat.DATA_MATRIX,
BarcodeFormat.QR_CODE,
BarcodeFormat.PDF_417 -> true
else -> false
}.setVisibilityFor(addQuietZoneSwitch)
}
override fun onNothingSelected(parentView: AdapterView<*>?) {}
@@ -160,7 +160,7 @@ class EncodeFragment : Fragment() {
position: Int,
id: Long
) {
val format = formats[formatView.selectedItemPosition]
val format = writeableFormats[formatView.selectedItemPosition]
prefs.indexOfLastSelectedEcLevel = format.packEcLevel(
prefs.indexOfLastSelectedEcLevel,
position
@@ -180,7 +180,7 @@ class EncodeFragment : Fragment() {
unescapeCheckBox.isChecked = prefs.expandEscapeSequences
var complete = false
val args = arguments
val args = intent?.extras ?: getExtrasFromDeeplink()
args?.getString(CONTENT_TEXT)?.let {
contentView.setText(it)
complete = it.isNotEmpty()
@@ -194,7 +194,7 @@ class EncodeFragment : Fragment() {
val barcodeFormat = args?.getString(FORMAT)
if (barcodeFormat != null) {
formatView.setSelection(
formats.indexOf(barcodeFormat.toFormat())
writeableFormats.indexOf(barcodeFormat.toFormat())
)
} else if (state == null) {
formatView.post {
@@ -214,11 +214,10 @@ class EncodeFragment : Fragment() {
) {
view.post {
val content = view.context.getContent() ?: return@post
fragmentManager?.setFragment(newEncodeFragment(content))
startActivity(newBarcodeIntent(content))
finish()
}
}
return view
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@@ -255,25 +254,21 @@ class EncodeFragment : Fragment() {
hideSoftKeyboard(contentView)
val content = getContent() ?: return
contentView.post {
fragmentManager?.addFragment(newEncodeFragment(content))
startActivity(newBarcodeIntent(content))
}
}
private fun newEncodeFragment(content: Any): Fragment {
val format = formats[formatView.selectedItemPosition]
return BarcodeFragment.newInstance(
private fun newBarcodeIntent(content: Any): Intent {
val format = writeableFormats[formatView.selectedItemPosition]
return BarcodeActivity.newIntent(
this,
content,
format,
format.getErrorCorrectionLevel(ecSpinner.selectedItemPosition),
when (format) {
BarcodeFormat.AZTEC,
BarcodeFormat.DATA_MATRIX,
BarcodeFormat.QR_CODE,
BarcodeFormat.PDF_417 -> if (
addQuietZoneSwitch.isChecked
) 1 else 0
else -> -1
if (addQuietZoneSwitch.isChecked) {
1
} else {
0
},
if (format.canBeInverted()) {
colorsSpinner.selectedItemPosition
@@ -316,6 +311,28 @@ class EncodeFragment : Fragment() {
unescapeCheckBox.isEnabled = false
}
private fun getExtrasFromDeeplink(): Bundle? {
val data = intent?.dataString ?: return null
if (!isEncodeDeeplink(data)) {
return null
}
val uri = data.toUri()
return Bundle().apply {
uri.getQueryParameter("content")?.let {
putString(CONTENT_TEXT, it)
}
uri.getQueryParameter("format")?.let {
putString(FORMAT, it)
}
putBoolean(
EXECUTE,
uri.getQueryParameter("execute").let {
it == "" || it?.toBoolean() == true
}
)
}
}
companion object {
private const val CONTENT_TEXT = "content_text"
private const val CONTENT_RAW = "content_raw"
@@ -323,11 +340,12 @@ class EncodeFragment : Fragment() {
private const val EXECUTE = "execute"
private const val PICK_FILE_RESULT_CODE = 1
fun <T> newInstance(
fun <T> newIntent(
context: Context,
content: T? = null,
format: String? = null,
execute: Boolean = false
): Fragment {
): Intent {
val args = Bundle()
content?.let {
when (content) {
@@ -340,9 +358,9 @@ class EncodeFragment : Fragment() {
args.putBoolean(EXECUTE, execute)
}
format?.let { args.putString(FORMAT, it) }
val fragment = EncodeFragment()
fragment.arguments = args
return fragment
return Intent(context, EncodeActivity::class.java).apply {
putExtras(args)
}
}
}
}
@@ -363,22 +381,26 @@ private fun BarcodeFormat.unpackEcLevel(packed: Int) =
(packed shr ecLevelShift()) and 15
private fun BarcodeFormat.ecLevelShift() = when (this) {
BarcodeFormat.AZTEC -> 0
BarcodeFormat.QR_CODE -> 4
BarcodeFormat.PDF_417 -> 8
BarcodeFormat.Aztec -> 0
BarcodeFormat.QRCode -> 4
BarcodeFormat.MicroQRCode -> 3
BarcodeFormat.RMQRCode -> 2
BarcodeFormat.PDF417 -> 8
else -> throw IllegalArgumentException("$this does not have error levels")
}
private fun BarcodeFormat.canBeInverted() = when (this) {
BarcodeFormat.AZTEC,
BarcodeFormat.DATA_MATRIX,
BarcodeFormat.QR_CODE -> true
BarcodeFormat.Aztec,
BarcodeFormat.DataMatrix,
BarcodeFormat.QRCode,
BarcodeFormat.MicroQRCode,
BarcodeFormat.RMQRCode -> true
else -> false
}
private fun String.toFormat(
default: BarcodeFormat = BarcodeFormat.QR_CODE
default: BarcodeFormat = BarcodeFormat.QRCode
): BarcodeFormat = try {
BarcodeFormat.valueOf(this)
} catch (_: IllegalArgumentException) {
@@ -386,9 +408,12 @@ private fun String.toFormat(
}
private fun BarcodeFormat.getErrorCorrectionLevel(position: Int) = when (this) {
BarcodeFormat.AZTEC -> position
BarcodeFormat.QR_CODE -> (position + 1) * 2
BarcodeFormat.PDF_417 -> position
BarcodeFormat.Aztec -> position
BarcodeFormat.QRCode,
BarcodeFormat.MicroQRCode -> (position + 1) * 2
BarcodeFormat.RMQRCode -> (position + 1) * 4
BarcodeFormat.PDF417 -> position
else -> 0
}.coerceIn(0, 8)
@@ -1,4 +1,4 @@
package de.markusfisch.android.binaryeye.fragment
package de.markusfisch.android.binaryeye.activity
import android.annotation.SuppressLint
import android.app.AlertDialog
@@ -8,13 +8,6 @@ import android.database.Cursor
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.support.v4.app.ActivityCompat
import android.support.v4.app.Fragment
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.view.ActionMode
import android.support.v7.widget.SearchView
import android.support.v7.widget.SwitchCompat
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
@@ -23,9 +16,12 @@ import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ListView
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.SwitchCompat
import androidx.core.content.ContextCompat
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.alertDialog
import de.markusfisch.android.binaryeye.app.db
import de.markusfisch.android.binaryeye.app.hasWritePermission
@@ -38,7 +34,6 @@ import de.markusfisch.android.binaryeye.database.Database
import de.markusfisch.android.binaryeye.database.exportCsv
import de.markusfisch.android.binaryeye.database.exportDatabase
import de.markusfisch.android.binaryeye.database.exportJson
import de.markusfisch.android.binaryeye.database.use
import de.markusfisch.android.binaryeye.io.askForFileName
import de.markusfisch.android.binaryeye.io.toSaveResult
import de.markusfisch.android.binaryeye.view.lockStatusBarColor
@@ -53,7 +48,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class HistoryFragment : Fragment() {
class HistoryActivity : ScreenActivity() {
private lateinit var useHistorySwitch: SwitchCompat
private lateinit var listView: ListView
private lateinit var fab: View
@@ -70,12 +65,10 @@ class HistoryFragment : Fragment() {
R.menu.fragment_history_edit,
menu
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
// SDK35+ doesn't draw a status bar background anymore.
Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM
) {
// SDK35+ doesn't draw a status bar background anymore.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
lockStatusBarColor()
val ac = activity ?: return false
val ac = this@HistoryActivity
@Suppress("DEPRECATION")
ac.window.statusBarColor = ContextCompat.getColor(
ac,
@@ -96,7 +89,7 @@ class HistoryFragment : Fragment() {
mode: ActionMode,
item: MenuItem
): Boolean {
val ac = activity ?: return false
val ac = this@HistoryActivity
return when (item.itemId) {
R.id.copy_scan -> {
scansAdapter?.getSelectedContent("\n")?.let {
@@ -148,22 +141,10 @@ class HistoryFragment : Fragment() {
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?
): View? {
val ac = activity ?: return null
ac.setTitle(R.string.history)
val view = inflater.inflate(
R.layout.fragment_history,
container,
false
)
setTitle(R.string.history)
val frame = findViewById(R.id.content_frame) as ViewGroup
val view = layoutInflater.inflate(R.layout.fragment_history, frame, false)
frame.addView(view)
useHistorySwitch = view.findViewById(
R.id.use_history
@@ -180,8 +161,8 @@ class HistoryFragment : Fragment() {
}
listView.setOnItemLongClickListener { _, v, position, id ->
scansAdapter?.select(v, id, position)
if (actionMode == null && ac is AppCompatActivity) {
actionMode = ac.delegate.startSupportActionMode(
if (actionMode == null) {
actionMode = delegate.startSupportActionMode(
actionModeCallback
)
}
@@ -198,10 +179,6 @@ class HistoryFragment : Fragment() {
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
listView.setPaddingFromWindowInsets()
update()
return view
}
override fun onDestroy() {
@@ -211,6 +188,11 @@ class HistoryFragment : Fragment() {
wipeLastShareFile()
}
override fun onResume() {
super.onResume()
update()
}
override fun onPause() {
super.onPause()
listViewState = listView.onSaveInstanceState()
@@ -235,35 +217,31 @@ class HistoryFragment : Fragment() {
private fun initSearchView(item: MenuItem?) {
item ?: return
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
item.isVisible = false
} else {
val ac = activity ?: return
val searchView = item.actionView as SearchView
val searchManager = ac.getSystemService(
Context.SEARCH_SERVICE
) as SearchManager
searchView.setSearchableInfo(
searchManager.getSearchableInfo(ac.componentName)
)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
update(query)
return false
}
val ac = this
val searchView = item.actionView as SearchView
val searchManager = ac.getSystemService(
Context.SEARCH_SERVICE
) as SearchManager
searchView.setSearchableInfo(
searchManager.getSearchableInfo(ac.componentName)
)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
update(query)
return false
}
override fun onQueryTextChange(query: String): Boolean {
update(query)
return false
}
})
}
override fun onQueryTextChange(query: String): Boolean {
update(query)
return false
}
})
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.clear -> {
context.askToRemoveScans()
this.askToRemoveScans()
true
}
@@ -273,7 +251,7 @@ class HistoryFragment : Fragment() {
}
R.id.share_as_file -> {
context.pickListSeparatorAndShare(true)
this.pickListSeparatorAndShare(true)
true
}
@@ -300,13 +278,13 @@ class HistoryFragment : Fragment() {
scope.launch {
val cursor = db.getScans(filter)
withContext(Dispatchers.Main) {
val ac = activity ?: return@withContext
val ac = this@HistoryActivity
val hasScans = cursor.count > 0
if (filter == null) {
if (!hasScans) {
listView.emptyView = useHistorySwitch
}
ActivityCompat.invalidateOptionsMenu(ac)
ac.invalidateOptionsMenu()
}
enableMenuItems(hasScans)
fab.visibility = if (hasScans) {
@@ -344,7 +322,7 @@ class HistoryFragment : Fragment() {
private fun showScan(id: Long) = db.getScan(id)?.also { scan ->
closeActionMode()
try {
fragmentManager?.addFragment(DecodeFragment.newInstance(scan))
startActivity(DecodeActivity.newIntent(this, scan))
} catch (_: IllegalArgumentException) {
// Ignore, can never happen.
}
@@ -418,7 +396,7 @@ class HistoryFragment : Fragment() {
private fun askToExportToFile() {
scope.launch {
val ac = activity ?: return@launch
val ac = this@HistoryActivity
progressView.useVisibility {
// Write permission is only required before Android Q.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
@@ -490,12 +468,12 @@ class HistoryFragment : Fragment() {
text?.let {
withContext(Dispatchers.Main) {
if (asFile) {
context.shareAsFile(
this@HistoryActivity.shareAsFile(
it,
String.format("scans.%s", ext)
)
} else {
context.shareText(it)
this@HistoryActivity.shareText(it)
}
}
}
@@ -1,158 +0,0 @@
package de.markusfisch.android.binaryeye.activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.PERMISSION_LOCATION
import de.markusfisch.android.binaryeye.app.PERMISSION_WRITE
import de.markusfisch.android.binaryeye.app.applyLocale
import de.markusfisch.android.binaryeye.app.permissionGrantedCallback
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.app.setFragment
import de.markusfisch.android.binaryeye.database.Scan
import de.markusfisch.android.binaryeye.fragment.DecodeFragment
import de.markusfisch.android.binaryeye.fragment.EncodeFragment
import de.markusfisch.android.binaryeye.fragment.HistoryFragment
import de.markusfisch.android.binaryeye.fragment.PreferencesFragment
import de.markusfisch.android.binaryeye.net.isEncodeDeeplink
import de.markusfisch.android.binaryeye.view.colorSystemAndToolBars
import de.markusfisch.android.binaryeye.view.initBars
class MainActivity : AppCompatActivity() {
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
when (requestCode) {
PERMISSION_LOCATION, PERMISSION_WRITE -> if (
grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
permissionGrantedCallback?.invoke()
permissionGrantedCallback = null
}
}
}
override fun onSupportNavigateUp(): Boolean {
val fm = supportFragmentManager
if (fm != null && fm.backStackEntryCount > 0) {
fm.popBackStack()
} else {
finish()
}
return true
}
override fun attachBaseContext(base: Context?) {
base?.applyLocale(prefs.customLocale)
super.attachBaseContext(base)
}
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setContentView(R.layout.activity_main)
initBars()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportFragmentManager.addOnBackStackChangedListener {
colorSystemAndToolBars(this@MainActivity)
}
if (state == null) {
supportFragmentManager?.setFragment(getFragmentForIntent(intent))
}
}
companion object {
private const val PREFERENCES = "preferences"
private const val HISTORY = "history"
private const val ENCODE = "encode"
const val DECODED = "decoded"
private fun getFragmentForIntent(intent: Intent?): Fragment {
intent ?: return PreferencesFragment()
return when {
intent.hasExtra(PREFERENCES) -> PreferencesFragment()
intent.hasExtra(HISTORY) -> HistoryFragment()
intent.dataString?.let(::isEncodeDeeplink) == true -> {
val uri = Uri.parse(intent.dataString)
EncodeFragment.newInstance(
content = uri.getQueryParameter("content"),
format = uri.getQueryParameter("format")?.uppercase(),
execute = uri.getQueryParameter("execute")
.let { it == "" || it.toBoolean() }
)
}
intent.hasExtra(ENCODE) -> EncodeFragment.newInstance(
intent.getStringExtra(ENCODE)
)
intent.hasExtra(DECODED) -> intent.getScanExtra(DECODED)?.let {
DecodeFragment.newInstance(it)
} ?: DecodeFragment()
else -> PreferencesFragment()
}
}
fun getPreferencesIntent(context: Context): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(PREFERENCES, true)
return intent
}
fun getHistoryIntent(context: Context): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(HISTORY, true)
return intent
}
fun getEncodeIntent(
context: Context,
text: String? = "",
isExternal: Boolean = false
): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(ENCODE, text)
if (isExternal) {
val flagActivityClearTask = if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
) {
Intent.FLAG_ACTIVITY_CLEAR_TASK
} else 0
intent.addFlags(
Intent.FLAG_ACTIVITY_NO_HISTORY or
flagActivityClearTask or
Intent.FLAG_ACTIVITY_NEW_TASK
)
}
return intent
}
fun getDecodeIntent(context: Context, scan: Scan): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(DECODED, scan)
return intent
}
}
}
private fun Intent.getScanExtra(name: String): Scan? = if (
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
) {
@Suppress("DEPRECATION")
getParcelableExtra(name)
} else {
getParcelableExtra(name, Scan::class.java)
}
@@ -1,43 +1,39 @@
package de.markusfisch.android.binaryeye.fragment
package de.markusfisch.android.binaryeye.activity
import android.content.Context
import android.net.wifi.WifiManager
import android.net.wifi.WifiNetworkSuggestion
import android.os.Build
import android.os.Bundle
import android.support.annotation.RequiresApi
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ListView
import androidx.annotation.RequiresApi
import androidx.core.util.size
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
import de.markusfisch.android.binaryeye.view.systemBarListViewScrollListener
import de.markusfisch.android.binaryeye.widget.toast
class NetworkSuggestionsFragment : Fragment() {
class NetworkSuggestionsActivity : ScreenActivity() {
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?
): View? {
val ac = activity ?: return null
ac.setTitle(R.string.network_suggestions)
val view = inflater.inflate(
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setTitle(R.string.network_suggestions)
val frame = findViewById(R.id.content_frame) as ViewGroup
val view = layoutInflater.inflate(
R.layout.fragment_network_suggestions,
container,
frame,
false
)
frame.addView(view)
val wm = ac.applicationContext.getSystemService(
val wm = applicationContext.getSystemService(
Context.WIFI_SERVICE
) as WifiManager
val suggestionArrayAdapter = ArrayAdapter(
ac,
this,
android.R.layout.simple_list_item_checked,
wm.networkSuggestions.map {
Suggestion(
@@ -58,7 +54,7 @@ class NetworkSuggestionsFragment : Fragment() {
val removeList = ArrayList<WifiNetworkSuggestion>()
val removeFromAdapter = ArrayList<Suggestion>()
val checked = listView.checkedItemPositions
val size = checked.size()
val size = checked.size
for (i in 0 until size) {
if (checked.valueAt(i)) {
val pos = checked.keyAt(i)
@@ -76,13 +72,11 @@ class NetworkSuggestionsFragment : Fragment() {
}
wm.removeNetworkSuggestions(removeList)
} else {
ac.toast(
toast(
R.string.clear_network_suggestions_nothing_to_remove
)
}
}
return view
}
}
@@ -9,9 +9,10 @@ import android.graphics.RectF
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.applyLocale
import de.markusfisch.android.binaryeye.app.prefs
@@ -49,7 +50,6 @@ class PickActivity : AppCompatActivity() {
tryRotate = true,
tryInvert = true,
tryDownscale = true,
returnCodabarStartEnd = true,
maxNumberOfSymbols = 1,
formats = prefs.barcodeFormats.toFormatSet()
)
@@ -110,7 +110,7 @@ class PickActivity : AppCompatActivity() {
cropHandleName = "picker_crop_handle"
}
findViewById(R.id.scan).setOnClickListener {
findViewById<View>(R.id.scan).setOnClickListener {
showResult()
}
}
@@ -0,0 +1,34 @@
package de.markusfisch.android.binaryeye.activity
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.applyLocale
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.fragment.PreferencesFragment
import de.markusfisch.android.binaryeye.view.initBars
class PreferencesActivity : AppCompatActivity() {
override fun attachBaseContext(base: Context?) {
base?.applyLocale(prefs.customLocale)
super.attachBaseContext(base)
}
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setContentView(R.layout.activity_main)
initBars()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (state == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.content_frame, PreferencesFragment())
.commit()
}
}
override fun onSupportNavigateUp(): Boolean {
finish()
return true
}
}
@@ -1,10 +1,8 @@
package de.markusfisch.android.binaryeye.fragment
package de.markusfisch.android.binaryeye.activity
import android.app.AlertDialog
import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
@@ -17,30 +15,27 @@ import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
import de.markusfisch.android.binaryeye.view.systemBarListViewScrollListener
import de.markusfisch.android.binaryeye.widget.toast
class ProfilesFragment : Fragment() {
class ProfilesActivity : ScreenActivity() {
private val profiles = ArrayList<ProfileItem>()
private lateinit var adapter: ProfilesAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?
): View? {
val ac = activity ?: return null
ac.setTitle(R.string.profile)
val view = inflater.inflate(
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setTitle(R.string.profile)
val frame = findViewById(R.id.content_frame) as ViewGroup
val view = layoutInflater.inflate(
R.layout.fragment_profiles,
container,
frame,
false
)
frame.addView(view)
refreshProfiles()
val listView = view.findViewById<ListView>(R.id.profiles)
listView.emptyView = view.findViewById(R.id.no_profiles)
adapter = ProfilesAdapter(ac, profiles)
adapter = ProfilesAdapter(this, profiles)
listView.adapter = adapter
listView.setOnScrollListener(systemBarListViewScrollListener)
listView.setOnItemClickListener { _, _, position, _ ->
@@ -57,8 +52,6 @@ class ProfilesFragment : Fragment() {
view.findViewById<View>(R.id.inset_layout).setPaddingFromWindowInsets()
listView.setPaddingFromWindowInsets()
return view
}
private fun refreshProfiles() {
@@ -78,48 +71,45 @@ class ProfilesFragment : Fragment() {
}
private fun selectProfile(position: Int) {
val ac = activity ?: return
val profile = profiles[position].name
if (prefs.profile == profile) {
return
}
prefs.load(ac, profile)
prefs.load(this, profile)
adapter.notifyDataSetChanged()
}
// Dialogs do not have a parent view.
@Suppress("InflateParams")
private fun addProfile() {
val ac = activity ?: return
val view = ac.layoutInflater.inflate(
val view = layoutInflater.inflate(
R.layout.dialog_profile_name, null
)
val editText = view.findViewById<EditText>(R.id.name)
AlertDialog.Builder(ac)
AlertDialog.Builder(this)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
val name = editText.text.toString().trim()
if (name.isNotEmpty() && prefs.addProfile(name)) {
prefs.load(ac, name)
prefs.load(this, name)
refreshProfiles()
} else {
ac.toast(R.string.profile_invalid_name)
toast(R.string.profile_invalid_name)
}
}
.show()
}
private fun confirmRemoveProfile(position: Int) {
val ac = activity ?: return
val profile = profiles[position]
val profileName = profile.name ?: return
AlertDialog.Builder(ac)
AlertDialog.Builder(this)
.setMessage(R.string.profile_remove_confirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
val isCurrent = prefs.profile == profileName
prefs.removeProfile(profileName)
if (isCurrent) {
prefs.load(ac, null)
prefs.load(this, null)
}
refreshProfiles()
}
@@ -0,0 +1,68 @@
package de.markusfisch.android.binaryeye.activity
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isNotEmpty
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.PERMISSION_LOCATION
import de.markusfisch.android.binaryeye.app.PERMISSION_WRITE
import de.markusfisch.android.binaryeye.app.applyLocale
import de.markusfisch.android.binaryeye.app.permissionGrantedCallback
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.view.initBars
abstract class ScreenActivity : AppCompatActivity() {
override fun attachBaseContext(base: Context?) {
base?.applyLocale(prefs.customLocale)
super.attachBaseContext(base)
}
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setContentView(R.layout.activity_main)
initBars()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
override fun onSupportNavigateUp(): Boolean {
finish()
return true
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
)
when (requestCode) {
PERMISSION_LOCATION, PERMISSION_WRITE -> {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
permissionGrantedCallback?.invoke()
permissionGrantedCallback = null
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
onCreateOptionsMenu(menu, menuInflater)
return menu.isNotEmpty() || super.onCreateOptionsMenu(menu)
}
protected open fun onCreateOptionsMenu(
menu: Menu,
inflater: MenuInflater
) {
}
}
@@ -2,7 +2,7 @@ package de.markusfisch.android.binaryeye.activity
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import androidx.appcompat.app.AppCompatActivity
class SplashActivity : AppCompatActivity() {
override fun onCreate(state: Bundle?) {
@@ -1,7 +1,8 @@
package de.markusfisch.android.binaryeye.activity
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.prefs
@@ -9,10 +10,10 @@ class WelcomeActivity : AppCompatActivity() {
override fun onCreate(state: Bundle?) {
super.onCreate(state)
setContentView(R.layout.activity_welcome)
findViewById(R.id.expert).setOnClickListener {
findViewById<View>(R.id.expert).setOnClickListener {
finish()
}
findViewById(R.id.simple).setOnClickListener {
findViewById<View>(R.id.simple).setOnClickListener {
prefs.apply {
showCropHandle = false
zoomBySwiping = false
@@ -2,12 +2,12 @@ package de.markusfisch.android.binaryeye.adapter
import android.content.Context
import android.database.Cursor
import android.support.v4.content.ContextCompat
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CursorAdapter
import android.widget.TextView
import androidx.core.content.ContextCompat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.database.Database
import java.text.DateFormat
@@ -139,7 +139,11 @@ class ScansAdapter(context: Context, cursor: Cursor) :
)
}
private val formatWordBoundary =
"(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[A-Za-z])(?=\\d)|(?<=\\d)(?=[A-Za-z])".toRegex()
fun String.prettifyFormatName() = replace("_", " ")
.replace(formatWordBoundary, " ")
private fun formatDateTime(rfc: String): String {
try {
@@ -1,19 +0,0 @@
package de.markusfisch.android.binaryeye.app
import android.annotation.SuppressLint
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import de.markusfisch.android.binaryeye.R
fun FragmentManager.setFragment(fragment: Fragment) {
getTransaction(fragment).commit()
}
fun FragmentManager.addFragment(fragment: Fragment) {
getTransaction(fragment).addToBackStack(null).commit()
}
@SuppressLint("CommitTransaction")
private fun FragmentManager.getTransaction(
fragment: Fragment
) = beginTransaction().replace(R.id.content_frame, fragment)
@@ -4,8 +4,8 @@ import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Build
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
var permissionGrantedCallback: (() -> Any)? = null
@@ -3,7 +3,7 @@ package de.markusfisch.android.binaryeye.bluetooth
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import android.support.v7.preference.ListPreference
import androidx.preference.ListPreference
import de.markusfisch.android.binaryeye.database.Scan
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -48,7 +48,8 @@ fun Scan.toBarcode(): Barcode<*> {
ContentBarcode(
content,
format,
errorCorrectionLevel.toErrorCorrectionInt()
errorCorrectionLevel.toErrorCorrectionInt(),
margin = 1
)
}
}
@@ -108,7 +109,7 @@ class ContentBarcode<T>(
content: T,
format: BarcodeFormat,
val ecLevel: Int = -1,
val margin: Int = -1,
val margin: Int = 0,
colors: BarcodeColors = BarcodeColors.BLACK_ON_WHITE
) : Barcode<T>(content, format, colors) {
override fun toBitmap(size: Int) = ZxingCpp.encodeAsBitmap(
@@ -186,18 +187,24 @@ private fun BitMatrix.inflate(
private fun BarcodeFormat.height(size: Int) = when (this) {
// 1D barcodes don't need as much vertical space.
BarcodeFormat.CODABAR,
BarcodeFormat.CODE_39,
BarcodeFormat.CODE_93,
BarcodeFormat.CODE_128,
BarcodeFormat.DATA_BAR,
BarcodeFormat.DATA_BAR_EXPANDED,
BarcodeFormat.DX_FILM_EDGE,
BarcodeFormat.EAN_8,
BarcodeFormat.EAN_13,
BarcodeFormat.Codabar,
BarcodeFormat.Code39,
BarcodeFormat.Code39Ext,
BarcodeFormat.Code32,
BarcodeFormat.PZN,
BarcodeFormat.Code93,
BarcodeFormat.Code128,
BarcodeFormat.DataBarOmni,
BarcodeFormat.DataBarStk,
BarcodeFormat.DataBarLtd,
BarcodeFormat.DataBarExp,
BarcodeFormat.DataBarExpStk,
BarcodeFormat.DXFilmEdge,
BarcodeFormat.EAN8,
BarcodeFormat.EAN13,
BarcodeFormat.ITF,
BarcodeFormat.UPC_A,
BarcodeFormat.UPC_E -> size / 3
BarcodeFormat.UPCA,
BarcodeFormat.UPCE -> size / 3
else -> size
}
}
@@ -8,16 +8,11 @@ import android.os.PersistableBundle
fun Context.copyToClipboard(text: String, isSensitive: Boolean = false) {
val service = getSystemService(Context.CLIPBOARD_SERVICE)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
@Suppress("DEPRECATION")
(service as android.text.ClipboardManager).text = text
} else {
(service as android.content.ClipboardManager).setPrimaryClip(
ClipData.newPlainText("plain text", text).apply {
setSensitive(isSensitive)
}
)
}
(service as android.content.ClipboardManager).setPrimaryClip(
ClipData.newPlainText("plain text", text).apply {
setSensitive(isSensitive)
}
)
}
private fun ClipData.setSensitive(isSensitive: Boolean) {
@@ -5,7 +5,8 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.support.v4.content.FileProvider
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import de.markusfisch.android.binaryeye.BuildConfig
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.widget.toast
@@ -54,12 +55,8 @@ fun Context.openUri(uri: Uri, silent: Boolean = false): Boolean {
}
}
fun String.parseAndNormalizeUri(): Uri = Uri.parse(this).let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
it.normalizeScheme()
} else {
it
}
fun String.parseAndNormalizeUri(): Uri = toUri().let {
it.normalizeScheme()
}
fun Context.shareText(text: String, mimeType: String = "text/plain") {
@@ -3,15 +3,6 @@ package de.markusfisch.android.binaryeye.database
import android.database.Cursor
import de.markusfisch.android.zxingcpp.ZxingCpp
// Overwrite Kotlin's ".use" function for Cursor because Cursor cannot
// be cast to Closeable below API level 16. This should be removed when
// the minSDK is increased to at least JELLY_BEAN (16).
inline fun <R> Cursor.use(block: (Cursor) -> R): R = try {
block.invoke(this)
} finally {
close()
}
fun Cursor.getInt(name: String): Int {
val idx = getColumnIndex(name)
return if (idx < 0) 0 else getInt(idx)
@@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.preference.Preferences
import de.markusfisch.android.binaryeye.zxingcpp.migrateBarcodeFormatName
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
class Database {
@@ -123,7 +124,9 @@ class Database {
Scan(
it.getString(SCANS_TEXT),
it.getBlob(SCANS_RAW),
BarcodeFormat.valueOf(it.getString(SCANS_FORMAT)),
BarcodeFormat.valueOf(
it.getString(SCANS_FORMAT).migrateBarcodeFormatName()
),
it.getString(SCANS_ERROR_CORRECTION_LEVEL),
it.getString(SCANS_VERSION),
it.getInt(SCANS_DATA_MASK),
@@ -263,7 +266,7 @@ class Database {
}
private class OpenHelper(context: Context) :
SQLiteOpenHelper(context, FILE_NAME, null, 8) {
SQLiteOpenHelper(context, FILE_NAME, null, 9) {
override fun onCreate(db: SQLiteDatabase) {
db.createScans()
}
@@ -294,6 +297,9 @@ class Database {
if (oldVersion < 8) {
db.addSymbolColumns()
}
if (oldVersion < 9) {
db.migrateToNativeFormatNames()
}
}
}
@@ -391,6 +397,43 @@ class Database {
execSQL("UPDATE $SCANS SET $SCANS_GTIN_ISSUE_NUMBER = $SCANS_ISSUE_NUMBER")
}
private fun SQLiteDatabase.migrateToNativeFormatNames() {
val mappings = listOf(
"NONE" to "None",
"AZTEC" to "Aztec",
"CODABAR" to "Codabar",
"CODE_39" to "Code39",
"CODE_39_EXT" to "Code39Ext",
"CODE_32" to "Code32",
"PZN" to "PZN",
"CODE_93" to "Code93",
"CODE_128" to "Code128",
"DATA_BAR_OMNI" to "DataBarOmni",
"DATA_BAR_STK" to "DataBarStk",
"DATA_BAR_LTD" to "DataBarLtd",
"DATA_BAR_EXPANDED" to "DataBarExp",
"DATA_BAR_EXP_STK" to "DataBarExpStk",
"DATA_MATRIX" to "DataMatrix",
"DX_FILM_EDGE" to "DXFilmEdge",
"EAN_8" to "EAN8",
"EAN_13" to "EAN13",
"ITF" to "ITF",
"MAXICODE" to "MaxiCode",
"PDF_417" to "PDF417",
"QR_CODE" to "QRCode",
"MICRO_QR_CODE" to "MicroQRCode",
"RMQR_CODE" to "RMQRCode",
"UPC_A" to "UPCA",
"UPC_E" to "UPCE"
)
for ((oldName, newName) in mappings) {
execSQL(
"UPDATE $SCANS SET $SCANS_FORMAT = ? WHERE $SCANS_FORMAT = ?",
arrayOf(newName, oldName)
)
}
}
private fun SQLiteDatabase.migrateToVersionString() {
execSQL("ALTER TABLE $SCANS ADD $SCANS_VERSION TEXT")
execSQL("UPDATE $SCANS SET $SCANS_VERSION = $SCANS_VERSION_NUMBER")
@@ -1,9 +1,8 @@
package de.markusfisch.android.binaryeye.database
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import android.os.Bundle
import android.text.format.DateFormat
import de.markusfisch.android.binaryeye.zxingcpp.migrateBarcodeFormatName
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
import de.markusfisch.android.zxingcpp.ZxingCpp.BitMatrix
import de.markusfisch.android.zxingcpp.ZxingCpp.ContentType
@@ -28,7 +27,7 @@ data class Scan(
val dateTime: String = getDateTime(),
var id: Long = 0L,
val label: String? = null
) : Parcelable {
) {
// Needs to be overwritten manually, as ByteArray is an array and
// this isn't handled well by Kotlin.
override fun equals(other: Any?): Boolean {
@@ -81,57 +80,6 @@ data class Scan(
return result
}
private constructor(parcel: Parcel) : this(
text = parcel.readString() ?: "",
raw = parcel.readSizedByteArray(),
format = BarcodeFormat.valueOf(parcel.readString() ?: ""),
errorCorrectionLevel = parcel.readString(),
version = parcel.readString(),
dataMask = parcel.readInt(),
symbol = parcel.readBitMatrix(),
sequenceSize = parcel.readInt(),
sequenceIndex = parcel.readInt(),
sequenceId = parcel.readString() ?: "",
country = parcel.readString(),
addOn = parcel.readString(),
price = parcel.readString(),
issueNumber = parcel.readString(),
dateTime = parcel.readString() ?: "",
id = parcel.readLong(),
label = parcel.readString()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.apply {
writeString(text)
writeSizedByteArray(raw)
writeString(format.name)
writeString(errorCorrectionLevel)
writeString(version)
writeInt(dataMask)
writeBitMatrix(symbol)
writeInt(sequenceSize)
writeInt(sequenceIndex)
writeString(sequenceId)
writeString(country)
writeString(addOn)
writeString(price)
writeString(issueNumber)
writeString(dateTime)
writeLong(id)
writeString(label)
}
}
override fun describeContents() = 0
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<Scan> {
override fun createFromParcel(parcel: Parcel) = Scan(parcel)
override fun newArray(size: Int) = arrayOfNulls<Scan>(size)
}
}
}
fun Result.toScan(): Scan {
@@ -159,16 +107,62 @@ fun Result.toScan(): Scan {
)
}
fun Scan.toBundle() = Bundle().apply {
putString(SCAN_TEXT, text)
putByteArray(SCAN_RAW, raw)
putString(SCAN_FORMAT, format.name)
putString(SCAN_ERROR_CORRECTION_LEVEL, errorCorrectionLevel)
putString(SCAN_VERSION, version)
putInt(SCAN_DATA_MASK, dataMask)
putInt(SCAN_SYMBOL_WIDTH, symbol?.width ?: 0)
putInt(SCAN_SYMBOL_HEIGHT, symbol?.height ?: 0)
putByteArray(SCAN_SYMBOL_DATA, symbol?.data)
putInt(SCAN_SEQUENCE_SIZE, sequenceSize)
putInt(SCAN_SEQUENCE_INDEX, sequenceIndex)
putString(SCAN_SEQUENCE_ID, sequenceId)
putString(SCAN_COUNTRY, country)
putString(SCAN_ADD_ON, addOn)
putString(SCAN_PRICE, price)
putString(SCAN_ISSUE_NUMBER, issueNumber)
putString(SCAN_DATE_TIME, dateTime)
putLong(SCAN_ID, id)
putString(SCAN_LABEL, label)
}
fun Bundle.toScan(): Scan? {
val formatName = getString(SCAN_FORMAT)?.migrateBarcodeFormatName()
if (formatName.isNullOrEmpty()) {
return null
}
return try {
Scan(
text = getString(SCAN_TEXT) ?: "",
raw = getByteArray(SCAN_RAW),
format = BarcodeFormat.valueOf(formatName),
errorCorrectionLevel = getString(SCAN_ERROR_CORRECTION_LEVEL),
version = getString(SCAN_VERSION),
dataMask = getInt(SCAN_DATA_MASK, -1),
symbol = getBitMatrix(),
sequenceSize = getInt(SCAN_SEQUENCE_SIZE, -1),
sequenceIndex = getInt(SCAN_SEQUENCE_INDEX, -1),
sequenceId = getString(SCAN_SEQUENCE_ID) ?: "",
country = getString(SCAN_COUNTRY),
addOn = getString(SCAN_ADD_ON),
price = getString(SCAN_PRICE),
issueNumber = getString(SCAN_ISSUE_NUMBER),
dateTime = getString(SCAN_DATE_TIME) ?: getDateTime(),
id = getLong(SCAN_ID, 0L),
label = getString(SCAN_LABEL)
)
} catch (_: IllegalArgumentException) {
null
}
}
private fun getDateTime(
time: Long = System.currentTimeMillis()
) = DateFormat.format(
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
// Up to API level 17, 'k' was interpreted as 'H', which wasn't
// implemented until API level 18.
"yyyy-MM-dd kk:mm:ss"
} else {
"yyyy-MM-dd HH:mm:ss"
},
"yyyy-MM-dd HH:mm:ss",
time
).toString() + String.format(
Locale.getDefault(),
@@ -176,39 +170,32 @@ private fun getDateTime(
time % 1000
)
private fun Parcel.writeSizedByteArray(array: ByteArray?) {
val size = array?.size ?: 0
writeInt(size)
if (size > 0) {
writeByteArray(array)
}
}
private fun Parcel.readSizedByteArray(): ByteArray? {
val size = readInt()
return if (size > 0) {
val array = ByteArray(size)
readByteArray(array)
array
} else {
null
}
}
private fun Parcel.writeBitMatrix(bm: BitMatrix?) {
writeInt(bm?.width ?: 0)
bm?.let {
writeInt(bm.height)
writeSizedByteArray(bm.data)
}
}
private fun Parcel.readBitMatrix(): BitMatrix? {
val width = readInt()
if (width < 1) {
private fun Bundle.getBitMatrix(): BitMatrix? {
val width = getInt(SCAN_SYMBOL_WIDTH, 0)
val height = getInt(SCAN_SYMBOL_HEIGHT, 0)
val data = getByteArray(SCAN_SYMBOL_DATA)
if (width < 1 || height < 1 || data == null) {
return null
}
val height = readInt()
val data = readSizedByteArray() ?: return null
return BitMatrix(width, height, data)
}
private const val SCAN_TEXT = "text"
private const val SCAN_RAW = "raw"
private const val SCAN_FORMAT = "format"
private const val SCAN_ERROR_CORRECTION_LEVEL = "error_correction_level"
private const val SCAN_VERSION = "version"
private const val SCAN_DATA_MASK = "data_mask"
private const val SCAN_SYMBOL_WIDTH = "symbol_width"
private const val SCAN_SYMBOL_HEIGHT = "symbol_height"
private const val SCAN_SYMBOL_DATA = "symbol_data"
private const val SCAN_SEQUENCE_SIZE = "sequence_size"
private const val SCAN_SEQUENCE_INDEX = "sequence_index"
private const val SCAN_SEQUENCE_ID = "sequence_id"
private const val SCAN_COUNTRY = "country"
private const val SCAN_ADD_ON = "add_on"
private const val SCAN_PRICE = "price"
private const val SCAN_ISSUE_NUMBER = "issue_number"
private const val SCAN_DATE_TIME = "date_time"
private const val SCAN_ID = "id"
private const val SCAN_LABEL = "label"
@@ -9,15 +9,17 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.wifi.WifiManager
import android.os.Build
import android.os.Bundle
import android.support.annotation.RequiresApi
import android.support.v14.preference.MultiSelectListPreference
import android.support.v7.preference.ListPreference
import android.support.v7.preference.Preference
import android.support.v7.preference.PreferenceFragmentCompat
import android.support.v7.preference.PreferenceGroup
import androidx.annotation.RequiresApi
import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceGroup
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.activity.AutomatedActionsActivity
import de.markusfisch.android.binaryeye.activity.NetworkSuggestionsActivity
import de.markusfisch.android.binaryeye.activity.ProfilesActivity
import de.markusfisch.android.binaryeye.activity.SplashActivity
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.app.hasBluetoothPermission
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.bluetooth.setBluetoothHosts
@@ -36,7 +38,7 @@ class PreferencesFragment : PreferenceFragmentCompat() {
key: String?
) {
key ?: return
val preference = findPreference(key) ?: return
val preference = findPreference<Preference>(key) ?: return
if (preference.key != PROFILE) {
prefs.update()
}
@@ -76,8 +78,9 @@ class PreferencesFragment : PreferenceFragmentCompat() {
preferenceScreen = null
setPreferencesFromResource(R.xml.preferences, rootKey)
preferenceScreen.sharedPreferences
.registerOnSharedPreferenceChangeListener(changeListener)
?.registerOnSharedPreferenceChangeListener(changeListener)
setSummaries(preferenceScreen)
setIcons()
wireProfiles()
wireAutomatedActions()
@@ -86,23 +89,21 @@ class PreferencesFragment : PreferenceFragmentCompat() {
}
private fun wireProfiles() {
findPreference(PROFILE).apply {
findPreference<Preference>(PROFILE)?.apply {
updateProfileSummary(this)
onPreferenceClickListener = Preference.OnPreferenceClickListener {
activity?.supportFragmentManager?.addFragment(
ProfilesFragment()
)
startActivity(Intent(activity, ProfilesActivity::class.java))
true
}
}
}
private fun wireAutomatedActions() {
findPreference(AUTOMATED_ACTIONS).apply {
findPreference<Preference>(AUTOMATED_ACTIONS)?.apply {
updateAutomatedActionsSummary(this)
onPreferenceClickListener = Preference.OnPreferenceClickListener {
activity?.supportFragmentManager?.addFragment(
AutomatedActionsFragment()
startActivity(
Intent(activity, AutomatedActionsActivity::class.java)
)
true
}
@@ -113,20 +114,20 @@ class PreferencesFragment : PreferenceFragmentCompat() {
if (prefs.sendScanBluetooth &&
activity?.hasBluetoothPermission() == true
) {
setBluetoothHosts(
findPreference("send_scan_bluetooth_host") as ListPreference
)
findPreference<ListPreference>("send_scan_bluetooth_host")?.let {
setBluetoothHosts(it)
}
}
}
private fun wireClearNetworkPreferences() {
findPreference("clear_network_suggestions").apply {
findPreference<Preference>("clear_network_suggestions")?.apply {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
// From R+ we can query past network suggestions and
// make them editable.
setOnPreferenceClickListener {
activity?.supportFragmentManager?.addFragment(
NetworkSuggestionsFragment()
startActivity(
Intent(activity, NetworkSuggestionsActivity::class.java)
)
true
}
@@ -135,10 +136,10 @@ class PreferencesFragment : PreferenceFragmentCompat() {
// Note that previous versions of this app allowed
// adding network suggestions on Q as well, so we
// need to keep this option.
setOnPreferenceClickListener {
context.askToClearNetworkSuggestions()
true
}
setOnPreferenceClickListener {
context?.askToClearNetworkSuggestions()
true
}
} else {
// There are no network suggestions below Q.
isVisible = false
@@ -176,10 +177,10 @@ class PreferencesFragment : PreferenceFragmentCompat() {
loadPreferences()
}
activity?.setTitle(R.string.preferences)
findPreference(AUTOMATED_ACTIONS)?.let {
findPreference<Preference>(AUTOMATED_ACTIONS)?.let {
updateAutomatedActionsSummary(it)
}
findPreference(PROFILE)?.let {
findPreference<Preference>(PROFILE)?.let {
updateProfileSummary(it)
}
listView.setPaddingFromWindowInsets()
@@ -190,12 +191,12 @@ class PreferencesFragment : PreferenceFragmentCompat() {
override fun onPause() {
super.onPause()
preferenceScreen.sharedPreferences
.unregisterOnSharedPreferenceChangeListener(changeListener)
?.unregisterOnSharedPreferenceChangeListener(changeListener)
}
override fun onDisplayPreferenceDialog(preference: Preference) {
if (preference is UrlPreference) {
val fm = fragmentManager
val fm = parentFragmentManager
UrlDialogFragment.newInstance(preference.key).apply {
setTargetFragment(this@PreferencesFragment, 0)
show(fm, null)
@@ -244,6 +245,45 @@ class PreferencesFragment : PreferenceFragmentCompat() {
}
}
private fun setIcons() {
setIcon(PROFILE, R.drawable.ic_action_profile)
setIcon("formats", R.drawable.ic_action_restrict_format)
setIcon("show_crop_handle", R.drawable.ic_action_scan)
setIcon("zoom_by_swiping", R.drawable.ic_action_search)
setIcon("auto_rotate", R.drawable.ic_action_rotation_unlocked)
setIcon("try_harder", R.drawable.ic_action_search)
setIcon("bulk_mode", R.drawable.ic_action_bulk_mode)
setIcon("bulk_mode_delay", R.drawable.ic_action_bulk_mode)
setIcon("show_toast_in_bulk_mode", R.drawable.ic_action_bulk_mode)
setIcon("vibrate", R.drawable.ic_action_info)
setIcon("beep", R.drawable.ic_action_info)
setIcon("beep_tone_name", R.drawable.ic_action_info)
setIcon("use_history", R.drawable.ic_action_history)
setIcon("ignore_duplicates_name", R.drawable.ic_action_remove)
setIcon("copy_immediately", R.drawable.ic_action_copy)
setIcon("send_scan_active", R.drawable.ic_action_share)
setIcon("send_scan_url", R.drawable.ic_action_link)
setIcon("send_scan_type", R.drawable.ic_action_share)
setIcon("send_scan_bluetooth", R.drawable.ic_action_wifi)
setIcon("send_scan_bluetooth_host", R.drawable.ic_action_wifi)
setIcon("open_immediately", R.drawable.ic_action_open)
setIcon("show_meta_data", R.drawable.ic_action_info)
setIcon("show_hex_dump", R.drawable.ic_action_info)
setIcon("show_checksum", R.drawable.ic_action_info)
setIcon("show_recreation", R.drawable.ic_action_create)
setIcon("close_automatically", R.drawable.ic_action_remove)
setIcon("default_search_url", R.drawable.ic_action_search)
setIcon("open_with_url", R.drawable.ic_action_link)
setIcon(AUTOMATED_ACTIONS, R.drawable.ic_action_create)
setIcon("clear_network_suggestions", R.drawable.ic_action_wifi)
setIcon("brighten_screen", R.drawable.ic_action_bright)
setIcon("custom_locale", R.drawable.ic_action_preferences)
}
private fun setIcon(key: String, iconResId: Int) {
findPreference<Preference>(key)?.setIcon(iconResId)
}
private fun updateAutomatedActionsSummary(preference: Preference) {
val count = prefs.automatedActions.size
preference.summary = if (count == 0) {
@@ -270,12 +310,10 @@ class PreferencesFragment : PreferenceFragmentCompat() {
private fun Activity.restartApp() {
val intent = Intent(this, SplashActivity::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
)
}
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
)
startActivity(intent)
finish()
// Restart to begin with an unmodified Locale to follow system settings.
@@ -1,9 +1,9 @@
package de.markusfisch.android.binaryeye.fragment
import android.os.Bundle
import android.support.v7.preference.PreferenceDialogFragmentCompat
import android.view.View
import android.widget.TextView
import androidx.preference.PreferenceDialogFragmentCompat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.database.Scan
@@ -15,10 +15,10 @@ class UrlDialogFragment : PreferenceDialogFragmentCompat() {
private var urlView: TextView? = null
private var testButton: TextView? = null
override fun onBindDialogView(view: View?) {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
urlView = view?.findViewById(R.id.url)
testButton = view?.findViewById(R.id.test_url)
urlView = view.findViewById(R.id.url)
testButton = view.findViewById(R.id.test_url)
testButton?.setOnClickListener {
testUrl(testButton)
}
@@ -51,7 +51,7 @@ class UrlDialogFragment : PreferenceDialogFragmentCompat() {
Scan(
"test",
null,
BarcodeFormat.NONE
BarcodeFormat.None
).sendAsync(url, prefs.sendScanType) { code, body ->
textView.text = when {
code != null -> "$code"
@@ -3,7 +3,7 @@ package de.markusfisch.android.binaryeye.graphics
import android.content.Context
import android.graphics.DashPathEffect
import android.graphics.Paint
import android.support.v4.content.ContextCompat
import androidx.core.content.ContextCompat
import de.markusfisch.android.binaryeye.R
fun Context.getDashedBorderPaint() = Paint(Paint.ANTI_ALIAS_FLAG).apply {
@@ -5,26 +5,22 @@ import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.createBitmap
fun Resources.getBitmapFromDrawable(
resId: Int
): Bitmap = getBitmapFromDrawable(getDrawableCompat(resId))
fun Resources.getDrawableCompat(
resId: Int
): Drawable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getDrawable(resId, null)
} else {
@Suppress("DEPRECATION")
getDrawable(resId)
}
): Bitmap = getBitmapFromDrawable(
ResourcesCompat.getDrawable(this, resId, null) ?: throw Resources.NotFoundException(
"Drawable resource ID #0x${resId.toString(16)}"
)
)
private fun getBitmapFromDrawable(drawable: Drawable): Bitmap {
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val bitmap = Bitmap.createBitmap(
val bitmap = createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
@@ -2,12 +2,13 @@ package de.markusfisch.android.binaryeye.graphics
import android.graphics.Bitmap
import android.graphics.Canvas
import androidx.core.graphics.createBitmap
fun Bitmap.fixTransparency(): Bitmap {
val result = analyzeTransparency()
val cfg = config
return if (cfg != null && result.hasTransparentPixels) {
val copy = Bitmap.createBitmap(
val copy = createBitmap(
width,
height,
cfg
@@ -6,8 +6,8 @@ import android.content.Context
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.support.annotation.MainThread
import android.widget.EditText
import androidx.annotation.MainThread
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.alertDialog
import java.io.File
@@ -2,6 +2,7 @@ package de.markusfisch.android.binaryeye.preference
import android.content.SharedPreferences
import android.graphics.Point
import androidx.core.content.edit
import org.json.JSONObject
fun SharedPreferences.storeCropHandle(
@@ -10,13 +11,15 @@ fun SharedPreferences.storeCropHandle(
viewHeight: Int,
cropHandle: Point
) {
edit().putString(
cropHandleKey(name, viewWidth, viewHeight),
JSONObject().apply {
put("x", cropHandle.x)
put("y", cropHandle.y)
}.toString()
).apply()
edit {
putString(
cropHandleKey(name, viewWidth, viewHeight),
JSONObject().apply {
put("x", cropHandle.x)
put("y", cropHandle.y)
}.toString()
)
}
}
fun SharedPreferences.restoreCropHandle(
@@ -5,12 +5,12 @@ import android.content.Context
import android.content.SharedPreferences
import android.graphics.Point
import android.media.ToneGenerator
import android.os.Build
import android.preference.PreferenceManager
import android.support.annotation.RequiresApi
import androidx.core.content.edit
import de.markusfisch.android.binaryeye.automation.AutomatedAction
import de.markusfisch.android.binaryeye.automation.AutomatedAction.Companion.fromJsonArray
import de.markusfisch.android.binaryeye.automation.AutomatedAction.Companion.toJsonArray
import de.markusfisch.android.binaryeye.zxingcpp.migrateBarcodeFormatName
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
import org.json.JSONArray
@@ -22,31 +22,39 @@ class Preferences {
var profile: String? = null
set(value) {
defaultPreferences.edit().putString(PROFILE, value).apply()
defaultPreferences.edit {
putString(PROFILE, value)
}
field = value
}
var barcodeFormats = setOf(
BarcodeFormat.AZTEC.name,
BarcodeFormat.CODABAR.name,
BarcodeFormat.CODE_39.name,
BarcodeFormat.CODE_93.name,
BarcodeFormat.CODE_128.name,
BarcodeFormat.DATA_BAR.name,
BarcodeFormat.DATA_BAR_EXPANDED.name,
BarcodeFormat.DATA_MATRIX.name,
BarcodeFormat.DX_FILM_EDGE.name,
BarcodeFormat.EAN_8.name,
BarcodeFormat.EAN_13.name,
BarcodeFormat.Aztec.name,
BarcodeFormat.Codabar.name,
BarcodeFormat.Code39.name,
BarcodeFormat.Code39Ext.name,
BarcodeFormat.Code32.name,
BarcodeFormat.PZN.name,
BarcodeFormat.Code93.name,
BarcodeFormat.Code128.name,
BarcodeFormat.DataBar.name,
BarcodeFormat.DataBarOmni.name,
BarcodeFormat.DataBarStk.name,
BarcodeFormat.DataBarLtd.name,
BarcodeFormat.DataBarExp.name,
BarcodeFormat.DataBarExpStk.name,
BarcodeFormat.DataMatrix.name,
BarcodeFormat.DXFilmEdge.name,
BarcodeFormat.EAN8.name,
BarcodeFormat.EAN13.name,
BarcodeFormat.ITF.name,
BarcodeFormat.MAXICODE.name,
BarcodeFormat.PDF_417.name,
BarcodeFormat.QR_CODE.name,
BarcodeFormat.MICRO_QR_CODE.name,
BarcodeFormat.RMQR_CODE.name,
BarcodeFormat.UPC_A.name,
BarcodeFormat.UPC_E.name,
BarcodeFormat.MaxiCode.name,
BarcodeFormat.PDF417.name,
BarcodeFormat.QRCode.name,
BarcodeFormat.MicroQRCode.name,
BarcodeFormat.RMQRCode.name,
BarcodeFormat.UPCA.name,
BarcodeFormat.UPCE.name,
)
@RequiresApi(Build.VERSION_CODES.HONEYCOMB)
set(value) {
apply(BARCODE_FORMATS, value)
field = value
@@ -270,10 +278,9 @@ class Preferences {
}
fun saveProfiles() {
defaultPreferences.edit().putString(
PROFILES,
JSONArray(profiles).toString()
).apply()
defaultPreferences.edit {
putString(PROFILES, JSONArray(profiles).toString())
}
}
fun loadProfiles() {
@@ -286,14 +293,24 @@ class Preferences {
}
fun update() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
preferences.getStringSet(BARCODE_FORMATS, barcodeFormats)?.let {
barcodeFormats = addFormatsOnUpdate(
it,
BarcodeFormat.RMQR_CODE,
BarcodeFormat.DX_FILM_EDGE
)
preferences.getStringSet(BARCODE_FORMATS, barcodeFormats)?.let {
val migrated = migrateBarcodeFormats(it)
if (migrated != it) {
apply(BARCODE_FORMATS, migrated)
}
barcodeFormats = addFormatsOnUpdate(
migrated,
BarcodeFormat.Code39Ext,
BarcodeFormat.Code32,
BarcodeFormat.PZN,
BarcodeFormat.DataBarOmni,
BarcodeFormat.DataBarStk,
BarcodeFormat.DataBarLtd,
BarcodeFormat.DataBarExp,
BarcodeFormat.DataBarExpStk,
BarcodeFormat.RMQRCode,
BarcodeFormat.DXFilmEdge
)
}
showCropHandle = preferences.getBoolean(
@@ -322,7 +339,9 @@ class Preferences {
// Map old setting to new one if it wasn't on default value.
val ignoreConsecutiveDuplicates = "ignore_consecutive_duplicates"
if (!preferences.getBoolean(ignoreConsecutiveDuplicates, true)) {
preferences.edit().remove(ignoreConsecutiveDuplicates).apply()
preferences.edit {
remove(ignoreConsecutiveDuplicates)
}
ignoreDuplicatesName = "accept_duplicates"
}
@@ -418,12 +437,20 @@ class Preferences {
for (format in formats) {
val name = "${format.name}_added"
if (!preferences.getBoolean(name, false)) {
preferences.edit().putBoolean(name, true).apply()
preferences.edit {
putBoolean(name, true)
}
add(format.name)
}
}
}
private fun migrateBarcodeFormats(formats: Set<String>): Set<String> {
return formats.mapTo(mutableSetOf()) { format ->
format.migrateBarcodeFormatName()
}
}
fun restoreCropHandle(
name: String,
viewWidth: Int,
@@ -452,33 +479,41 @@ class Preferences {
else -> IgnoreDuplicates.Never
}
private fun put(label: String, value: Boolean) =
preferences.edit().putBoolean(label, value)
private fun apply(label: String, value: Boolean) {
put(label, value).apply()
preferences.edit {
putBoolean(label, value)
}
}
private fun apply(label: String, value: String) {
preferences.edit().putString(label, value).apply()
preferences.edit {
putString(label, value)
}
}
private fun apply(label: String, value: Int) {
preferences.edit().putInt(label, value).apply()
preferences.edit {
putInt(label, value)
}
}
private fun apply(label: String, value: Float) {
preferences.edit().putFloat(label, value).apply()
preferences.edit {
putFloat(label, value)
}
}
@SuppressLint("ApplySharedPref")
private fun commit(label: String, value: String) {
preferences.edit().putString(label, value).commit()
preferences.edit(true) {
putString(label, value)
}
}
@RequiresApi(Build.VERSION_CODES.HONEYCOMB)
private fun apply(label: String, value: Set<String>) {
preferences.edit().putStringSet(label, value).apply()
preferences.edit {
putStringSet(label, value)
}
}
companion object {
@@ -1,12 +1,12 @@
package de.markusfisch.android.binaryeye.preference
import android.content.Context
import android.support.v7.preference.DialogPreference
import android.util.AttributeSet
import androidx.preference.DialogPreference
import de.markusfisch.android.binaryeye.R
class UrlPreference(
context: Context?,
context: Context,
attrs: AttributeSet?
) : DialogPreference(context, attrs) {
private var url: String? = null
@@ -5,7 +5,7 @@ import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import android.service.quicksettings.TileService
import android.support.annotation.RequiresApi
import androidx.annotation.RequiresApi
import de.markusfisch.android.binaryeye.activity.CameraActivity
@RequiresApi(Build.VERSION_CODES.N)
@@ -2,16 +2,16 @@ package de.markusfisch.android.binaryeye.view
import android.content.Context
import android.content.ContextWrapper
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.view.View
import android.view.WindowManager
import android.widget.AbsListView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import de.markusfisch.android.binaryeye.R
val systemBarListViewScrollListener = object : AbsListView.OnScrollListener {
@@ -43,7 +43,7 @@ val systemBarRecyclerViewScrollListener = object : RecyclerView.OnScrollListener
layoutManager.findFirstCompletelyVisibleItemPosition() != 0
val scrollable = scrolled ||
layoutManager.findLastVisibleItemPosition() <
recyclerView.adapter.itemCount - 1
(recyclerView.adapter?.itemCount ?: 0) - 1
colorSystemAndToolBars(recyclerView.context, scrolled, scrollable)
}
@@ -62,23 +62,21 @@ private fun lastChildOutOfView(listView: AbsListView): Boolean {
}
fun AppCompatActivity.initBars() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Keeps the soft keyboard from repositioning the layout.
window.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
)
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
// Keeps the soft keyboard from repositioning the layout.
window.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
)
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
colorSystemAndToolBars(this)
setPaddingFromWindowInsets(
findViewById(R.id.main),
(findViewById(R.id.toolbar) as Toolbar).apply {
setSupportActionBar(this)
},
findViewById(R.id.navbar)?.apply {
findViewById<View>(R.id.navbar)?.apply {
setBackgroundColor(translucentPrimaryColor)
}
)
@@ -94,7 +92,6 @@ fun unlockStatusBarColor() {
}
private var translucentPrimaryColor = 0
private val actionBarBackground = ColorDrawable()
fun colorSystemAndToolBars(
context: Context,
scrolled: Boolean = false,
@@ -108,40 +105,29 @@ fun colorSystemAndToolBars(
}
val topColor = if (scrolled) translucentPrimaryColor else 0
val activity = getAppCompatActivity(context) ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// System bars no longer have a background from SDK35+.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
val window = activity.window
if (!statusBarColorLocked) {
@Suppress("DEPRECATION")
window.statusBarColor = topColor
}
// System bars no longer have a background from SDK35+.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
val window = activity.window
if (!statusBarColorLocked) {
@Suppress("DEPRECATION")
window.navigationBarColor = if (scrolled || scrollable) {
translucentPrimaryColor
} else {
0
}
window.statusBarColor = topColor
}
@Suppress("DEPRECATION")
window.navigationBarColor = if (scrolled || scrollable) {
translucentPrimaryColor
} else {
activity.findViewById(R.id.navbar)?.apply {
visibility = if (scrolled || scrollable) {
View.VISIBLE
} else {
View.GONE
}
0
}
} else {
activity.findViewById<View>(R.id.navbar)?.apply {
visibility = if (scrolled || scrollable) {
View.VISIBLE
} else {
View.GONE
}
}
}
activity.supportActionBar?.setBackgroundDrawable(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// Avoid allocation on Honeycomb and better.
actionBarBackground.color = topColor
actionBarBackground
} else {
// ColorDrawable.setColor() doesn't exist pre Honeycomb.
ColorDrawable(topColor)
}
)
activity.supportActionBar?.setBackgroundDrawable(topColor.toDrawable())
}
private fun getAppCompatActivity(context: Context): AppCompatActivity? {
@@ -2,12 +2,11 @@ package de.markusfisch.android.binaryeye.view
import android.graphics.Rect
import android.os.Build
import android.support.annotation.RequiresApi
import android.support.v4.view.ViewCompat
import android.support.v4.view.WindowInsetsCompat
import android.support.v7.widget.Toolbar
import android.view.View
import android.view.WindowInsets
import androidx.appcompat.widget.Toolbar
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
private var toolbarHeight = 0
fun setPaddingFromWindowInsets(
@@ -53,19 +52,15 @@ fun View.setPaddingFromWindowInsets() {
// For the original post see here:
// https://medium.com/androiddevelopers/windowinsets-listeners-to-layouts-8f9ccc8fa4d1
fun View.doOnApplyWindowInsets(f: (View, Rect) -> Unit) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
f(this, insetsWithToolbar())
} else {
ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->
f(v, insetsWithToolbar(insets))
insets
}
// It's important to explicitly request the insets (again) in
// case the view was created in Fragment.onCreateView() because
// setOnApplyWindowInsetsListener() won't fire when the view
// isn't attached.
requestApplyInsetsWhenAttached()
ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->
f(v, insetsWithToolbar(insets))
insets
}
// It's important to explicitly request the insets (again) in
// case the view was created in Fragment.onCreateView() because
// setOnApplyWindowInsetsListener() won't fire when the view
// isn't attached.
requestApplyInsetsWhenAttached()
}
private fun insetsWithToolbar(insets: WindowInsetsCompat? = null) = Rect(
@@ -75,7 +70,6 @@ private fun insetsWithToolbar(insets: WindowInsetsCompat? = null) = Rect(
insets?.systemWindowInsetBottom ?: 0
)
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun View.requestApplyInsetsWhenAttached() {
if (isAttachedToWindow) {
ViewCompat.requestApplyInsets(this)
@@ -9,14 +9,14 @@ import android.graphics.Point
import android.graphics.Rect
import android.graphics.Region
import android.os.Build
import android.support.v4.content.ContextCompat
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.graphics.withSave
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.graphics.getBitmapFromDrawable
import de.markusfisch.android.binaryeye.graphics.getDashedBorderPaint
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -34,7 +34,6 @@ class DetectorView : View {
coordinatesLast = 0
invalidate()
}
private val roiPaint = context.getDashedBorderPaint()
private val dotPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = ContextCompat.getColor(context, R.color.dot)
style = Paint.Style.FILL
@@ -270,10 +269,8 @@ class DetectorView : View {
if (minDist < 1) {
return
}
// canvas.clipRect() doesn't work reliably below KITKAT.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
val radius = min(minDist / 2, cornerRadius).toFloat()
save()
val radius = min(minDist / 2, cornerRadius).toFloat()
withSave {
clipOutPathCompat(
calculateRoundedRectPath(
roi.left.toFloat(),
@@ -285,9 +282,6 @@ class DetectorView : View {
)
)
drawColor(shadeColor)
restore()
} else {
drawRect(roi, roiPaint)
}
}
@@ -0,0 +1,36 @@
package de.markusfisch.android.binaryeye.zxingcpp
/**
* Translate legacy barcode format names from ZXingC++ before 3.x
* to current BarcodeFormat enum names.
*/
fun String.migrateBarcodeFormatName() = when (this) {
"NONE" -> "None"
"AZTEC" -> "Aztec"
"CODABAR" -> "Codabar"
"CODE_39" -> "Code39"
"CODE_39_EXT" -> "Code39Ext"
"CODE_32" -> "Code32"
"PZN" -> "PZN"
"CODE_93" -> "Code93"
"CODE_128" -> "Code128"
"DATA_BAR" -> "DataBar"
"DATA_BAR_OMNI" -> "DataBarOmni"
"DATA_BAR_STK" -> "DataBarStk"
"DATA_BAR_LTD" -> "DataBarLtd"
"DATA_BAR_EXPANDED" -> "DataBarExp"
"DATA_BAR_EXP_STK" -> "DataBarExpStk"
"DATA_MATRIX" -> "DataMatrix"
"DX_FILM_EDGE" -> "DXFilmEdge"
"EAN_8" -> "EAN8"
"EAN_13" -> "EAN13"
"ITF" -> "ITF"
"MAXICODE" -> "MaxiCode"
"PDF_417" -> "PDF417"
"QR_CODE" -> "QRCode"
"MICRO_QR_CODE" -> "MicroQRCode"
"RMQR_CODE" -> "RMQRCode"
"UPC_A" -> "UPCA"
"UPC_E" -> "UPCE"
else -> this
}
+9 -8
View File
@@ -1,13 +1,16 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<de.markusfisch.android.cameraview.widget.CameraView
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.camera.view.PreviewView
android:id="@+id/camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="match_parent"
app:scaleType="fillCenter"/>
<de.markusfisch.android.binaryeye.widget.DetectorView
android:id="@+id/detector_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -19,15 +22,13 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:layout_marginBottom="16dp"
android:layout_marginRight="88dp"
android:layout_marginEnd="88dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:padding="24dp"/>
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/FAB"
android:id="@+id/flash"
android:contentDescription="@string/toggle_flash"
android:src="@drawable/ic_action_flash"/>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</merge>
+1 -1
View File
@@ -1,5 +1,5 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
+3 -3
View File
@@ -8,17 +8,17 @@
android:id="@+id/detector_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="@bool/fitsSystemWindows">
<include layout="@layout/toolbar"/>
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/FAB"
android:id="@+id/scan"
android:contentDescription="@string/scan_code"
android:src="@drawable/ic_action_next"/>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<include layout="@layout/navbar"/>
</merge>
+4 -3
View File
@@ -1,5 +1,6 @@
<ScrollView
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"
@@ -22,11 +23,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:drawableTop="@drawable/logo"
android:drawablePadding="16dp"
android:gravity="center"
android:text="@string/welcome"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/welcome"/>
app:drawableTopCompat="@drawable/logo"/>
<LinearLayout
style="@style/WelcomeButton"
@@ -54,4 +55,4 @@
</LinearLayout>
</LinearLayout>
</FrameLayout>
</ScrollView>
</ScrollView>
@@ -9,7 +9,7 @@
android:clipToPadding="false"
android:divider="@color/separator"
android:dividerHeight="1px"/>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/inset_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -22,10 +22,10 @@
android:gravity="center"
android:padding="32dp"
android:text="@string/automated_actions_empty"/>
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/FAB"
android:id="@+id/add"
android:contentDescription="@string/automated_action_add"
android:src="@drawable/ic_action_create"/>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>
+3 -3
View File
@@ -7,14 +7,14 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/view_barcode"/>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/inset_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/FAB"
android:id="@+id/share"
android:contentDescription="@string/share"
android:src="@drawable/ic_action_share"/>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>
+3 -3
View File
@@ -77,14 +77,14 @@
android:contentDescription="@string/view_barcode"/>
</LinearLayout>
</de.markusfisch.android.binaryeye.widget.ConfinedScrollView>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/inset_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/FAB"
android:id="@+id/open"
android:contentDescription="@string/open_url"
android:src="@drawable/ic_action_open"/>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>
+4 -5
View File
@@ -65,22 +65,21 @@
android:id="@+id/colors"
android:layout_marginBottom="16dp"
android:entries="@array/colors"/>
<android.support.v7.widget.SwitchCompat
android:visibility="gone"
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/add_quiet_zone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/add_quiet_zone"/>
</LinearLayout>
</de.markusfisch.android.binaryeye.widget.ConfinedScrollView>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/inset_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/FAB"
android:id="@+id/encode"
android:contentDescription="@string/encode"
android:src="@drawable/ic_action_next"/>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>
+4 -4
View File
@@ -10,11 +10,11 @@
android:clipToPadding="false"
android:divider="@color/separator"
android:dividerHeight="1px"/>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/inset_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.SwitchCompat
<androidx.appcompat.widget.SwitchCompat
android:visibility="gone"
android:id="@+id/use_history"
android:layout_width="match_parent"
@@ -22,12 +22,12 @@
android:layout_alignParentTop="true"
android:padding="16dp"
android:text="@string/use_history"/>
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.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>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<include layout="@layout/progress_view"/>
</FrameLayout>
@@ -10,7 +10,7 @@
android:clipToPadding="false"
android:divider="@color/separator"
android:dividerHeight="1px"/>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/inset_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -23,10 +23,10 @@
android:gravity="center"
android:padding="32dp"
android:text="@string/clear_network_suggestions_nothing_to_remove"/>
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/FAB"
android:id="@+id/remove"
android:contentDescription="@string/remove_suggestion"
android:src="@drawable/ic_action_remove"/>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>
@@ -9,7 +9,7 @@
android:clipToPadding="false"
android:divider="@color/separator"
android:dividerHeight="1px"/>
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/inset_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -22,10 +22,10 @@
android:gravity="center"
android:padding="32dp"
android:text="@string/profile_empty"/>
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/FAB"
android:id="@+id/add"
android:contentDescription="@string/profile_add"
android:src="@drawable/ic_action_create"/>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>
-3
View File
@@ -9,7 +9,6 @@
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"/>
<TextView
@@ -18,9 +17,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:layout_toRightOf="@+id/time"
android:layout_toEndOf="@+id/time"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"/>
<TextView
+1 -1
View File
@@ -1,4 +1,4 @@
<android.support.v7.widget.Toolbar
<androidx.appcompat.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="Overdraw"
+1 -1
View File
@@ -5,7 +5,7 @@
android:id="@+id/search"
android:icon="@drawable/ic_action_search"
android:title="@string/search_scan"
material:actionViewClass="android.support.v7.widget.SearchView"
material:actionViewClass="androidx.appcompat.widget.SearchView"
material:showAsAction="collapseActionView|always"/>
<item
android:id="@+id/export_history"
-1
View File
@@ -141,7 +141,6 @@
<string name="automated_actions_none">Tidak ada tindakan otomatis</string>
<string name="automated_actions_empty">Belum ada tindakan otomatis</string>
<plurals name="automated_actions_count">
<item quantity="one">%d tindakan otomatis</item>
<item quantity="other">%d tindakan otomatis</item>
</plurals>
<string name="automated_action_add">Tambah tindakan</string>
-1
View File
@@ -141,7 +141,6 @@
<string name="automated_actions_none">自動アクションはありません</string>
<string name="automated_actions_empty">まだ自動アクションがありません</string>
<plurals name="automated_actions_count">
<item quantity="one">%d 件の自動アクション</item>
<item quantity="other">%d 件の自動アクション</item>
</plurals>
<string name="automated_action_add">アクションを追加</string>
-1
View File
@@ -141,7 +141,6 @@
<string name="automated_actions_none">자동 작업 없음</string>
<string name="automated_actions_empty">아직 자동 작업 없음</string>
<plurals name="automated_actions_count">
<item quantity="one">%d개의 자동 작업</item>
<item quantity="other">%d개의 자동 작업</item>
</plurals>
<string name="automated_action_add">작업 추가</string>
@@ -13,7 +13,6 @@
<string name="binary_data">(dados em binário)</string>
<plurals name="barcode_info">
<item quantity="one">%2$d caractere, %1$s</item>
<item quantity="many">%2$d de characters, %1$s</item>
<item quantity="other">%2$d caracteres, %1$s</item>
</plurals>
<string name="error_correction_level">Nível de correção de erro</string>
+13 -13
View File
@@ -42,11 +42,11 @@
<string name="error_flash">Ошибка при переключении вспышки</string>
<string name="share">Поделиться</string>
<string name="share_file">Поделиться файлом</string>
<string name="share_as">Поделиться как?</string>
<string name="copy_to_clipboard">Скопировать в буфер обмена</string>
<string name="copy_as_deeplink">Копировать как диплинк</string>
<string name="share_as">Поделиться</string>
<string name="copy_to_clipboard">Копировать в буфер обмена</string>
<string name="copy_as_deeplink">Копировать как прямую ссылку</string>
<string name="copied_to_clipboard">Скопировано в буфер обмена</string>
<string name="copy_password">Скопировать пароль в буфер обмена</string>
<string name="copy_password">Копировать пароль в буфер обмена</string>
<string name="open_url">Открыть URL</string>
<string name="cannot_resolve_action">Не найдено приложение для этого действия</string>
<string name="pick_search_engine">Выберите поисковую систему</string>
@@ -143,7 +143,7 @@
<string name="automated_actions">Автоматические действия</string>
<string name="automated_actions_none">Нет автоматических действий</string>
<string name="automated_actions_empty">Пока нет автоматических действий</string>
<string name="automated_actions_empty">Автоматических действий пока нет</string>
<plurals name="automated_actions_count">
<item quantity="one">%d автоматическое действие</item>
<item quantity="few">%d автоматических действия</item>
@@ -151,9 +151,9 @@
<item quantity="other">%d автоматических действий</item>
</plurals>
<string name="automated_action_add">Добавить действие</string>
<string name="automated_action_edit">Редактировать действие</string>
<string name="automated_action_edit">Изменить действие</string>
<string name="automated_action_regex">Регулярное выражение</string>
<string name="automated_action_regex_hint">напр. ^https?://.*</string>
<string name="automated_action_regex_hint">например: ^https?://.*</string>
<string name="automated_action_regex_required">Требуется регулярное выражение</string>
<string name="automated_action_regex_invalid">Неверное регулярное выражение</string>
<string name="automated_action_type">Тип действия</string>
@@ -202,10 +202,10 @@
<string name="enter_name">Укажите название сканирования</string>
<string name="enter_name_hint">Название сканирования</string>
<string name="no_barcode_found">Штрих-код не найден</string>
<string name="pick_list_separator">Как разделять пункты списка?</string>
<string name="pick_list_separator">Разделение элементов списка</string>
<string name="separator_line_break">Разрыв строки</string>
<string name="separator_ruler">Горизонтальная линия</string>
<string name="save_as_file_name">Сохранить в папке скачиваний Downloads?</string>
<string name="save_as_file_name">Сохранить в файл с именем</string>
<string name="file_name">Имя файла</string>
<string name="error_saving_file">Невозможно сохранить файл</string>
<string name="error_file_exists">Файл уже существует</string>
@@ -228,12 +228,12 @@
<string name="add_to_history">Добавить в историю</string>
<string name="added_to_history">Добавлено в историю</string>
<string name="export_to_file">Экспортировать в файл</string>
<string name="export_as">Экспортировать как?</string>
<string name="export_as">Формат файла экспорта</string>
<string name="export_csv_comma">CSV (разделитель – запятая)</string>
<string name="export_csv_semicolon">CSV (разделитель – точка с запятой)</string>
<string name="export_json">JSON</string>
<string name="export_database">База данных SQLite</string>
<string name="saved_in_downloads">Сохранено в загрузках</string>
<string name="saved_in_downloads">Сохранено в папке Downloads</string>
<string name="rotate_image_cw">Повернуть изображение по часовой стрелке</string>
<string name="toggle_free_rotation">Переключить свободный поворот</string>
<string name="pick_file">Открыть файл</string>
@@ -260,12 +260,12 @@
<string name="email_subject">Тема</string>
<string name="email_body">Текст</string>
<string name="scheme">Схема</string>
<string name="host">Хост</string>
<string name="host">Узел</string>
<string name="query">Запрос</string>
<string name="automated_action_type_custom_intent">Пользовательский Intent</string>
<string name="automated_action_intent_uri_template">Шаблон URI Intent</string>
<string name="automated_action_intent_uri_hint">intent:#Intent;action=android.intent.action.SEND;type=text/plain;S.android.intent.extra.TEXT={RESULT};end</string>
<string name="automated_action_intent_uri_required">URI Intent обязателен</string>
<string name="automated_action_intent_uri_required">Требуется URI Intent</string>
<string name="automated_action_intent_uri_invalid">Недопустимый URI Intent</string>
<string name="automated_action_list_custom_intent">Пользовательский Intent: %s</string>
<string name="automated_action_list_custom_intent_empty">Пользовательский Intent</string>
-11
View File
@@ -1,11 +0,0 @@
<resources>
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@android:color/transparent</item>
<item name="colorAccent">@color/accent</item>
<item name="android:windowBackground">@drawable/background_window</item>
<item name="windowActionModeOverlay">true</item>
<item name="actionModeBackground">@color/accent</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>
</resources>
-12
View File
@@ -1,12 +0,0 @@
<resources>
<style name="Button" parent="@android:style/Widget.Button">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">@drawable/button_raised</item>
<item name="android:textColor">@color/button_text</item>
<item name="android:textStyle">bold</item>
<item name="android:paddingLeft">@dimen/button_padding</item>
<item name="android:paddingRight">@dimen/button_padding</item>
<item name="android:maxLines">1</item>
</style>
</resources>
-1
View File
@@ -141,7 +141,6 @@
<string name="automated_actions_none">Không có hành động tự động</string>
<string name="automated_actions_empty">Chưa có hành động tự động</string>
<plurals name="automated_actions_count">
<item quantity="one">%d hành động tự động</item>
<item quantity="other">%d hành động tự động</item>
</plurals>
<string name="automated_action_add">Thêm hành động</string>
@@ -141,7 +141,6 @@
<string name="automated_actions_none">没有自动操作</string>
<string name="automated_actions_empty">尚无自动操作</string>
<plurals name="automated_actions_count">
<item quantity="one">%d 个自动操作</item>
<item quantity="other">%d 个自动操作</item>
</plurals>
<string name="automated_action_add">添加操作</string>
@@ -141,7 +141,6 @@
<string name="automated_actions_none">沒有自動操作</string>
<string name="automated_actions_empty">尚無自動操作</string>
<plurals name="automated_actions_count">
<item quantity="one">%d 個自動操作</item>
<item quantity="other">%d 個自動操作</item>
</plurals>
<string name="automated_action_add">新增操作</string>
+34 -22
View File
@@ -1,18 +1,24 @@
<resources>
<string-array name="barcode_formats_names">
<item>AZTEC</item>
<item>CODABAR</item>
<item>Aztec</item>
<item>Codabar</item>
<item>CODE 39</item>
<item>CODE 39 EXT</item>
<item>CODE 32</item>
<item>PZN</item>
<item>CODE 93</item>
<item>CODE 128</item>
<item>DATA BAR</item>
<item>DATA BAR OMNI</item>
<item>DATA BAR STK</item>
<item>DATA BAR LTD</item>
<item>DATA BAR EXPANDED</item>
<item>DATA BAR EXP STK</item>
<item>DATA MATRIX</item>
<item>DX FILM EDGE</item>
<item>EAN 8</item>
<item>EAN 13</item>
<item>ITF</item>
<item>MAXICODE</item>
<item>MaxiCode</item>
<item>PDF 417</item>
<item>QR CODE</item>
<item>MICRO QR CODE</item>
@@ -21,24 +27,30 @@
<item>UPC E</item>
</string-array>
<string-array name="barcode_formats_values">
<item>AZTEC</item>
<item>CODABAR</item>
<item>CODE_39</item>
<item>CODE_93</item>
<item>CODE_128</item>
<item>DATA_BAR</item>
<item>DATA_BAR_EXPANDED</item>
<item>DATA_MATRIX</item>
<item>DX_FILM_EDGE</item>
<item>EAN_8</item>
<item>EAN_13</item>
<item>Aztec</item>
<item>Codabar</item>
<item>Code39</item>
<item>Code39Ext</item>
<item>Code32</item>
<item>PZN</item>
<item>Code93</item>
<item>Code128</item>
<item>DataBarOmni</item>
<item>DataBarStk</item>
<item>DataBarLtd</item>
<item>DataBarExp</item>
<item>DataBarExpStk</item>
<item>DataMatrix</item>
<item>DXFilmEdge</item>
<item>EAN8</item>
<item>EAN13</item>
<item>ITF</item>
<item>MAXICODE</item>
<item>PDF_417</item>
<item>QR_CODE</item>
<item>MICRO_QR_CODE</item>
<item>RMQR_CODE</item>
<item>UPC_A</item>
<item>UPC_E</item>
<item>MaxiCode</item>
<item>PDF417</item>
<item>QRCode</item>
<item>MicroQRCode</item>
<item>RMQRCode</item>
<item>UPCA</item>
<item>UPCE</item>
</string-array>
</resources>
@@ -0,0 +1,7 @@
<resources>
<string-array name="mqr_error_correction_levels">
<item>@string/error_correction_level_l</item>
<item>@string/error_correction_level_m</item>
<item>@string/error_correction_level_q</item>
</string-array>
</resources>
@@ -0,0 +1,6 @@
<resources>
<string-array name="rmqr_error_correction_levels">
<item>@string/error_correction_level_m</item>
<item>@string/error_correction_level_h</item>
</string-array>
</resources>
+2 -3
View File
@@ -6,7 +6,7 @@
<item name="android:windowBackground">@drawable/background_window</item>
<item name="windowActionModeOverlay">true</item>
<item name="actionModeBackground">@color/accent</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>
<style name="SplashTheme" parent="@style/AppTheme">
<item name="android:windowBackground">@drawable/background_splash</item>
@@ -17,8 +17,7 @@
</style>
<style name="Button" parent="@android:style/Widget.Button">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">48dp</item>
<item name="android:layout_gravity">center_horizontal</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">@drawable/button_raised</item>
<item name="android:textColor">@color/button_text</item>
<item name="android:textStyle">bold</item>
-178
View File
@@ -1,178 +0,0 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/preferences"
android:key="preferences">
<PreferenceCategory android:title="@string/profile_category">
<Preference
android:key="profile"
android:title="@string/profile"
android:summary="@string/profile_default"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/scan_category">
<MultiSelectListPreference
android:key="formats"
android:title="@string/barcode_formats"
android:entries="@array/barcode_formats_names"
android:entryValues="@array/barcode_formats_values"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_crop_handle"
android:title="@string/show_crop_handle"
android:summary="@string/show_crop_handle_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="zoom_by_swiping"
android:title="@string/zoom_by_swiping"
android:summary="@string/zoom_by_swiping_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="auto_rotate"
android:title="@string/auto_rotate"
android:summary="@string/auto_rotate_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="try_harder"
android:title="@string/try_harder"
android:summary="@string/try_harder_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="bulk_mode"
android:title="@string/bulk_mode"
android:summary="@string/bulk_mode_summary"/>
<ListPreference
android:key="bulk_mode_delay"
android:title="@string/bulk_mode_delay"
android:entries="@array/bulk_mode_delays_names"
android:entryValues="@array/bulk_mode_delays_values"
android:defaultValue="500"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_toast_in_bulk_mode"
android:title="@string/show_toast_in_bulk_mode"
android:summary="@string/show_toast_in_bulk_mode_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="vibrate"
android:title="@string/vibrate"
android:summary="@string/vibrate_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="beep"
android:title="@string/beep"
android:summary="@string/beep_summary"/>
<ListPreference
android:key="beep_tone_name"
android:title="@string/beep_tone"
android:entries="@array/beep_tones_names"
android:entryValues="@array/beep_tones_values"
android:defaultValue="tone_prop_beep"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="use_history"
android:title="@string/use_history"
android:summary="@string/use_history_summary"/>
<ListPreference
android:key="ignore_duplicates_name"
android:title="@string/ignore_duplicates"
android:entries="@array/ignore_duplicates_names"
android:entryValues="@array/ignore_duplicates_values"
android:defaultValue="ignore_consecutive_duplicates"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="copy_immediately"
android:title="@string/copy_immediately"
android:summary="@string/copy_immediately_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/send_category">
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="send_scan_active"
android:title="@string/send_scan_active"
android:summary="@string/send_scan_active_summary"/>
<de.markusfisch.android.binaryeye.preference.UrlPreference
android:key="send_scan_url"
android:title="@string/send_scan_url"/>
<ListPreference
android:key="send_scan_type"
android:title="@string/send_scan_type"
android:entries="@array/send_type_names"
android:entryValues="@array/send_type_values"
android:defaultValue="0"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="send_scan_bluetooth"
android:title="@string/send_scan_bluetooth"
android:summary="@string/send_scan_bluetooth_summary"/>
<ListPreference
android:key="send_scan_bluetooth_host"
android:title="@string/send_scan_bluetooth_host"
android:entries="@array/bluetooth_host_names"
android:entryValues="@array/bluetooth_host_values"
android:defaultValue="@string/no_bluetooth_hosts_paired_value"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/content_category">
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="open_immediately"
android:title="@string/open_immediately"
android:summary="@string/open_immediately_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_meta_data"
android:title="@string/show_meta_data"
android:summary="@string/show_meta_data_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_hex_dump"
android:title="@string/show_hex_dump"
android:summary="@string/show_hex_dump_summary"/>
<ListPreference
android:key="show_checksum"
android:title="@string/show_checksum"
android:entries="@array/checksum_names"
android:entryValues="@array/checksum_values"
android:defaultValue="@string/no_checksum"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_recreation"
android:title="@string/show_recreation"
android:summary="@string/show_recreation_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="close_automatically"
android:title="@string/close_automatically"
android:summary="@string/close_automatically_summary"/>
<ListPreference
android:key="default_search_url"
android:title="@string/default_search_engine"
android:entries="@array/search_engines_names"
android:entryValues="@array/search_engines_values"
android:defaultValue=""/>
<de.markusfisch.android.binaryeye.preference.UrlPreference
android:key="open_with_url"
android:title="@string/open_with_url"/>
<Preference
android:key="automated_actions"
android:title="@string/automated_actions"
android:summary="@string/automated_actions_none"/>
<Preference
android:key="clear_network_suggestions"
android:title="@string/clear_network_suggestions"
android:summary="@string/clear_network_suggestions_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/generation_category">
<android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="brighten_screen"
android:title="@string/brighten_screen"
android:summary="@string/brighten_screen_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/locale_category">
<ListPreference
android:key="custom_locale"
android:title="@string/custom_locale"
android:entries="@array/locale_names"
android:entryValues="@array/locale_values"
android:defaultValue="0"/>
</PreferenceCategory>
</PreferenceScreen>
+18 -18
View File
@@ -14,27 +14,27 @@
android:title="@string/barcode_formats"
android:entries="@array/barcode_formats_names"
android:entryValues="@array/barcode_formats_values"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_crop_handle"
android:title="@string/show_crop_handle"
android:summary="@string/show_crop_handle_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="zoom_by_swiping"
android:title="@string/zoom_by_swiping"
android:summary="@string/zoom_by_swiping_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="auto_rotate"
android:title="@string/auto_rotate"
android:summary="@string/auto_rotate_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="try_harder"
android:title="@string/try_harder"
android:summary="@string/try_harder_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="bulk_mode"
android:title="@string/bulk_mode"
@@ -45,17 +45,17 @@
android:entries="@array/bulk_mode_delays_names"
android:entryValues="@array/bulk_mode_delays_values"
android:defaultValue="500"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_toast_in_bulk_mode"
android:title="@string/show_toast_in_bulk_mode"
android:summary="@string/show_toast_in_bulk_mode_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="vibrate"
android:title="@string/vibrate"
android:summary="@string/vibrate_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="beep"
android:title="@string/beep"
@@ -66,7 +66,7 @@
android:entries="@array/beep_tones_names"
android:entryValues="@array/beep_tones_values"
android:defaultValue="tone_prop_beep"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="use_history"
android:title="@string/use_history"
@@ -77,14 +77,14 @@
android:entries="@array/ignore_duplicates_names"
android:entryValues="@array/ignore_duplicates_values"
android:defaultValue="ignore_consecutive_duplicates"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="copy_immediately"
android:title="@string/copy_immediately"
android:summary="@string/copy_immediately_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/send_category">
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="send_scan_active"
android:title="@string/send_scan_active"
@@ -98,7 +98,7 @@
android:entries="@array/send_type_names"
android:entryValues="@array/send_type_values"
android:defaultValue="0"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="send_scan_bluetooth"
android:title="@string/send_scan_bluetooth"
@@ -111,17 +111,17 @@
android:defaultValue="@string/no_bluetooth_hosts_paired_value"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/content_category">
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="open_immediately"
android:title="@string/open_immediately"
android:summary="@string/open_immediately_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_meta_data"
android:title="@string/show_meta_data"
android:summary="@string/show_meta_data_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_hex_dump"
android:title="@string/show_hex_dump"
@@ -132,12 +132,12 @@
android:entries="@array/checksum_names"
android:entryValues="@array/checksum_values"
android:defaultValue="@string/no_checksum"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_recreation"
android:title="@string/show_recreation"
android:summary="@string/show_recreation_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="close_automatically"
android:title="@string/close_automatically"
@@ -161,7 +161,7 @@
android:summary="@string/clear_network_suggestions_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/generation_category">
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="brighten_screen"
android:title="@string/brighten_screen"
+23 -18
View File
@@ -9,27 +9,32 @@
android:summary="@string/profile_default"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/scan_category">
<android.support.v7.preference.SwitchPreferenceCompat
<MultiSelectListPreference
android:key="formats"
android:title="@string/barcode_formats"
android:entries="@array/barcode_formats_names"
android:entryValues="@array/barcode_formats_values"/>
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_crop_handle"
android:title="@string/show_crop_handle"
android:summary="@string/show_crop_handle_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="zoom_by_swiping"
android:title="@string/zoom_by_swiping"
android:summary="@string/zoom_by_swiping_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="auto_rotate"
android:title="@string/auto_rotate"
android:summary="@string/auto_rotate_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="try_harder"
android:title="@string/try_harder"
android:summary="@string/try_harder_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="bulk_mode"
android:title="@string/bulk_mode"
@@ -40,17 +45,17 @@
android:entries="@array/bulk_mode_delays_names"
android:entryValues="@array/bulk_mode_delays_values"
android:defaultValue="500"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_toast_in_bulk_mode"
android:title="@string/show_toast_in_bulk_mode"
android:summary="@string/show_toast_in_bulk_mode_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="vibrate"
android:title="@string/vibrate"
android:summary="@string/vibrate_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="beep"
android:title="@string/beep"
@@ -61,7 +66,7 @@
android:entries="@array/beep_tones_names"
android:entryValues="@array/beep_tones_values"
android:defaultValue="tone_prop_beep"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="use_history"
android:title="@string/use_history"
@@ -72,14 +77,14 @@
android:entries="@array/ignore_duplicates_names"
android:entryValues="@array/ignore_duplicates_values"
android:defaultValue="ignore_consecutive_duplicates"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="copy_immediately"
android:title="@string/copy_immediately"
android:summary="@string/copy_immediately_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/send_category">
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="send_scan_active"
android:title="@string/send_scan_active"
@@ -93,7 +98,7 @@
android:entries="@array/send_type_names"
android:entryValues="@array/send_type_values"
android:defaultValue="0"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="send_scan_bluetooth"
android:title="@string/send_scan_bluetooth"
@@ -106,17 +111,17 @@
android:defaultValue="@string/no_bluetooth_hosts_paired_value"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/content_category">
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="open_immediately"
android:title="@string/open_immediately"
android:summary="@string/open_immediately_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_meta_data"
android:title="@string/show_meta_data"
android:summary="@string/show_meta_data_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_hex_dump"
android:title="@string/show_hex_dump"
@@ -127,12 +132,12 @@
android:entries="@array/checksum_names"
android:entryValues="@array/checksum_values"
android:defaultValue="@string/no_checksum"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="show_recreation"
android:title="@string/show_recreation"
android:summary="@string/show_recreation_summary"/>
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="close_automatically"
android:title="@string/close_automatically"
@@ -156,7 +161,7 @@
android:summary="@string/clear_network_suggestions_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/generation_category">
<android.support.v7.preference.SwitchPreferenceCompat
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="brighten_screen"
android:title="@string/brighten_screen"
+3 -9
View File
@@ -22,9 +22,7 @@
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="de.markusfisch.android.binaryeye"
android:targetClass="de.markusfisch.android.binaryeye.activity.MainActivity">
<extra android:name="encode" android:value="encode"/>
</intent>
android:targetClass="de.markusfisch.android.binaryeye.activity.EncodeActivity"/>
</shortcut>
<shortcut
android:shortcutId="history"
@@ -35,9 +33,7 @@
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="de.markusfisch.android.binaryeye"
android:targetClass="de.markusfisch.android.binaryeye.activity.MainActivity">
<extra android:name="history" android:value="history"/>
</intent>
android:targetClass="de.markusfisch.android.binaryeye.activity.HistoryActivity"/>
</shortcut>
<shortcut
android:shortcutId="preferences"
@@ -48,8 +44,6 @@
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="de.markusfisch.android.binaryeye"
android:targetClass="de.markusfisch.android.binaryeye.activity.MainActivity">
<extra android:name="preferences" android:value="preferences"/>
</intent>
android:targetClass="de.markusfisch.android.binaryeye.activity.PreferencesActivity"/>
</shortcut>
</shortcuts>
@@ -56,20 +56,20 @@ class BinaryEyeDeeplinkTest {
fun createEncodeDeeplinkTests() {
// Basic test
assertEquals(
"https://markusfisch.de/encode?format=QR_CODE&content=test",
createEncodeDeeplink("QR_CODE", "test")
"https://markusfisch.de/encode?format=QRCode&content=test",
createEncodeDeeplink("QRCode", "test")
)
// Test with special characters in content
assertEquals(
"https://markusfisch.de/encode?format=QR_CODE&content=test%20with%20spaces",
createEncodeDeeplink("QR_CODE", "test with spaces")
"https://markusfisch.de/encode?format=QRCode&content=test%20with%20spaces",
createEncodeDeeplink("QRCode", "test with spaces")
)
// Test with different barcode format
assertEquals(
"https://markusfisch.de/encode?format=CODE_128&content=123456",
createEncodeDeeplink("CODE_128", "123456")
"https://markusfisch.de/encode?format=Code128&content=123456",
createEncodeDeeplink("Code128", "123456")
)
}
}
+2 -3
View File
@@ -1,9 +1,8 @@
buildscript {
ext {
kotlin_version = '2.2.21'
tools_version = '9.0.0'
kotlin_version = '2.3.20'
tools_version = '9.1.0'
sdk_version = 36
support_version = '25.3.1'
}
repositories {
@@ -0,0 +1,5 @@
* Improve UI of search barcode dialog
* Stability improvements
* Update ZXingCpp
* Update Russian translation
@@ -0,0 +1,2 @@
* Fix barcode regeneration
@@ -0,0 +1,2 @@
* Stability improvement
@@ -0,0 +1,2 @@
* Fix generating 1D barcodes
@@ -0,0 +1,2 @@
* Downgrade ZXingCpp for compatibilty
@@ -0,0 +1,3 @@
* Update ZXingCpp
* Ask for confirmation before deleting a scan from the result screen
@@ -0,0 +1,6 @@
* Add icons to preferences screen
* Update history listing on resume
* Fix quiet zone for recreated barcodes
* Fix automatic screen brightening
* Migrate to CameraX
+1 -1
View File
@@ -1,6 +1,6 @@
#Wed Jan 26 11:58:22 CET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
distributionPath=wrapper/dists
networkTimeout=10000
zipStorePath=wrapper/dists