mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-02 08:23:00 +01:00
Add ability to use volume buttons to capture image/video.
This commit is contained in:
committed by
Cody Henthorne
parent
f584ef1d72
commit
d28fc98cfd
@@ -6,6 +6,7 @@
|
|||||||
package org.signal.camera.hud
|
package org.signal.camera.hud
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.view.KeyEvent
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
@@ -15,6 +16,7 @@ import androidx.compose.animation.fadeOut
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -32,13 +34,21 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -108,6 +118,20 @@ fun BoxScope.StandardCameraHud(
|
|||||||
stringResources: StringResources = StringResources(0, 0)
|
stringResources: StringResources = StringResources(0, 0)
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
val viewConfiguration = LocalViewConfiguration.current
|
||||||
|
var volumeKeyPressStartTime by remember { mutableStateOf(0L) }
|
||||||
|
var isRecordingFromVolumeKey by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(state.isRecording) {
|
||||||
|
if (!state.isRecording) {
|
||||||
|
isRecordingFromVolumeKey = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.captureError) {
|
LaunchedEffect(state.captureError) {
|
||||||
state.captureError?.let { error ->
|
state.captureError?.let { error ->
|
||||||
@@ -126,15 +150,64 @@ fun BoxScope.StandardCameraHud(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StandardCameraHudContent(
|
Box(
|
||||||
state = state,
|
modifier = Modifier
|
||||||
emitter = emitter,
|
.fillMaxSize()
|
||||||
modifier = modifier,
|
.focusRequester(focusRequester)
|
||||||
maxRecordingDurationMs = maxRecordingDurationMs,
|
.onPreviewKeyEvent { keyEvent ->
|
||||||
mediaSelectionCount = mediaSelectionCount,
|
val nativeEvent = keyEvent.nativeKeyEvent
|
||||||
hasAudioPermission = hasAudioPermission,
|
val keyCode = nativeEvent.keyCode
|
||||||
stringResources = stringResources
|
|
||||||
)
|
if (keyCode != KeyEvent.KEYCODE_VOLUME_UP && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||||
|
return@onPreviewKeyEvent false
|
||||||
|
}
|
||||||
|
|
||||||
|
when (nativeEvent.action) {
|
||||||
|
KeyEvent.ACTION_DOWN -> {
|
||||||
|
if (nativeEvent.repeatCount == 0) {
|
||||||
|
volumeKeyPressStartTime = nativeEvent.eventTime
|
||||||
|
isRecordingFromVolumeKey = false
|
||||||
|
} else if (!isRecordingFromVolumeKey &&
|
||||||
|
volumeKeyPressStartTime > 0 &&
|
||||||
|
nativeEvent.eventTime - volumeKeyPressStartTime >= viewConfiguration.longPressTimeoutMillis
|
||||||
|
) {
|
||||||
|
volumeKeyPressStartTime = 0
|
||||||
|
if (hasAudioPermission()) {
|
||||||
|
isRecordingFromVolumeKey = true
|
||||||
|
emitter(StandardCameraHudEvents.VideoCaptureStarted)
|
||||||
|
} else {
|
||||||
|
emitter(StandardCameraHudEvents.AudioPermissionRequired)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent.ACTION_UP -> {
|
||||||
|
if (isRecordingFromVolumeKey) {
|
||||||
|
isRecordingFromVolumeKey = false
|
||||||
|
emitter(StandardCameraHudEvents.VideoCaptureStopped)
|
||||||
|
} else if (volumeKeyPressStartTime > 0) {
|
||||||
|
emitter(StandardCameraHudEvents.PhotoCaptureTriggered)
|
||||||
|
}
|
||||||
|
volumeKeyPressStartTime = 0
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.focusable()
|
||||||
|
) {
|
||||||
|
StandardCameraHudContent(
|
||||||
|
state = state,
|
||||||
|
emitter = emitter,
|
||||||
|
modifier = modifier,
|
||||||
|
maxRecordingDurationMs = maxRecordingDurationMs,
|
||||||
|
mediaSelectionCount = mediaSelectionCount,
|
||||||
|
hasAudioPermission = hasAudioPermission,
|
||||||
|
stringResources = stringResources
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
Reference in New Issue
Block a user