Skip to content

Commit

Permalink
Provide audible or haptic feedback on click (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
ldeso committed May 24, 2024
1 parent 362ddd3 commit c2b2206
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 7 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<!-- Copyright 2024 Léo de Souza -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

## 1.9.0 – Unreleased

### New Feature

- Provide audible or haptic feedback on click

## [1.8.8](https://github.com/ldeso/blitz/releases/tag/v1.8.8) – 2024-05-21

This release improves the style of the text, enables hardware memory tagging on compatible devices and updates dependencies.
Expand Down
1 change: 1 addition & 0 deletions metadata/en-US/changelogs/190.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
• Provide audible or haptic feedback on click
78 changes: 71 additions & 7 deletions src/main/kotlin/ui/ClockInputs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ package net.leodesouza.blitz.ui

import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.media.AudioManager
import android.media.AudioManager.RINGER_MODE_NORMAL
import android.media.AudioManager.RINGER_MODE_VIBRATE
import android.os.Build
import androidx.activity.compose.BackHandler
import androidx.activity.compose.PredictiveBackHandler
Expand All @@ -15,6 +18,8 @@ import androidx.compose.foundation.gestures.detectVerticalDragGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedback
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
Expand Down Expand Up @@ -108,6 +113,8 @@ fun ClockBackHandler(
* @param[isBusyProvider] Lambda for whether the clock is currently busy.
* @param[displayOrientation] The [ORIENTATION_PORTRAIT] or [ORIENTATION_LANDSCAPE] of the display.
* @param[layoutDirection] Whether the layout direction is left-to-right or right-to-left.
* @param[audioManager] The interface used to play sound effects.
* @param[haptics] The interface used to provide haptic feedback.
* @param[start] Callback called to start the clock.
* @param[play] Callback called to switch to the next player.
* @param[save] Callback called to save the time or configuration.
Expand All @@ -121,6 +128,8 @@ fun Modifier.clockInput(
isBusyProvider: () -> Boolean,
displayOrientation: Int, // ORIENTATION_PORTRAIT or ORIENTATION_LANDSCAPE
layoutDirection: LayoutDirection,
audioManager: AudioManager,
haptics: HapticFeedback,
start: () -> Unit,
play: () -> Unit,
save: () -> Unit,
Expand All @@ -130,6 +139,8 @@ fun Modifier.clockInput(
onClickEvent(
clockState = clockStateProvider(),
isBusy = isBusyProvider(),
audioManager = audioManager,
haptics = haptics,
start = start,
play = play,
)
Expand All @@ -153,7 +164,11 @@ fun Modifier.clockInput(
},
onDragEnd = {
onDragEnd(
clockState = clockStateProvider(), isBusy = isBusyProvider(), play = play,
clockState = clockStateProvider(),
isBusy = isBusyProvider(),
audioManager = audioManager,
haptics = haptics,
play = play,
)
},
onHorizontalDrag = { _: PointerInputChange, dragAmount: Float ->
Expand All @@ -178,7 +193,11 @@ fun Modifier.clockInput(
},
onDragEnd = {
onDragEnd(
clockState = clockStateProvider(), isBusy = isBusyProvider(), play = play,
clockState = clockStateProvider(),
isBusy = isBusyProvider(),
audioManager = audioManager,
haptics = haptics,
play = play,
)
},
onVerticalDrag = { _: PointerInputChange, dragAmount: Float ->
Expand All @@ -201,17 +220,32 @@ fun Modifier.clockInput(
*
* @param[clockState] Current state of the clock.
* @param[isBusy] Whether the clock is currently busy.
* @param[audioManager] The interface used to play sound effects.
* @param[haptics] The interface used to provide haptic feedback.
* @param[start] Callback called to start the clock.
* @param[play] Callback called to switch to the next player.
*/
private fun onClickEvent(
clockState: ClockState, isBusy: Boolean, start: () -> Unit, play: () -> Unit,
clockState: ClockState,
isBusy: Boolean,
audioManager: AudioManager,
haptics: HapticFeedback,
start: () -> Unit,
play: () -> Unit,
) {
if (!isBusy) {
when (clockState) {
ClockState.PAUSED, ClockState.SOFT_RESET, ClockState.FULL_RESET -> start()
ClockState.TICKING -> play()
else -> Unit
ClockState.PAUSED, ClockState.SOFT_RESET, ClockState.FULL_RESET -> run {
start()
provideFeedback(audioManager, haptics)
}

ClockState.TICKING -> run {
play()
provideFeedback(audioManager, haptics)
}

ClockState.FINISHED -> Unit
}
}
}
Expand Down Expand Up @@ -286,11 +320,20 @@ private fun onDragStart(clockState: ClockState, isBusy: Boolean, save: () -> Uni
*
* @param[clockState] Current state of the clock.
* @param[isBusy] Whether the clock is currently busy.
* @param[audioManager] The interface used to play sound effects.
* @param[haptics] The interface used to provide haptic feedback.
* @param[play] Callback called to switch to the next player.
*/
private fun onDragEnd(clockState: ClockState, isBusy: Boolean, play: () -> Unit) {
private fun onDragEnd(
clockState: ClockState,
isBusy: Boolean,
audioManager: AudioManager,
haptics: HapticFeedback,
play: () -> Unit,
) {
if (!isBusy && clockState == ClockState.TICKING) {
play()
provideFeedback(audioManager, haptics)
}
}

Expand Down Expand Up @@ -388,3 +431,24 @@ private fun onVerticalDrag(
}
}
}

/**
* Use the [audioManager] interface to play a sound effect when the ringtone mode is set to
* [RINGER_MODE_NORMAL], or use the [haptics] interface to provide haptic feedback when
* the ringtone mode is set to [RINGER_MODE_VIBRATE].
*/
private fun provideFeedback(audioManager: AudioManager, haptics: HapticFeedback) {
when (audioManager.ringerMode) {
RINGER_MODE_NORMAL -> with(audioManager) {
val streamType = AudioManager.STREAM_MUSIC
val volumeIndex = getStreamVolume(streamType).toFloat()
if (volumeIndex > 0F) {
val maxVolumeIndex = getStreamMaxVolume(streamType).toFloat()
val volume = volumeIndex / maxVolumeIndex
playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, volume)
}
}

RINGER_MODE_VIBRATE -> haptics.performHapticFeedback(HapticFeedbackType.LongPress)
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/ui/ClockScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

package net.leodesouza.blitz.ui

import android.content.Context
import android.media.AudioManager
import androidx.activity.BackEventCompat
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
Expand All @@ -16,6 +18,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.LayoutDirection
Expand Down Expand Up @@ -55,6 +59,8 @@ fun ClockScreen(
val lifecycleOwner = LocalLifecycleOwner.current
val displayOrientation = LocalConfiguration.current.orientation
val layoutDirection = LocalLayoutDirection.current
val audioManager = LocalContext.current.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val haptics = LocalHapticFeedback.current

val whiteTime by clockViewModel.whiteTime.collectAsStateWithLifecycle(lifecycleOwner)
val blackTime by clockViewModel.blackTime.collectAsStateWithLifecycle(lifecycleOwner)
Expand Down Expand Up @@ -113,6 +119,8 @@ fun ClockScreen(
isBusyProvider = { isBusy },
displayOrientation = displayOrientation,
layoutDirection = layoutDirection,
audioManager = audioManager,
haptics = haptics,
start = {
onClockStart()
clockViewModel.start()
Expand Down

0 comments on commit c2b2206

Please sign in to comment.