stronger haptics + live language switching
Haptics: all feedback stronger and more distinct — - click (send): 1.0 intensity CLICK (was 0.85) - thinkingStart: double-tap TICK 0.5→0.7 (was single 0.22) - responseStart: three-step crescendo TICK→CLICK→CLICK (firmer) - responseEnd: firm CLICK + settling TICK (was soft 0.3 TICK) Language: selectLocale() now applies immediately — - API 33+: LocaleManager.setApplicationLocales (no restart) - API <33: updateConfiguration + activity.recreate() No new dependencies needed.
This commit is contained in:
parent
163a2cee28
commit
e9e2a66b7d
3 changed files with 44 additions and 22 deletions
|
|
@ -36,47 +36,53 @@ class Haptics(private val vibrator: Vibrator?) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Firmer click — send button. */
|
/** Firmer click — send button, satisfying "message sent" confirmation. */
|
||||||
fun click() {
|
fun click() {
|
||||||
val vib = active() ?: return
|
val vib = active() ?: return
|
||||||
if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_CLICK)) {
|
if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_CLICK)) {
|
||||||
vib.composePrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK to 0.85f)
|
vib.composePrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK to 1.0f)
|
||||||
} else {
|
} else {
|
||||||
vib.predefinedOrOneShot(soft = false)
|
vib.predefinedOrOneShot(soft = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Very soft — the model received the prompt and starts thinking. */
|
/** Double-tap — the model received the prompt and starts thinking. */
|
||||||
fun thinkingStart() {
|
fun thinkingStart() {
|
||||||
val vib = active() ?: return
|
|
||||||
if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_TICK)) {
|
|
||||||
vib.composePrimitives(VibrationEffect.Composition.PRIMITIVE_TICK to 0.22f)
|
|
||||||
} else {
|
|
||||||
vib.predefinedOrOneShot(soft = true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** "The answer is arriving" — a three-step rising crescendo (the key moment). */
|
|
||||||
fun responseStart() {
|
|
||||||
val vib = active() ?: return
|
val vib = active() ?: return
|
||||||
if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_TICK)) {
|
if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_TICK)) {
|
||||||
vib.composePrimitives(
|
vib.composePrimitives(
|
||||||
Triple(VibrationEffect.Composition.PRIMITIVE_TICK, 0.30f, 0),
|
Triple(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
|
||||||
Triple(VibrationEffect.Composition.PRIMITIVE_TICK, 0.60f, 40),
|
Triple(VibrationEffect.Composition.PRIMITIVE_TICK, 0.7f, 60),
|
||||||
Triple(VibrationEffect.Composition.PRIMITIVE_TICK, 1.00f, 40),
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
vib.predefinedOrOneShot(soft = false)
|
vib.predefinedOrOneShot(soft = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gentle settle — stream finished. */
|
/** Rising crescendo — first token arrived, answer is streaming. */
|
||||||
|
fun responseStart() {
|
||||||
|
val vib = active() ?: return
|
||||||
|
if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_CLICK)) {
|
||||||
|
vib.composePrimitives(
|
||||||
|
Triple(VibrationEffect.Composition.PRIMITIVE_TICK, 0.4f, 0),
|
||||||
|
Triple(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.7f, 50),
|
||||||
|
Triple(VibrationEffect.Composition.PRIMITIVE_CLICK, 1.0f, 50),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
vib.predefinedOrOneShot(soft = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Firm settle — stream finished, answer complete. */
|
||||||
fun responseEnd() {
|
fun responseEnd() {
|
||||||
val vib = active() ?: return
|
val vib = active() ?: return
|
||||||
if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_TICK)) {
|
if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_CLICK)) {
|
||||||
vib.composePrimitives(VibrationEffect.Composition.PRIMITIVE_TICK to 0.3f)
|
vib.composePrimitives(
|
||||||
|
Triple(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.8f, 0),
|
||||||
|
Triple(VibrationEffect.Composition.PRIMITIVE_TICK, 0.4f, 80),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
vib.predefinedOrOneShot(soft = true)
|
vib.predefinedOrOneShot(soft = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import dev.kaizen.app.ui.theme.themes.KaizenThemeId
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import dev.kaizen.app.chat.MeshBackground
|
import dev.kaizen.app.chat.MeshBackground
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -129,7 +130,8 @@ fun SettingsScreen(
|
||||||
SettingsOptionRow(title = stringResource(R.string.settings_language_title), description = stringResource(R.string.settings_language_desc), icon = Icons.Rounded.Language) {
|
SettingsOptionRow(title = stringResource(R.string.settings_language_title), description = stringResource(R.string.settings_language_desc), icon = Icons.Rounded.Language) {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
listOf("system" to R.string.settings_language_system, "de" to R.string.settings_language_de, "en" to R.string.settings_language_en).forEach { (code, labelRes) ->
|
listOf("system" to R.string.settings_language_system, "de" to R.string.settings_language_de, "en" to R.string.settings_language_en).forEach { (code, labelRes) ->
|
||||||
AppearanceTogglePill(selected = locale == code, label = stringResource(labelRes), onClick = { viewModel.selectLocale(code) })
|
val ctx = LocalContext.current
|
||||||
|
AppearanceTogglePill(selected = locale == code, label = stringResource(labelRes), onClick = { viewModel.selectLocale(code, ctx as? android.app.Activity) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,22 @@ class SettingsViewModel(private val store: SecureStore? = null) : ViewModel() {
|
||||||
_userEmail.value = email
|
_userEmail.value = email
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectLocale(locale: String) {
|
fun selectLocale(locale: String, activity: android.app.Activity? = null) {
|
||||||
_locale.value = locale
|
_locale.value = locale
|
||||||
store?.saveLocale(locale)
|
store?.saveLocale(locale)
|
||||||
|
val localeList = if (locale == "system") {
|
||||||
|
android.os.LocaleList.getDefault()
|
||||||
|
} else {
|
||||||
|
android.os.LocaleList(java.util.Locale.forLanguageTag(locale))
|
||||||
|
}
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
activity?.getSystemService(android.app.LocaleManager::class.java)?.applicationLocales = localeList
|
||||||
|
} else {
|
||||||
|
val config = android.content.res.Configuration(activity?.resources?.configuration)
|
||||||
|
config.setLocales(localeList)
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
activity?.resources?.updateConfiguration(config, activity.resources.displayMetrics)
|
||||||
|
activity?.recreate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue