diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d8c9a30..54e7572 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,6 +9,7 @@
+
diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/receive/ReceiveScreen.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/receive/ReceiveScreen.kt
index cdb7ab5..9bffb5a 100644
--- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/receive/ReceiveScreen.kt
+++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/receive/ReceiveScreen.kt
@@ -58,10 +58,14 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bitcointxoko.gudariwallet.LocalAppStrings
import com.bitcointxoko.gudariwallet.ui.ReceiveState
+import com.bitcointxoko.gudariwallet.ui.SendState
import com.bitcointxoko.gudariwallet.ui.WalletViewModel
import com.bitcointxoko.gudariwallet.ui.components.UnitWheelPicker
import com.bitcointxoko.gudariwallet.ui.nfc.NfcViewModel
+import com.bitcointxoko.gudariwallet.util.buildUpTaps
+import com.bitcointxoko.gudariwallet.util.doubleTap
import com.bitcointxoko.gudariwallet.util.fiatLabel
+import com.bitcointxoko.gudariwallet.util.heavyClick
import kotlinx.coroutines.delay
@Composable
@@ -107,6 +111,14 @@ fun ReceiveScreen(
}
}
+ LaunchedEffect(receiveState) {
+ when (receiveState) {
+ is ReceiveState.PaymentReceived -> context.buildUpTaps()
+ // is SendState.Error -> context.errorBuzz()
+ else -> Unit
+ }
+ }
+
Box(modifier = Modifier.fillMaxSize()) {
if (receiveState !is ReceiveState.InvoiceReady) {
Column(
diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendScreen.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendScreen.kt
index 24a6a8b..8685d93 100644
--- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendScreen.kt
+++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendScreen.kt
@@ -31,8 +31,10 @@ import com.bitcointxoko.gudariwallet.ui.WalletViewModel
import com.bitcointxoko.gudariwallet.ui.nfc.NfcViewModel
import com.bitcointxoko.gudariwallet.ui.receive.AmountDisplay
import com.bitcointxoko.gudariwallet.util.SendInputDetector
+import com.bitcointxoko.gudariwallet.util.doubleTap
import com.bitcointxoko.gudariwallet.util.feePpm
import com.bitcointxoko.gudariwallet.util.fiatLabel
+import com.bitcointxoko.gudariwallet.util.heavyClick
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -82,6 +84,16 @@ fun SendScreen(
}
}
+ LaunchedEffect(sendState) {
+ when (sendState) {
+ is SendState.Bolt11Decoded,
+ is SendState.LnurlInvoiceReady -> context.doubleTap()
+ is SendState.PaymentSent -> context.heavyClick()
+ // is SendState.Error -> context.errorBuzz()
+ else -> Unit
+ }
+ }
+
Column(
modifier = Modifier
.fillMaxSize()
diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/util/Haptics.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/util/Haptics.kt
new file mode 100644
index 0000000..eccffb8
--- /dev/null
+++ b/app/src/main/java/com/bitcointxoko/gudariwallet/util/Haptics.kt
@@ -0,0 +1,54 @@
+package com.bitcointxoko.gudariwallet.util
+
+import android.content.Context
+import android.os.*
+
+fun Context.heavyClick() {
+ val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ (getSystemService(VibratorManager::class.java)).defaultVibrator
+ } else {
+ @Suppress("DEPRECATION")
+ getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK))
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ vibrator.vibrate(VibrationEffect.createOneShot(50L, VibrationEffect.DEFAULT_AMPLITUDE))
+ }
+}
+
+fun Context.doubleTap() {
+ val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ (getSystemService(VibratorManager::class.java)).defaultVibrator
+ } else {
+ @Suppress("DEPRECATION")
+ getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK))
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ vibrator.vibrate(VibrationEffect.createOneShot(50L, VibrationEffect.DEFAULT_AMPLITUDE))
+ }
+}
+
+fun Context.buildUpTaps() {
+ val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ (getSystemService(VibratorManager::class.java)).defaultVibrator
+ } else {
+ @Suppress("DEPRECATION")
+ getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ // Timings: off, on, off, on, off, on, off, FINAL ON
+ val timings = longArrayOf(0, 30, 80, 30, 50, 30, 20, 60)
+ val amplitudes = intArrayOf(0, 60, 0, 90, 0, 140, 0, 255)
+ // ^gap ^tap ^gap ^tap ^gap ^tap ^gap ^THUD
+
+ vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, -1))
+ } else {
+ // Legacy fallback — simple triple-tap
+ @Suppress("DEPRECATION")
+ vibrator.vibrate(longArrayOf(0, 40, 60, 40, 40, 80), -1)
+ }
+}
\ No newline at end of file