Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add haptic Feedback #127

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pretixscan/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<uses-permission android:name="android.permission.VIBRATE" />

<queries>
<package android:name="eu.pretix.pretixprint" />
<package android:name="eu.pretix.pretixprint.debug" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,10 @@ class AppConfig(ctx: Context) : ConfigStore {
get() = default_prefs.getBoolean(PREFS_KEY_SOUNDS, true)
set(value) = default_prefs.edit().putBoolean(PREFS_KEY_SOUNDS, value).apply()

var haptics: Boolean
get() = default_prefs.getBoolean(PREFS_KEY_HAPTICS, false)
set(value) = default_prefs.edit().putBoolean(PREFS_KEY_HAPTICS, value).apply()

var proxyMode: Boolean
get() = default_prefs.getBoolean(PREFS_KEY_SCAN_PROXY, false)
set(value) = default_prefs.edit().putBoolean(PREFS_KEY_SCAN_PROXY, value).apply()
Expand Down Expand Up @@ -447,6 +451,7 @@ class AppConfig(ctx: Context) : ConfigStore {
val PREFS_KEY_IGNORE_QUESTIONS = "pref_ignore_questions"
val PREFS_KEY_SCAN_TYPE = "pref_scan_type"
val PREFS_KEY_SOUNDS = "pref_sounds"
val PREFS_KEY_HAPTICS = "pref_haptics"
val PREFS_KEY_HIDE_NAMES = "pref_hide_names"
val PREFS_KEY_SEARCH_DISABLE = "pref_search_disable"
val PREFS_KEY_KIOSK_MODE = "pref_kiosk_mode"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.ResultReceiver
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.util.Base64
Expand Down Expand Up @@ -255,6 +258,22 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result
view_data.isOffline.set(conf.offlineMode)
}

private fun vibrate(success: Boolean) {
val vibrator: Vibrator
if (Build.VERSION.SDK_INT >= 31) {
val vb = this.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibrator = vb.defaultVibrator
} else {
vibrator = this.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
}

if (Build.VERSION.SDK_INT >= 26) {
vibrator.vibrate(VibrationEffect.createOneShot(if (success) 50L else 200L, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
vibrator.vibrate(if (success) 50L else 200L)
}
}

private fun setSearchFilter(f: String) {
binding.cardSearch.visibility = View.VISIBLE
view_data.searchState.set(LOADING)
Expand Down Expand Up @@ -957,8 +976,9 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result
showLoadingCard()
hideSearchCard()

if (answers == null && !ignore_unpaid && !conf.offlineMode && conf.sounds) {
mediaPlayers[R.raw.beep]?.start()
if (answers == null && !ignore_unpaid && !conf.offlineMode) {
if (conf.sounds) mediaPlayers[R.raw.beep]?.start()
if (conf.haptics) vibrate(true)
}

bgScope.launch {
Expand Down Expand Up @@ -1054,30 +1074,40 @@ class MainActivity : AppCompatActivity(), ReloadableActivity, ScannerView.Result
lastScanResult = result
lastIgnoreUnpaid = ignore_unpaid

if (conf.sounds)
when (result.type) {
when (result.type) {
TicketCheckProvider.CheckResult.Type.VALID -> when (result.scanType) {
TicketCheckProvider.CheckInType.ENTRY ->
if (result.isRequireAttention) {
mediaPlayers[R.raw.attention]?.start()
if (conf.haptics) vibrate(false)
} else {
mediaPlayers[R.raw.enter]?.start()
if (conf.sounds) mediaPlayers[R.raw.enter]?.start()
if (conf.haptics) vibrate(true)
}
TicketCheckProvider.CheckInType.EXIT -> mediaPlayers[R.raw.exit]?.start()
TicketCheckProvider.CheckInType.EXIT -> {
if (conf.sounds) mediaPlayers[R.raw.exit]?.start()
if (conf.haptics) vibrate(true)
}
}
TicketCheckProvider.CheckResult.Type.INVALID,
TicketCheckProvider.CheckResult.Type.ERROR,
TicketCheckProvider.CheckResult.Type.UNPAID,
TicketCheckProvider.CheckResult.Type.CANCELED,
TicketCheckProvider.CheckResult.Type.PRODUCT,
TicketCheckProvider.CheckResult.Type.RULES,
TicketCheckProvider.CheckResult.Type.AMBIGUOUS,
TicketCheckProvider.CheckResult.Type.REVOKED,
TicketCheckProvider.CheckResult.Type.UNAPPROVED,
TicketCheckProvider.CheckResult.Type.BLOCKED,
TicketCheckProvider.CheckResult.Type.INVALID_TIME,
TicketCheckProvider.CheckResult.Type.USED -> {
if (conf.sounds) mediaPlayers[R.raw.error]?.start()
if (conf.haptics) vibrate(false)
}
TicketCheckProvider.CheckResult.Type.ANSWERS_REQUIRED -> {
if (conf.sounds) mediaPlayers[R.raw.attention]?.start()
if (conf.haptics) vibrate(false)
}
TicketCheckProvider.CheckResult.Type.INVALID -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.ERROR -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.UNPAID -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.CANCELED -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.PRODUCT -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.RULES -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.AMBIGUOUS -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.REVOKED -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.UNAPPROVED -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.BLOCKED -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.INVALID_TIME -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.USED -> mediaPlayers[R.raw.error]?.start()
TicketCheckProvider.CheckResult.Type.ANSWERS_REQUIRED -> mediaPlayers[R.raw.attention]?.start()
else -> {
}
}
Expand Down
1 change: 1 addition & 0 deletions pretixscan/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
</string-array>
<string name="settings_summary_scan_offline">When scanning offline, your device will verify all input with its internal database instead with the server and synchronize its database with the server occasionally. This is more reliable, but allows a ticket to be scanned twice if you scan with multiple devices.</string>
<string name="settings_label_sounds">Play sounds</string>
<string name="settings_label_haptics">Enable Haptic Feedback</string>
<string name="settings_label_hide_names">Hide names and other personal information</string>
<string name="settings_label_hide_names_summary">Does not affect search, useful e.g. in combination with kiosk mode</string>
<string name="settings_label_badges">Badges</string>
Expand Down
4 changes: 4 additions & 0 deletions pretixscan/app/src/main/res/xml/preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
android:defaultValue="true"
android:key="pref_sounds"
android:title="@string/settings_label_sounds" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pref_haptics"
android:title="@string/settings_label_haptics" />
<Preference
android:fragment="eu.pretix.pretixscan.droid.ui.PinSettingsFragment"
android:key="pin_protect"
Expand Down