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.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper
|
||||
import org.thoughtcrime.securesms.util.VibrateUtil
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
|
@ -101,6 +102,8 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
|||
viewModel.callActions.collect {
|
||||
when (it) {
|
||||
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()
|
||||
}
|
||||
|
||||
override fun onVideoTooltipDismissed() {
|
||||
viewModel.onVideoTooltipDismissed()
|
||||
}
|
||||
|
||||
private fun observeCallEvents() {
|
||||
webRtcCallViewModel.events.observe(this) { event ->
|
||||
viewModel.onCallEvent(event)
|
||||
|
|
|
@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.util.RemoteConfig
|
|||
*/
|
||||
@Composable
|
||||
fun CallControls(
|
||||
displayVideoTooltip: Boolean,
|
||||
callControlsState: CallControlsState,
|
||||
callControlsCallback: CallControlsCallback,
|
||||
modifier: Modifier = Modifier
|
||||
|
@ -96,10 +97,16 @@ fun CallControls(
|
|||
|
||||
val hasCameraPermission = ContextCompat.checkSelfPermission(LocalContext.current, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
|
||||
if (callControlsState.displayVideoToggle) {
|
||||
ToggleVideoButton(
|
||||
isVideoEnabled = callControlsState.isVideoEnabled && hasCameraPermission,
|
||||
onChange = callControlsCallback::onVideoToggleClick
|
||||
)
|
||||
CallScreenTooltipBox(
|
||||
text = stringResource(R.string.WebRtcCallActivity__tap_here_to_turn_on_your_video),
|
||||
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
|
||||
|
@ -162,6 +169,7 @@ fun CallControlsPreview() {
|
|||
startCallButtonText = R.string.WebRtcCallView__start_call,
|
||||
displayEndCallButton = true
|
||||
),
|
||||
displayVideoTooltip = false,
|
||||
callControlsCallback = CallControlsCallback.Empty
|
||||
)
|
||||
}
|
||||
|
@ -179,6 +187,7 @@ interface CallControlsCallback {
|
|||
fun onAdditionalActionsClick()
|
||||
fun onStartCallClick(isVideoCall: Boolean)
|
||||
fun onEndCallClick()
|
||||
fun onVideoTooltipDismissed()
|
||||
|
||||
object Empty : CallControlsCallback {
|
||||
override fun onAudioDeviceSheetDisplayChanged(displayed: Boolean) = Unit
|
||||
|
@ -189,6 +198,7 @@ interface CallControlsCallback {
|
|||
override fun onAdditionalActionsClick() = Unit
|
||||
override fun onStartCallClick(isVideoCall: Boolean) = Unit
|
||||
override fun onEndCallClick() = Unit
|
||||
override fun onVideoTooltipDismissed() = Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -154,6 +154,7 @@ fun CallScreen(
|
|||
CallControls(
|
||||
callControlsState = callControlsState,
|
||||
callControlsCallback = callControlsCallback,
|
||||
displayVideoTooltip = callScreenState.displayVideoTooltip,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.alpha(callControlsAlpha)
|
||||
|
|
|
@ -25,7 +25,11 @@ data class CallScreenState(
|
|||
val hangup: Hangup? = null,
|
||||
val callControlsChange: CallControlsChange? = 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(
|
||||
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.WebRtcCallViewModel
|
||||
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.events.WebRtcViewModel
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
@ -144,17 +145,30 @@ class CallViewModel(
|
|||
internalDialog.update { CallScreenDialogType.NONE }
|
||||
}
|
||||
|
||||
fun onVideoTooltipDismissed() {
|
||||
webRtcCallViewModel.onDismissedVideoTooltip()
|
||||
internalCallScreenState.update { it.copy(displayVideoTooltip = false) }
|
||||
}
|
||||
|
||||
fun onCallEvent(event: CallEvent) {
|
||||
when (event) {
|
||||
CallEvent.DismissSwitchCameraTooltip -> Unit // TODO
|
||||
CallEvent.DismissVideoTooltip -> Unit // TODO
|
||||
is CallEvent.ShowGroupCallSafetyNumberChange -> Unit // TODO
|
||||
CallEvent.ShowSwipeToSpeakerHint -> Unit // TODO
|
||||
CallEvent.ShowSwitchCameraTooltip -> Unit // TODO
|
||||
CallEvent.ShowVideoTooltip -> Unit // TODO
|
||||
CallEvent.ShowWifiToCellularPopup -> Unit // TODO
|
||||
CallEvent.DismissSwitchCameraTooltip -> internalCallScreenState.update { it.copy(displaySwitchCameraTooltip = false) }
|
||||
CallEvent.DismissVideoTooltip -> internalCallScreenState.update { it.copy(displayVideoTooltip = false) }
|
||||
is CallEvent.ShowGroupCallSafetyNumberChange -> {
|
||||
viewModelScope.launch {
|
||||
internalCallActions.emit(Action.ShowGroupCallSafetyNumberChangeDialog(event.identityRecords))
|
||||
}
|
||||
}
|
||||
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)
|
||||
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.)
|
||||
*/
|
||||
enum class Action {
|
||||
sealed interface Action {
|
||||
/**
|
||||
* Tries to enable local video via the normal toggle callback. Should display permissions
|
||||
* 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 {
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
|
|
Loading…
Add table
Reference in a new issue