Add turn on your video tooltip to call screen v2.
This commit is contained in:
parent
5552455c2e
commit
eaf81e56d6
7 changed files with 153 additions and 15 deletions
|
@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.events.WebRtcViewModel
|
||||||
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity
|
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
|
||||||
import org.thoughtcrime.securesms.util.FullscreenHelper
|
import org.thoughtcrime.securesms.util.FullscreenHelper
|
||||||
import org.thoughtcrime.securesms.util.VibrateUtil
|
import org.thoughtcrime.securesms.util.VibrateUtil
|
||||||
import org.thoughtcrime.securesms.util.viewModel
|
import org.thoughtcrime.securesms.util.viewModel
|
||||||
|
@ -101,6 +102,8 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
||||||
viewModel.callActions.collect {
|
viewModel.callActions.collect {
|
||||||
when (it) {
|
when (it) {
|
||||||
CallViewModel.Action.EnableVideo -> onVideoToggleClick(true)
|
CallViewModel.Action.EnableVideo -> onVideoToggleClick(true)
|
||||||
|
is CallViewModel.Action.ShowGroupCallSafetyNumberChangeDialog -> SafetyNumberBottomSheet.forGroupCall(it.untrustedIdentities).show(supportFragmentManager)
|
||||||
|
CallViewModel.Action.SwitchToSpeaker -> Unit // TODO - Switch user to speaker view.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,6 +327,10 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
||||||
viewModel.hangup()
|
viewModel.hangup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onVideoTooltipDismissed() {
|
||||||
|
viewModel.onVideoTooltipDismissed()
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeCallEvents() {
|
private fun observeCallEvents() {
|
||||||
webRtcCallViewModel.events.observe(this) { event ->
|
webRtcCallViewModel.events.observe(this) { event ->
|
||||||
viewModel.onCallEvent(event)
|
viewModel.onCallEvent(event)
|
||||||
|
|
|
@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.util.RemoteConfig
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun CallControls(
|
fun CallControls(
|
||||||
|
displayVideoTooltip: Boolean,
|
||||||
callControlsState: CallControlsState,
|
callControlsState: CallControlsState,
|
||||||
callControlsCallback: CallControlsCallback,
|
callControlsCallback: CallControlsCallback,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
|
@ -96,10 +97,16 @@ fun CallControls(
|
||||||
|
|
||||||
val hasCameraPermission = ContextCompat.checkSelfPermission(LocalContext.current, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
|
val hasCameraPermission = ContextCompat.checkSelfPermission(LocalContext.current, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
|
||||||
if (callControlsState.displayVideoToggle) {
|
if (callControlsState.displayVideoToggle) {
|
||||||
ToggleVideoButton(
|
CallScreenTooltipBox(
|
||||||
isVideoEnabled = callControlsState.isVideoEnabled && hasCameraPermission,
|
text = stringResource(R.string.WebRtcCallActivity__tap_here_to_turn_on_your_video),
|
||||||
onChange = callControlsCallback::onVideoToggleClick
|
displayTooltip = displayVideoTooltip,
|
||||||
)
|
onTooltipDismissed = callControlsCallback::onVideoTooltipDismissed
|
||||||
|
) {
|
||||||
|
ToggleVideoButton(
|
||||||
|
isVideoEnabled = callControlsState.isVideoEnabled && hasCameraPermission,
|
||||||
|
onChange = callControlsCallback::onVideoToggleClick
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasRecordAudioPermission = ContextCompat.checkSelfPermission(LocalContext.current, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
|
val hasRecordAudioPermission = ContextCompat.checkSelfPermission(LocalContext.current, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
|
||||||
|
@ -162,6 +169,7 @@ fun CallControlsPreview() {
|
||||||
startCallButtonText = R.string.WebRtcCallView__start_call,
|
startCallButtonText = R.string.WebRtcCallView__start_call,
|
||||||
displayEndCallButton = true
|
displayEndCallButton = true
|
||||||
),
|
),
|
||||||
|
displayVideoTooltip = false,
|
||||||
callControlsCallback = CallControlsCallback.Empty
|
callControlsCallback = CallControlsCallback.Empty
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -179,6 +187,7 @@ interface CallControlsCallback {
|
||||||
fun onAdditionalActionsClick()
|
fun onAdditionalActionsClick()
|
||||||
fun onStartCallClick(isVideoCall: Boolean)
|
fun onStartCallClick(isVideoCall: Boolean)
|
||||||
fun onEndCallClick()
|
fun onEndCallClick()
|
||||||
|
fun onVideoTooltipDismissed()
|
||||||
|
|
||||||
object Empty : CallControlsCallback {
|
object Empty : CallControlsCallback {
|
||||||
override fun onAudioDeviceSheetDisplayChanged(displayed: Boolean) = Unit
|
override fun onAudioDeviceSheetDisplayChanged(displayed: Boolean) = Unit
|
||||||
|
@ -189,6 +198,7 @@ interface CallControlsCallback {
|
||||||
override fun onAdditionalActionsClick() = Unit
|
override fun onAdditionalActionsClick() = Unit
|
||||||
override fun onStartCallClick(isVideoCall: Boolean) = Unit
|
override fun onStartCallClick(isVideoCall: Boolean) = Unit
|
||||||
override fun onEndCallClick() = Unit
|
override fun onEndCallClick() = Unit
|
||||||
|
override fun onVideoTooltipDismissed() = Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -154,6 +154,7 @@ fun CallScreen(
|
||||||
CallControls(
|
CallControls(
|
||||||
callControlsState = callControlsState,
|
callControlsState = callControlsState,
|
||||||
callControlsCallback = callControlsCallback,
|
callControlsCallback = callControlsCallback,
|
||||||
|
displayVideoTooltip = callScreenState.displayVideoTooltip,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.alpha(callControlsAlpha)
|
.alpha(callControlsAlpha)
|
||||||
|
|
|
@ -25,7 +25,11 @@ data class CallScreenState(
|
||||||
val hangup: Hangup? = null,
|
val hangup: Hangup? = null,
|
||||||
val callControlsChange: CallControlsChange? = null,
|
val callControlsChange: CallControlsChange? = null,
|
||||||
val callStatus: CallString? = null,
|
val callStatus: CallString? = null,
|
||||||
val isDisplayingAudioToggleSheet: Boolean = false
|
val isDisplayingAudioToggleSheet: Boolean = false,
|
||||||
|
val displaySwitchCameraTooltip: Boolean = false,
|
||||||
|
val displayVideoTooltip: Boolean = false,
|
||||||
|
val displaySwipeToSpeakerHint: Boolean = false,
|
||||||
|
val displayWifiToCellularPopup: Boolean = false
|
||||||
) {
|
) {
|
||||||
data class Hangup(
|
data class Hangup(
|
||||||
val hangupMessageType: HangupMessage.Type,
|
val hangupMessageType: HangupMessage.Type,
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.components.webrtc.v2
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.PlainTooltip
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TooltipBox
|
||||||
|
import androidx.compose.material3.TooltipDefaults
|
||||||
|
import androidx.compose.material3.rememberTooltipState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.colorResource
|
||||||
|
import kotlinx.coroutines.flow.drop
|
||||||
|
import org.signal.core.ui.DarkPreview
|
||||||
|
import org.signal.core.ui.Previews
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tooltip box appropriately styled for the call screen.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun CallScreenTooltipBox(
|
||||||
|
text: String,
|
||||||
|
displayTooltip: Boolean,
|
||||||
|
onTooltipDismissed: () -> Unit = {},
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val state = rememberTooltipState()
|
||||||
|
|
||||||
|
TooltipBox(
|
||||||
|
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||||
|
state = state,
|
||||||
|
tooltip = {
|
||||||
|
PlainTooltip(
|
||||||
|
caretSize = TooltipDefaults.caretSize,
|
||||||
|
shape = TooltipDefaults.plainTooltipContainerShape,
|
||||||
|
containerColor = colorResource(R.color.signal_light_colorPrimary),
|
||||||
|
contentColor = colorResource(R.color.signal_light_colorOnPrimary)
|
||||||
|
) {
|
||||||
|
Text(text = text)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(displayTooltip) {
|
||||||
|
if (displayTooltip) {
|
||||||
|
state.show()
|
||||||
|
} else {
|
||||||
|
state.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(state) {
|
||||||
|
snapshotFlow { state.isVisible }
|
||||||
|
.drop(1)
|
||||||
|
.collect { onTooltipDismissed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DarkPreview
|
||||||
|
@Composable
|
||||||
|
fun SwitchCameraTooltipBoxPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
CallScreenTooltipBox(
|
||||||
|
text = "Test Tooltip",
|
||||||
|
displayTooltip = true
|
||||||
|
) {
|
||||||
|
Text(text = "Test Content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioDevice
|
||||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput
|
||||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel
|
||||||
import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoViewModel
|
import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoViewModel
|
||||||
|
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||||
import org.thoughtcrime.securesms.events.WebRtcViewModel
|
import org.thoughtcrime.securesms.events.WebRtcViewModel
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
@ -144,17 +145,30 @@ class CallViewModel(
|
||||||
internalDialog.update { CallScreenDialogType.NONE }
|
internalDialog.update { CallScreenDialogType.NONE }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onVideoTooltipDismissed() {
|
||||||
|
webRtcCallViewModel.onDismissedVideoTooltip()
|
||||||
|
internalCallScreenState.update { it.copy(displayVideoTooltip = false) }
|
||||||
|
}
|
||||||
|
|
||||||
fun onCallEvent(event: CallEvent) {
|
fun onCallEvent(event: CallEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
CallEvent.DismissSwitchCameraTooltip -> Unit // TODO
|
CallEvent.DismissSwitchCameraTooltip -> internalCallScreenState.update { it.copy(displaySwitchCameraTooltip = false) }
|
||||||
CallEvent.DismissVideoTooltip -> Unit // TODO
|
CallEvent.DismissVideoTooltip -> internalCallScreenState.update { it.copy(displayVideoTooltip = false) }
|
||||||
is CallEvent.ShowGroupCallSafetyNumberChange -> Unit // TODO
|
is CallEvent.ShowGroupCallSafetyNumberChange -> {
|
||||||
CallEvent.ShowSwipeToSpeakerHint -> Unit // TODO
|
viewModelScope.launch {
|
||||||
CallEvent.ShowSwitchCameraTooltip -> Unit // TODO
|
internalCallActions.emit(Action.ShowGroupCallSafetyNumberChangeDialog(event.identityRecords))
|
||||||
CallEvent.ShowVideoTooltip -> Unit // TODO
|
}
|
||||||
CallEvent.ShowWifiToCellularPopup -> Unit // TODO
|
}
|
||||||
|
CallEvent.ShowSwipeToSpeakerHint -> internalCallScreenState.update { it.copy(displaySwipeToSpeakerHint = true) }
|
||||||
|
CallEvent.ShowSwitchCameraTooltip -> internalCallScreenState.update { it.copy(displaySwitchCameraTooltip = true) }
|
||||||
|
CallEvent.ShowVideoTooltip -> internalCallScreenState.update { it.copy(displayVideoTooltip = true) }
|
||||||
|
CallEvent.ShowWifiToCellularPopup -> internalCallScreenState.update { it.copy(displayWifiToCellularPopup = true) }
|
||||||
is CallEvent.StartCall -> startCall(event.isVideoCall)
|
is CallEvent.StartCall -> startCall(event.isVideoCall)
|
||||||
CallEvent.SwitchToSpeaker -> Unit // TODO
|
CallEvent.SwitchToSpeaker -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
internalCallActions.emit(Action.SwitchToSpeaker)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,11 +420,22 @@ class CallViewModel(
|
||||||
/**
|
/**
|
||||||
* Actions that require activity-level context (for example, to request permissions.)
|
* Actions that require activity-level context (for example, to request permissions.)
|
||||||
*/
|
*/
|
||||||
enum class Action {
|
sealed interface Action {
|
||||||
/**
|
/**
|
||||||
* Tries to enable local video via the normal toggle callback. Should display permissions
|
* Tries to enable local video via the normal toggle callback. Should display permissions
|
||||||
* dialogs as necessary.
|
* dialogs as necessary.
|
||||||
*/
|
*/
|
||||||
EnableVideo
|
data object EnableVideo : Action
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the safety number change dialog for the given untrusted identities. Since this dialog
|
||||||
|
* is not in compose-land, we delegate this as an action instead of embedding it in the screen state.
|
||||||
|
*/
|
||||||
|
data class ShowGroupCallSafetyNumberChangeDialog(val untrustedIdentities: List<IdentityRecord>) : Action
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately switch the user to speaker view
|
||||||
|
*/
|
||||||
|
data object SwitchToSpeaker : Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,10 @@ wire {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.runKtlintCheckOverMainSourceSet {
|
||||||
|
dependsOn(":core-util-jvm:generateMainProtos")
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.kotlin.reflect)
|
implementation(libs.kotlin.reflect)
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
|
Loading…
Add table
Reference in a new issue