Add haptic feedback

Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
2026-02-07 20:41:42 +03:00
Unverified
parent a0615f2cc2
commit cf1ec623e4
8 changed files with 63 additions and 3 deletions
+1
View File
@@ -19,6 +19,7 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:enableOnBackInvokedCallback="true"
android:theme="@style/Theme.FromChat.SplashScreen"
android:windowSoftInputMode="adjustResize">
<intent-filter>
@@ -1,9 +1,28 @@
package ru.fromchat.ui
import android.view.HapticFeedbackConstants
import androidx.activity.compose.BackHandler as AndroidBackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalView
@Composable
actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {
AndroidBackHandler(enabled = enabled, onBack = onBack)
}
@Composable
actual fun rememberHapticFeedbackInternal(): (Int) -> Unit {
val view = LocalView.current
return remember(view) {
{ ordinal ->
val constant = when (ordinal) {
HapticFeedbackEvent.ProfileOpened.ordinal -> HapticFeedbackConstants.CLOCK_TICK
HapticFeedbackEvent.ProfileClosed.ordinal -> HapticFeedbackConstants.CLOCK_TICK
HapticFeedbackEvent.MessageSent.ordinal -> HapticFeedbackConstants.CONFIRM
else -> HapticFeedbackConstants.CLOCK_TICK
}
view.performHapticFeedback(constant)
}
}
}
@@ -9,3 +9,6 @@ import androidx.compose.runtime.Composable
*/
@Composable
expect fun BackHandler(enabled: Boolean, onBack: () -> Unit)
@Composable
expect fun rememberHapticFeedbackInternal(): (Int) -> Unit
@@ -0,0 +1,10 @@
package ru.fromchat.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@Composable
fun rememberHapticFeedback(): (HapticFeedbackEvent) -> Unit {
val h = rememberHapticFeedbackInternal()
return remember(h) { { event: HapticFeedbackEvent -> h(event.ordinal) } }
}
@@ -0,0 +1,7 @@
package ru.fromchat.ui
enum class HapticFeedbackEvent {
ProfileOpened,
ProfileClosed,
MessageSent
}
@@ -72,7 +72,9 @@ import ru.fromchat.api.WebSocketMessage
import ru.fromchat.api.WebSocketUpdatesData
import ru.fromchat.back
import ru.fromchat.core.Logger
import ru.fromchat.ui.HapticFeedbackEvent
import ru.fromchat.ui.LocalNavController
import ru.fromchat.ui.rememberHapticFeedback
import ru.fromchat.ui.scaleOnPress
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
@@ -116,6 +118,7 @@ fun ChatScreen(
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
val haptic = rememberHapticFeedback()
val navController = LocalNavController.current
val profileUserId = panelState.profileUserId
val hazeState = rememberHazeState(blurEnabled = true)
@@ -389,6 +392,7 @@ fun ChatScreen(
scope.launch {
panel.sendMessageWithImmediateDisplay(text, replyTo?.id)
replyTo = null
haptic(HapticFeedbackEvent.MessageSent)
}
}
inputText = ""
@@ -15,6 +15,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import ru.fromchat.ui.BackHandler
import ru.fromchat.ui.HapticFeedbackEvent
import ru.fromchat.ui.rememberHapticFeedback
import ru.fromchat.ui.profile.ProfileScreen
private const val TRANSITION_MS = 320
@@ -27,11 +29,13 @@ fun DmContainerScreen(
) {
var showProfile by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
val haptic = rememberHapticFeedback()
val panel = remember(otherUserId) {
DmPanelCache.getOrCreate(otherUserId, scope)
}
BackHandler(enabled = showProfile) {
haptic(HapticFeedbackEvent.ProfileClosed)
showProfile = false
}
@@ -51,7 +55,10 @@ fun DmContainerScreen(
DmScreen(
panel = panel,
modifier = Modifier.fillMaxSize(),
onTitleClick = { showProfile = true },
onTitleClick = {
haptic(HapticFeedbackEvent.ProfileOpened)
showProfile = true
},
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this@AnimatedContent,
sharedAvatarKey = sharedAvatarKey
@@ -59,8 +66,14 @@ fun DmContainerScreen(
} else {
ProfileScreen(
userId = otherUserId,
onBack = { showProfile = false },
onChat = { _ -> showProfile = false },
onBack = {
haptic(HapticFeedbackEvent.ProfileClosed)
showProfile = false
},
onChat = { _ ->
haptic(HapticFeedbackEvent.ProfileClosed)
showProfile = false
},
modifier = Modifier.fillMaxSize(),
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this@AnimatedContent,
@@ -6,3 +6,6 @@ import androidx.compose.runtime.Composable
actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {
// iOS has no system back button; back is handled by navigation.
}
@Composable
actual fun rememberHapticFeedbackInternal(): (Int) -> Unit = { }