From 0c1c99d659f26cf2762a82b20abec7fd6b859bed Mon Sep 17 00:00:00 2001 From: Martin Gross Date: Tue, 17 Mar 2026 14:19:51 +0100 Subject: [PATCH 01/15] Add NFC ticket scanning --- .gitmodules | 3 + pretixscan/app/build.gradle | 11 ++ pretixscan/app/src/main/AndroidManifest.xml | 2 + .../eu/pretix/pretixscan/droid/AppConfig.kt | 11 ++ .../pretixscan/droid/ui/MainActivity.kt | 105 +++++++++++++++++- .../eu/pretix/pretixscan/utils/Settings.kt | 13 +++ pretixscan/libpretixsync-repo | 2 +- pretixscan/libpretixui-repo | 2 +- pretixscan/settings.gradle | 4 +- 9 files changed, 146 insertions(+), 7 deletions(-) create mode 100644 pretixscan/app/src/main/java/eu/pretix/pretixscan/utils/Settings.kt diff --git a/.gitmodules b/.gitmodules index c6c8d4eb..825981e0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,6 @@ [submodule "pretixscan/libpretixui-repo"] path = pretixscan/libpretixui-repo url = https://github.com/pretix/libpretixui-android.git +[submodule "pretixscan/libpretixnfc-repo"] + path = pretixscan/libpretixnfc-repo + url = https://github.com/pretix/libpretixnfc.git \ No newline at end of file diff --git a/pretixscan/app/build.gradle b/pretixscan/app/build.gradle index baaeda3f..c2858241 100644 --- a/pretixscan/app/build.gradle +++ b/pretixscan/app/build.gradle @@ -172,6 +172,17 @@ dependencies { implementation(project(':libpretixsync')) { transitive = false } + + // libpretixnfc + implementation(project(':libpretixnfc')) { + transitive = true + } + + // libpretixnfc-android + implementation(project(':libpretixnfc-android')) { + transitive = true + } + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/pretixscan/app/src/main/AndroidManifest.xml b/pretixscan/app/src/main/AndroidManifest.xml index 44c0140a..30e95ffb 100644 --- a/pretixscan/app/src/main/AndroidManifest.xml +++ b/pretixscan/app/src/main/AndroidManifest.xml @@ -102,6 +102,8 @@ android:exported="false" android:theme="@style/AppTheme.NoActionBar" /> + + Unit)? = null + private var nfcHandler: NfcHandler? = null + companion object { const val PERMISSIONS_REQUEST_CAMERA = 1337 } @@ -565,7 +576,9 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result Build.VERSION.RELEASE, "pretixSCAN Android", BuildConfig.VERSION_NAME, - null, + try { + conf.keyStore.getOrCreateRsaPubKey("device")?.toString(Charset.defaultCharset()) + } catch (e: NotImplementedError) { null }, null, (application as PretixScan).connectivityHelper ) @@ -839,6 +852,7 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result } check() connectivityManager.registerNetworkCallback(networkRequest, networkCallback!!) + reloadNfcHandler() } private fun setKioskAnimation() { @@ -966,6 +980,7 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result override fun onPause() { handler.removeCallbacks(syncRunnable) (application as PretixScan).connectivityHelper.removeListener(this) + nfcHandler?.stop() super.onPause() if (conf.useCamera) { binding.scannerView.stopCamera() @@ -984,7 +999,7 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result super.onStop() } - fun handleScan(raw_result: String, answers: MutableList?, ignore_unpaid: Boolean = false) { + fun handleScan(raw_result: String, answers: MutableList?, ignore_unpaid: Boolean = false, source_type: String = "barcode") { if (conf.kioskMode && conf.requiresPin("settings") && conf.verifyPin(raw_result)) { supportActionBar?.show() return @@ -1032,7 +1047,7 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result val provider = (application as PretixScan).getCheckProvider(conf) val startedAt = System.currentTimeMillis() try { - checkResult = provider.check(conf.eventSelectionToMap(), result, "barcode", answers, ignore_unpaid, conf.printBadges, when (conf.scanType) { + checkResult = provider.check(conf.eventSelectionToMap(), result, source_type, answers, ignore_unpaid, conf.printBadges, when (conf.scanType) { "exit" -> TicketCheckProvider.CheckInType.EXIT else -> TicketCheckProvider.CheckInType.ENTRY }, allowQuestions = !conf.ignoreQuestions) @@ -1606,4 +1621,86 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result outState.putString("result", om.writeValueAsString(lastScanResult)) } } + + override fun chipReadSuccessfully(identifier: String, mediaType: ReusableMediaType) { + if (identifier.startsWith("08")) { + runOnUiThread { + displayScanResult( + TicketCheckProvider.CheckResult(TicketCheckProvider.CheckResult.Type.ERROR, getString(R.string.nfc_random_uid), false), + null + ) + } + } + + handleScan(identifier, null, !conf.unpaidAsk, source_type = mediaType.serverName) + } + + override fun chipReadError(error: ChipReadError, identifier: String?) { + val error = when (error) { + ChipReadError.IO_ERROR -> getString(R.string.nfc_read_error) + ChipReadError.UNKNOWN_CHIP_TYPE -> getString(R.string.nfc_unknown_chip_type) + ChipReadError.FOREIGN_CHIP -> getString(R.string.nfc_foreign_chip) + ChipReadError.EMPTY_CHIP -> getString(R.string.nfc_empty_chip) + else -> error.toString() + } + + runOnUiThread { + displayScanResult( + TicketCheckProvider.CheckResult(TicketCheckProvider.CheckResult.Type.ERROR, error, false), + null + ) + } + } + + private fun reloadNfcHandler() { + if (conf.synchronizedEvents.isNotEmpty()) { + val eventSlug = conf.synchronizedEvents.first() + val settingsManager = SettingsManager(application) + val useRandomIdForNewTags = settingsManager.getBySlug(eventSlug)?.json?.optBoolean("reusable_media_type_nfc_mf0aes_random_uid", false) ?: false + val activeMediaTypes = getActiveMediaTypes( + settingsManager, + eventSlug + ) + if (nfcHandler?.isRunning() != true || activeMediaTypes.toSet() != nfcHandler?.getMediaTypes()?.toSet()) { + nfcHandler?.stop() + val keySets = (this.applicationContext as PretixScan).db.mediumKeySetQueries.selectAll() + .executeAsList() + .map { it.toModel() } + .map { + Mf0aesKeySet( + it.publicId, + it.organizer == conf.organizerSlug && it.active, + conf.keyStore.decryptRsa( + "device", + eu.pretix.libpretixsync.utils.codec.binary.Base64.decodeBase64(it.uidKey.toByteArray(Charset.defaultCharset())) + ), + conf.keyStore.decryptRsa( + "device", + eu.pretix.libpretixsync.utils.codec.binary.Base64.decodeBase64(it.diversificationKey.toByteArray(Charset.defaultCharset())) + ), + ) + } + + nfcHandler = getNfcHandler(this, keySets, useRandomIdForNewTags, nfcReaderType = conf.nfcReaderType) + nfcHandler!!.setOnChipReadListener(this) + try { + nfcHandler!!.start(activeMediaTypes) + } catch (e: NfcUnsupported) { + runOnUiThread { + displayScanResult( + TicketCheckProvider.CheckResult(TicketCheckProvider.CheckResult.Type.ERROR, getString(R.string.nfc_not_supported), false), + null + ) + } + } catch (e: NfcDisabled) { + runOnUiThread { + displayScanResult( + TicketCheckProvider.CheckResult(TicketCheckProvider.CheckResult.Type.ERROR, getString(R.string.nfc_disabled), false), + null + ) + } + } + } + } + } } diff --git a/pretixscan/app/src/main/java/eu/pretix/pretixscan/utils/Settings.kt b/pretixscan/app/src/main/java/eu/pretix/pretixscan/utils/Settings.kt new file mode 100644 index 00000000..a40c65e9 --- /dev/null +++ b/pretixscan/app/src/main/java/eu/pretix/pretixscan/utils/Settings.kt @@ -0,0 +1,13 @@ +package eu.pretix.pretixscan.utils + +import android.app.Application +import eu.pretix.libpretixsync.models.Settings +import eu.pretix.libpretixsync.models.db.toModel +import eu.pretix.libpretixsync.utils.SettingsManager +import eu.pretix.pretixscan.droid.PretixScan + +class SettingsManager(private val application: Application): SettingsManager { + override fun getBySlug(eventSlug: String): Settings? { + return (application as PretixScan).db.settingsQueries.selectBySlug(eventSlug).executeAsOneOrNull()?.toModel() + } +} \ No newline at end of file diff --git a/pretixscan/libpretixsync-repo b/pretixscan/libpretixsync-repo index c4011dc8..f10dcf3c 160000 --- a/pretixscan/libpretixsync-repo +++ b/pretixscan/libpretixsync-repo @@ -1 +1 @@ -Subproject commit c4011dc8c6604a50664bd64ddeec395d745cb589 +Subproject commit f10dcf3ce5e9991fa4a56624b8ac240f44904464 diff --git a/pretixscan/libpretixui-repo b/pretixscan/libpretixui-repo index 4136af04..5bbc8bf9 160000 --- a/pretixscan/libpretixui-repo +++ b/pretixscan/libpretixui-repo @@ -1 +1 @@ -Subproject commit 4136af0492dff9610b724d3b718ed3189132aa39 +Subproject commit 5bbc8bf9c8441b454315cfa9f28c28be175a27dc diff --git a/pretixscan/settings.gradle b/pretixscan/settings.gradle index ffa3386a..b9a52074 100644 --- a/pretixscan/settings.gradle +++ b/pretixscan/settings.gradle @@ -12,9 +12,11 @@ plugins { id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' } -include ':app', ':libpretixsync', ':libpretixui-android', ':android-libserenegiantcommon', ':android-libusbcameracommon', ':android-libuvccamera' +include ':app', ':libpretixsync', ':libpretixui-android', ':libpretixnfc', ':libpretixnfc-android', ':android-libserenegiantcommon', ':android-libusbcameracommon', ':android-libuvccamera' project(':libpretixsync').projectDir = new File('libpretixsync-repo/libpretixsync') project(':libpretixui-android').projectDir = new File('libpretixui-repo/libpretixui-android') +project(':libpretixnfc').projectDir = new File('libpretixnfc-repo/libpretixnfc') +project(':libpretixnfc-android').projectDir = new File('libpretixnfc-repo/libpretixnfc-android') project(':android-libserenegiantcommon').projectDir = new File('libpretixui-repo/android-libserenegiantcommon') project(':android-libusbcameracommon').projectDir = new File('libpretixui-repo/android-libusbcameracommon') project(':android-libuvccamera').projectDir = new File('libpretixui-repo/android-libuvccamera') From a11172edeb8b75cf2d28639b787433f80c4adf23 Mon Sep 17 00:00:00 2001 From: Martin Gross Date: Tue, 17 Mar 2026 14:26:50 +0100 Subject: [PATCH 02/15] Properly add/init libpretixnfc-repo --- .gitmodules | 2 +- pretixscan/libpretixnfc-repo | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 pretixscan/libpretixnfc-repo diff --git a/.gitmodules b/.gitmodules index 825981e0..4efe2e93 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,4 +7,4 @@ url = https://github.com/pretix/libpretixui-android.git [submodule "pretixscan/libpretixnfc-repo"] path = pretixscan/libpretixnfc-repo - url = https://github.com/pretix/libpretixnfc.git \ No newline at end of file + url = https://github.com/pretix/libpretixnfc.git diff --git a/pretixscan/libpretixnfc-repo b/pretixscan/libpretixnfc-repo new file mode 160000 index 00000000..58abfe32 --- /dev/null +++ b/pretixscan/libpretixnfc-repo @@ -0,0 +1 @@ +Subproject commit 58abfe32bad4c2a3bf4efb71c8d238c640c8a3e2 From 05f7e87877b54219874657d84b670233140e0759 Mon Sep 17 00:00:00 2001 From: Martin Gross Date: Tue, 17 Mar 2026 17:04:32 +0100 Subject: [PATCH 03/15] Bump libpretixnfc --- pretixscan/libpretixnfc-repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pretixscan/libpretixnfc-repo b/pretixscan/libpretixnfc-repo index 58abfe32..c4badfb5 160000 --- a/pretixscan/libpretixnfc-repo +++ b/pretixscan/libpretixnfc-repo @@ -1 +1 @@ -Subproject commit 58abfe32bad4c2a3bf4efb71c8d238c640c8a3e2 +Subproject commit c4badfb55f27c9cd954df205fd04cf67e255500b From cdbedde0adf86494950a1b41b28768de04d422e0 Mon Sep 17 00:00:00 2001 From: Martin Gross Date: Mon, 30 Mar 2026 15:47:08 +0200 Subject: [PATCH 04/15] Bump libpretixnfc --- pretixscan/libpretixnfc-repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pretixscan/libpretixnfc-repo b/pretixscan/libpretixnfc-repo index c4badfb5..2d710b84 160000 --- a/pretixscan/libpretixnfc-repo +++ b/pretixscan/libpretixnfc-repo @@ -1 +1 @@ -Subproject commit c4badfb55f27c9cd954df205fd04cf67e255500b +Subproject commit 2d710b84875c42aa44516ce2a29c6d6eb6c692c8 From 6eb6cf8633ace558a5bd6c562049cb212bead008 Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Wed, 1 Apr 2026 15:07:42 +0200 Subject: [PATCH 05/15] Revert libpretixui update --- pretixscan/libpretixui-repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pretixscan/libpretixui-repo b/pretixscan/libpretixui-repo index 5bbc8bf9..4136af04 160000 --- a/pretixscan/libpretixui-repo +++ b/pretixscan/libpretixui-repo @@ -1 +1 @@ -Subproject commit 5bbc8bf9c8441b454315cfa9f28c28be175a27dc +Subproject commit 4136af0492dff9610b724d3b718ed3189132aa39 From 78071490d5f4cc02462043d711bb38c066de5596 Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Wed, 1 Apr 2026 16:39:01 +0200 Subject: [PATCH 06/15] Bump libpretixnfc --- pretixscan/libpretixnfc-repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pretixscan/libpretixnfc-repo b/pretixscan/libpretixnfc-repo index 2d710b84..fd5cd82f 160000 --- a/pretixscan/libpretixnfc-repo +++ b/pretixscan/libpretixnfc-repo @@ -1 +1 @@ -Subproject commit 2d710b84875c42aa44516ce2a29c6d6eb6c692c8 +Subproject commit fd5cd82f13005c9099dd03e5157fc56d9387e018 From 7b5ea43e5bd426ec8af170c0b072144aa504eb39 Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Wed, 1 Apr 2026 19:12:05 +0200 Subject: [PATCH 07/15] Fix display of nfc error messages --- .../main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt index 25d4b97f..e87c3fa8 100644 --- a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt +++ b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt @@ -1625,11 +1625,13 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result override fun chipReadSuccessfully(identifier: String, mediaType: ReusableMediaType) { if (identifier.startsWith("08")) { runOnUiThread { + showLoadingCard() displayScanResult( TicketCheckProvider.CheckResult(TicketCheckProvider.CheckResult.Type.ERROR, getString(R.string.nfc_random_uid), false), null ) } + return } handleScan(identifier, null, !conf.unpaidAsk, source_type = mediaType.serverName) @@ -1645,6 +1647,7 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result } runOnUiThread { + showLoadingCard() displayScanResult( TicketCheckProvider.CheckResult(TicketCheckProvider.CheckResult.Type.ERROR, error, false), null @@ -1687,6 +1690,7 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result nfcHandler!!.start(activeMediaTypes) } catch (e: NfcUnsupported) { runOnUiThread { + showLoadingCard() displayScanResult( TicketCheckProvider.CheckResult(TicketCheckProvider.CheckResult.Type.ERROR, getString(R.string.nfc_not_supported), false), null @@ -1694,6 +1698,7 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result } } catch (e: NfcDisabled) { runOnUiThread { + showLoadingCard() displayScanResult( TicketCheckProvider.CheckResult(TicketCheckProvider.CheckResult.Type.ERROR, getString(R.string.nfc_disabled), false), null From 1372ecdc804d9f8fb57d858790eff8c6e8707fad Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Wed, 1 Apr 2026 19:48:46 +0200 Subject: [PATCH 08/15] Reload NFC handler on end of sync, else keyset is still missing after first sync with new app version --- .../main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt index e87c3fa8..84f33af4 100644 --- a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt +++ b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt @@ -282,6 +282,8 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result } view_data.configDetails.set(confdetails.trim()) view_data.isOffline.set(conf.offlineMode) + + reloadNfcHandler() } private fun setSearchFilter(f: String) { @@ -852,7 +854,6 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result } check() connectivityManager.registerNetworkCallback(networkRequest, networkCallback!!) - reloadNfcHandler() } private fun setKioskAnimation() { From 8fd342908288fd2e9e8eb80aa6b3be085a0a8fae Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Tue, 7 Apr 2026 16:18:06 +0200 Subject: [PATCH 09/15] Bump libpretixsync --- pretixscan/libpretixsync-repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pretixscan/libpretixsync-repo b/pretixscan/libpretixsync-repo index f10dcf3c..8b1df57d 160000 --- a/pretixscan/libpretixsync-repo +++ b/pretixscan/libpretixsync-repo @@ -1 +1 @@ -Subproject commit f10dcf3ce5e9991fa4a56624b8ac240f44904464 +Subproject commit 8b1df57d2fecf937deaf25e461dbb8ae362fbf2b From 1965738334f53a976ded088825b4d6080e720f95 Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Tue, 14 Apr 2026 14:27:15 +0200 Subject: [PATCH 10/15] Silently ignore NfcUnsupported and NfcDisabled, they just throw errors in the face of non-nfc-device users that cannot change anything about it --- .../pretixscan/droid/ui/MainActivity.kt | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt index 84f33af4..35a73b7a 100644 --- a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt +++ b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt @@ -1689,22 +1689,10 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result nfcHandler!!.setOnChipReadListener(this) try { nfcHandler!!.start(activeMediaTypes) - } catch (e: NfcUnsupported) { - runOnUiThread { - showLoadingCard() - displayScanResult( - TicketCheckProvider.CheckResult(TicketCheckProvider.CheckResult.Type.ERROR, getString(R.string.nfc_not_supported), false), - null - ) - } - } catch (e: NfcDisabled) { - runOnUiThread { - showLoadingCard() - displayScanResult( - TicketCheckProvider.CheckResult(TicketCheckProvider.CheckResult.Type.ERROR, getString(R.string.nfc_disabled), false), - null - ) - } + } catch (_: NfcUnsupported) { + // silently ignore, else users on non-nfc-devices are warned all the time + } catch (_: NfcDisabled) { + // silently ignore, we may want to show an unobtrusive warning in the future } } } From 9e12002ee5670bc4bc89ae5a100db087329278e7 Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Tue, 14 Apr 2026 14:28:16 +0200 Subject: [PATCH 11/15] Bump libpretixui, libpretixsync & libpretixnfc --- pretixscan/libpretixnfc-repo | 2 +- pretixscan/libpretixsync-repo | 2 +- pretixscan/libpretixui-repo | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pretixscan/libpretixnfc-repo b/pretixscan/libpretixnfc-repo index fd5cd82f..c1c1c661 160000 --- a/pretixscan/libpretixnfc-repo +++ b/pretixscan/libpretixnfc-repo @@ -1 +1 @@ -Subproject commit fd5cd82f13005c9099dd03e5157fc56d9387e018 +Subproject commit c1c1c661d505ca919e8dba414c94b1511fe64f2a diff --git a/pretixscan/libpretixsync-repo b/pretixscan/libpretixsync-repo index 8b1df57d..1edc026e 160000 --- a/pretixscan/libpretixsync-repo +++ b/pretixscan/libpretixsync-repo @@ -1 +1 @@ -Subproject commit 8b1df57d2fecf937deaf25e461dbb8ae362fbf2b +Subproject commit 1edc026ebe40b4c68b7cc5f5f5383883b933f231 diff --git a/pretixscan/libpretixui-repo b/pretixscan/libpretixui-repo index 4136af04..8bb21189 160000 --- a/pretixscan/libpretixui-repo +++ b/pretixscan/libpretixui-repo @@ -1 +1 @@ -Subproject commit 4136af0492dff9610b724d3b718ed3189132aa39 +Subproject commit 8bb211896fd68e3888d187ce6c60f0b982a9317e From d1bc351ae9a314600b7563ccb2a05a2fff68d5b8 Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Tue, 14 Apr 2026 14:28:42 +0200 Subject: [PATCH 12/15] Early exit reloadNfcHandler if no reusable media are nfc based --- .../main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt index 35a73b7a..f1647df3 100644 --- a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt +++ b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt @@ -1665,6 +1665,12 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result settingsManager, eventSlug ) + if (!activeMediaTypes.any { it.isNfcBased }) { + if (nfcHandler?.isRunning() == true) { + nfcHandler?.stop() + } + return + } if (nfcHandler?.isRunning() != true || activeMediaTypes.toSet() != nfcHandler?.getMediaTypes()?.toSet()) { nfcHandler?.stop() val keySets = (this.applicationContext as PretixScan).db.mediumKeySetQueries.selectAll() From b6775399f9cd6507ae8794ce86fbfffb8cf2c0b1 Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Tue, 14 Apr 2026 15:34:11 +0200 Subject: [PATCH 13/15] Remember scan source type if a dialog has to appear --- .../pretixscan/droid/ui/MainActivity.kt | 45 +++++++++++++------ .../droid/ui/UnpaidOrderDialogHelper.kt | 19 ++++---- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt index f1647df3..a06963de 100644 --- a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt +++ b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt @@ -189,6 +189,7 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result private var lastScanTime: Long = 0 private var lastScanCode: String = "" private var lastIgnoreUnpaid: Boolean = false + private var lastScanSourceType: ReusableMediaType = ReusableMediaType.BARCODE private var lastScanResult: TicketCheckProvider.CheckResult? = null private var keyboardBuffer: String = "" private var dialog: QuestionsDialogInterface? = null @@ -217,9 +218,10 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result } lastScanTime = System.currentTimeMillis() lastScanCode = result + lastScanSourceType = ReusableMediaType.BARCODE lastIgnoreUnpaid = false lastScanResult = null - handleScan(result, null, !conf.unpaidAsk) + handleScan(result, null, !conf.unpaidAsk, source_type = lastScanSourceType.serverName) } }) @@ -303,10 +305,11 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result override fun onSearchResultClicked(res: TicketCheckProvider.SearchResult) { lastScanTime = System.currentTimeMillis() lastScanCode = res.secret!! + lastScanSourceType = ReusableMediaType.BARCODE lastScanResult = null lastIgnoreUnpaid = false hideSearchCard() - handleScan(res.secret!!, null, !conf.unpaidAsk) + handleScan(res.secret!!, null, !conf.unpaidAsk, source_type = lastScanSourceType.serverName) } }) runOnUiThread { @@ -1077,9 +1080,13 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result } } - fun showQuestionsDialog(res: TicketCheckProvider.CheckResult, secret: String, ignore_unpaid: Boolean, - values: Map?, isResumed: Boolean, - retryHandler: ((String, MutableList, Boolean) -> Unit)): QuestionsDialogInterface { + fun showQuestionsDialog(res: TicketCheckProvider.CheckResult, + secret: String, + sourceType: ReusableMediaType, + ignore_unpaid: Boolean, + values: Map?, + isResumed: Boolean, + retryHandler: ((String, ReusableMediaType, MutableList, Boolean) -> Unit)): QuestionsDialogInterface { val questions = res.requiredAnswers!!.map { it.question.toModel() } for (q in questions) { @@ -1121,7 +1128,7 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result values_, null, null, - { answers -> retryHandler(secret, answers, ignore_unpaid) }, + { answers -> retryHandler(secret, sourceType, answers, ignore_unpaid) }, null, attendeeName, attendeeDOB, @@ -1176,9 +1183,9 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result startHidingTimer() if (result.type == TicketCheckProvider.CheckResult.Type.ANSWERS_REQUIRED) { view_data.resultState.set(DIALOG) - dialog = showQuestionsDialog(result, lastScanCode, ignore_unpaid, null, false) { secret, answers, ignore_unpaid -> + dialog = showQuestionsDialog(result, lastScanCode, lastScanSourceType, ignore_unpaid, null, false) { secret, sourceType, answers, ignore_unpaid -> stopHidingTimer() - handleScan(secret, answers, ignore_unpaid) + handleScan(secret, answers, ignore_unpaid, source_type = sourceType.serverName) } dialog!!.setOnCancelListener(DialogInterface.OnCancelListener { hideCard() }) view_data.setLed(this, view_data.resultState.get()!!, true) @@ -1186,9 +1193,9 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result } if (result.type == TicketCheckProvider.CheckResult.Type.UNPAID && result.isCheckinAllowed) { view_data.resultState.set(DIALOG) - dialog = showUnpaidDialog(this, result, lastScanCode, answers) { secret, answers, ignore_unpaid -> + dialog = showUnpaidDialog(this, result, lastScanCode, lastScanSourceType, answers) { secret, sourceType, answers, ignore_unpaid -> stopHidingTimer() - handleScan(secret, answers, ignore_unpaid) + handleScan(secret, answers, ignore_unpaid, source_type = sourceType.serverName) } dialog!!.setOnCancelListener(DialogInterface.OnCancelListener { hideCard() }) view_data.setLed(this, view_data.resultState.get()!!, true) @@ -1391,9 +1398,10 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result } lastScanTime = System.currentTimeMillis() lastScanCode = s + lastScanSourceType = ReusableMediaType.BARCODE lastScanResult = null lastIgnoreUnpaid = false - handleScan(s, null, !conf.unpaidAsk) + handleScan(s, null, !conf.unpaidAsk, source_type = lastScanSourceType.serverName) } override fun dispatchKeyEvent(event: KeyEvent): Boolean { @@ -1407,9 +1415,10 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result } lastScanTime = System.currentTimeMillis() lastScanCode = keyboardBuffer + lastScanSourceType = ReusableMediaType.BARCODE lastScanResult = null lastIgnoreUnpaid = false - handleScan(keyboardBuffer, null, !conf.unpaidAsk) + handleScan(keyboardBuffer, null, !conf.unpaidAsk, source_type = lastScanSourceType.serverName) keyboardBuffer = "" true } @@ -1573,6 +1582,7 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result view_data.resultState.set(DIALOG) lastScanCode = savedInstanceState.getString("lastScanCode", null) + lastScanSourceType = ReusableMediaType.entries.firstOrNull { it.serverName == savedInstanceState.getString("lastScanType", ReusableMediaType.BARCODE.serverName) } ?: ReusableMediaType.BARCODE lastIgnoreUnpaid = savedInstanceState.getBoolean("ignore_unpaid") lastScanResult = om.readValue(savedInstanceState.getString("result"), TicketCheckProvider.CheckResult::class.java) @@ -1590,9 +1600,9 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result } } - dialog = showQuestionsDialog(lastScanResult!!, lastScanCode, lastIgnoreUnpaid, values, true) { secret, answers, ignore_unpaid -> + dialog = showQuestionsDialog(lastScanResult!!, lastScanCode, lastScanSourceType, lastIgnoreUnpaid, values, true) { secret, sourceType, answers, ignore_unpaid -> stopHidingTimer() - handleScan(secret, answers, ignore_unpaid) + handleScan(secret, answers, ignore_unpaid, source_type = sourceType.serverName) } dialog!!.onRestoreInstanceState(answers) dialog!!.setOnCancelListener(DialogInterface.OnCancelListener { hideCard() }) @@ -1617,6 +1627,7 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result outState.putString("result_state", "DIALOG") outState.putString("lastScanCode", lastScanCode) + outState.putString("lastScanType", lastScanSourceType.serverName) outState.putBoolean("ignore_unpaid", lastIgnoreUnpaid) outState.putBundle("answers", dialog!!.onSaveInstanceState()) outState.putString("result", om.writeValueAsString(lastScanResult)) @@ -1635,6 +1646,12 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result return } + lastScanTime = System.currentTimeMillis() + lastScanCode = identifier + lastScanSourceType = mediaType + lastScanResult = null + lastIgnoreUnpaid = false + handleScan(identifier, null, !conf.unpaidAsk, source_type = mediaType.serverName) } diff --git a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/UnpaidOrderDialogHelper.kt b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/UnpaidOrderDialogHelper.kt index 183a9498..dd7ac07b 100644 --- a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/UnpaidOrderDialogHelper.kt +++ b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/UnpaidOrderDialogHelper.kt @@ -1,25 +1,25 @@ package eu.pretix.pretixscan.droid.ui import android.app.Activity -import android.app.Dialog import android.content.DialogInterface import android.content.Intent import android.view.WindowManager import androidx.appcompat.app.AlertDialog import eu.pretix.libpretixsync.check.TicketCheckProvider import eu.pretix.libpretixsync.db.Answer +import eu.pretix.libpretixsync.db.ReusableMediaType import eu.pretix.libpretixui.android.questions.QuestionsDialogInterface import eu.pretix.pretixscan.droid.R -class UnpaidDialog(ctx: Activity, val secret: String, val answers: MutableList?, - val retryHandler: ((String, MutableList?, Boolean) -> Unit)) : AlertDialog(ctx), QuestionsDialogInterface { +class UnpaidDialog(ctx: Activity, val secret: String, val sourceType: ReusableMediaType, val answers: MutableList?, + val retryHandler: ((String, ReusableMediaType, MutableList?, Boolean) -> Unit)) : AlertDialog(ctx), QuestionsDialogInterface { init { setTitle(R.string.dialog_unpaid_title) setMessage(ctx.getString(R.string.dialog_unpaid_text)) setButton(DialogInterface.BUTTON_POSITIVE, ctx.getString(R.string.dialog_unpaid_retry)) { p0, p1 -> dismiss() - retryHandler(secret, answers, true) + retryHandler(secret, sourceType, answers, true) } setButton(DialogInterface.BUTTON_NEGATIVE, ctx.getString(eu.pretix.libpretixui.android.R.string.cancel)) { p0, p1 -> cancel() @@ -31,10 +31,13 @@ class UnpaidDialog(ctx: Activity, val secret: String, val answers: MutableList?, - retryHandler: ((String, MutableList?, Boolean) -> Unit)): QuestionsDialogInterface { - val dialog = UnpaidDialog(ctx, secret, answers, retryHandler) +fun showUnpaidDialog(ctx: Activity, + res: TicketCheckProvider.CheckResult, + secret: String, + sourceType: ReusableMediaType, + answers: MutableList?, + retryHandler: ((String, ReusableMediaType, MutableList?, Boolean) -> Unit)): QuestionsDialogInterface { + val dialog = UnpaidDialog(ctx, secret, sourceType, answers, retryHandler) dialog.show() return dialog } From 4d6c4da4a0e365dd01d1f100b71fb841f8a06d09 Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Tue, 14 Apr 2026 15:37:45 +0200 Subject: [PATCH 14/15] Refactor source_type to a required field of handleScan --- .../pretixscan/droid/ui/MainActivity.kt | 63 ++++++++++++++++--- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt index a06963de..cfe9d771 100644 --- a/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt +++ b/pretixscan/app/src/main/java/eu/pretix/pretixscan/droid/ui/MainActivity.kt @@ -221,7 +221,12 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result lastScanSourceType = ReusableMediaType.BARCODE lastIgnoreUnpaid = false lastScanResult = null - handleScan(result, null, !conf.unpaidAsk, source_type = lastScanSourceType.serverName) + handleScan( + result, + lastScanSourceType.serverName, + null, + !conf.unpaidAsk + ) } }) @@ -309,7 +314,12 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result lastScanResult = null lastIgnoreUnpaid = false hideSearchCard() - handleScan(res.secret!!, null, !conf.unpaidAsk, source_type = lastScanSourceType.serverName) + handleScan( + res.secret!!, + lastScanSourceType.serverName, + null, + !conf.unpaidAsk + ) } }) runOnUiThread { @@ -1003,7 +1013,12 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result super.onStop() } - fun handleScan(raw_result: String, answers: MutableList?, ignore_unpaid: Boolean = false, source_type: String = "barcode") { + fun handleScan( + raw_result: String, + source_type: String, + answers: MutableList?, + ignore_unpaid: Boolean = false + ) { if (conf.kioskMode && conf.requiresPin("settings") && conf.verifyPin(raw_result)) { supportActionBar?.show() return @@ -1185,7 +1200,12 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result view_data.resultState.set(DIALOG) dialog = showQuestionsDialog(result, lastScanCode, lastScanSourceType, ignore_unpaid, null, false) { secret, sourceType, answers, ignore_unpaid -> stopHidingTimer() - handleScan(secret, answers, ignore_unpaid, source_type = sourceType.serverName) + handleScan( + secret, + sourceType.serverName, + answers, + ignore_unpaid + ) } dialog!!.setOnCancelListener(DialogInterface.OnCancelListener { hideCard() }) view_data.setLed(this, view_data.resultState.get()!!, true) @@ -1195,7 +1215,12 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result view_data.resultState.set(DIALOG) dialog = showUnpaidDialog(this, result, lastScanCode, lastScanSourceType, answers) { secret, sourceType, answers, ignore_unpaid -> stopHidingTimer() - handleScan(secret, answers, ignore_unpaid, source_type = sourceType.serverName) + handleScan( + secret, + sourceType.serverName, + answers, + ignore_unpaid + ) } dialog!!.setOnCancelListener(DialogInterface.OnCancelListener { hideCard() }) view_data.setLed(this, view_data.resultState.get()!!, true) @@ -1401,7 +1426,12 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result lastScanSourceType = ReusableMediaType.BARCODE lastScanResult = null lastIgnoreUnpaid = false - handleScan(s, null, !conf.unpaidAsk, source_type = lastScanSourceType.serverName) + handleScan( + s, + lastScanSourceType.serverName, + null, + !conf.unpaidAsk + ) } override fun dispatchKeyEvent(event: KeyEvent): Boolean { @@ -1418,7 +1448,12 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result lastScanSourceType = ReusableMediaType.BARCODE lastScanResult = null lastIgnoreUnpaid = false - handleScan(keyboardBuffer, null, !conf.unpaidAsk, source_type = lastScanSourceType.serverName) + handleScan( + keyboardBuffer, + lastScanSourceType.serverName, + null, + !conf.unpaidAsk + ) keyboardBuffer = "" true } @@ -1602,7 +1637,12 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result dialog = showQuestionsDialog(lastScanResult!!, lastScanCode, lastScanSourceType, lastIgnoreUnpaid, values, true) { secret, sourceType, answers, ignore_unpaid -> stopHidingTimer() - handleScan(secret, answers, ignore_unpaid, source_type = sourceType.serverName) + handleScan( + secret, + sourceType.serverName, + answers, + ignore_unpaid + ) } dialog!!.onRestoreInstanceState(answers) dialog!!.setOnCancelListener(DialogInterface.OnCancelListener { hideCard() }) @@ -1652,7 +1692,12 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result lastScanResult = null lastIgnoreUnpaid = false - handleScan(identifier, null, !conf.unpaidAsk, source_type = mediaType.serverName) + handleScan( + identifier, + mediaType.serverName, + null, + !conf.unpaidAsk + ) } override fun chipReadError(error: ChipReadError, identifier: String?) { From 8d85faf0e55c10a85d696c1754dfaf71c198a791 Mon Sep 17 00:00:00 2001 From: Maximilian Richt Date: Tue, 14 Apr 2026 15:55:34 +0200 Subject: [PATCH 15/15] Bump libpretixnfc --- pretixscan/libpretixnfc-repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pretixscan/libpretixnfc-repo b/pretixscan/libpretixnfc-repo index c1c1c661..d2a04c79 160000 --- a/pretixscan/libpretixnfc-repo +++ b/pretixscan/libpretixnfc-repo @@ -1 +1 @@ -Subproject commit c1c1c661d505ca919e8dba414c94b1511fe64f2a +Subproject commit d2a04c79ecb6d8b7abf4b8f1a59a609b879b0c39