feat: ui haptics

This commit is contained in:
2026-06-06 23:52:16 +02:00
parent 644119377d
commit 3dc4b63fc5
4 changed files with 79 additions and 0 deletions
+1
View File
@@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-feature android:name="android.hardware.camera" android:required="false" /> <uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.nfc" android:required="false" /> <uses-feature android:name="android.hardware.nfc" android:required="false" />
@@ -58,10 +58,14 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bitcointxoko.gudariwallet.LocalAppStrings import com.bitcointxoko.gudariwallet.LocalAppStrings
import com.bitcointxoko.gudariwallet.ui.ReceiveState import com.bitcointxoko.gudariwallet.ui.ReceiveState
import com.bitcointxoko.gudariwallet.ui.SendState
import com.bitcointxoko.gudariwallet.ui.WalletViewModel import com.bitcointxoko.gudariwallet.ui.WalletViewModel
import com.bitcointxoko.gudariwallet.ui.components.UnitWheelPicker import com.bitcointxoko.gudariwallet.ui.components.UnitWheelPicker
import com.bitcointxoko.gudariwallet.ui.nfc.NfcViewModel 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.fiatLabel
import com.bitcointxoko.gudariwallet.util.heavyClick
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@Composable @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()) { Box(modifier = Modifier.fillMaxSize()) {
if (receiveState !is ReceiveState.InvoiceReady) { if (receiveState !is ReceiveState.InvoiceReady) {
Column( Column(
@@ -31,8 +31,10 @@ import com.bitcointxoko.gudariwallet.ui.WalletViewModel
import com.bitcointxoko.gudariwallet.ui.nfc.NfcViewModel import com.bitcointxoko.gudariwallet.ui.nfc.NfcViewModel
import com.bitcointxoko.gudariwallet.ui.receive.AmountDisplay import com.bitcointxoko.gudariwallet.ui.receive.AmountDisplay
import com.bitcointxoko.gudariwallet.util.SendInputDetector import com.bitcointxoko.gudariwallet.util.SendInputDetector
import com.bitcointxoko.gudariwallet.util.doubleTap
import com.bitcointxoko.gudariwallet.util.feePpm import com.bitcointxoko.gudariwallet.util.feePpm
import com.bitcointxoko.gudariwallet.util.fiatLabel import com.bitcointxoko.gudariwallet.util.fiatLabel
import com.bitcointxoko.gudariwallet.util.heavyClick
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch 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( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -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)
}
}