[SES-1652] Swap video views in calls (#1533)

* WIP: swap video views

* feat: swap video views

* minor fixes

* minor fix

* minor fix

* update libsession-util

* Revert "update libsession-util"

This reverts commit 0d386e706e78d86147728cccb80636d920006d98.

* reverse updating libsession-util

* update libsession-util

* Tweaking colors to match designs

* More theme fixes

* WebRTC rework

Only using two sinks and swapping between them
Reworked the device rotation logic as it didn't work well with pitch ( you could tip the device front to back and the rotation went out of whack, so had to resort to more robust calculation for the device orientation.
Had to use a deprecated sensor setting but it's the only one I could use that works.

* Video management logic update

Rounded corners for floating inset
Proper handling of video scaling based on video proportions
Proper handling of mirroring logic for floating/fullscreen videos depending on whether they are the user or the remote video and whether the camera is front facing or not

* Showing floating video inset only when there is at least one video stream active, hiding it when both are inactive

* Rotating controls on rotation

* Clean up

* Review feedback

* OrientationManager

The new OrientationManager encapsulate the orientation logic and sends out a mutable state flow

* PR feedback

---------

Co-authored-by: Ryan Zhao <ryanzhaors@qq.com>
Co-authored-by: Ryan ZHAO <>
This commit is contained in:
ThomasSession 2024-07-11 09:44:17 +10:00 committed by GitHub
parent 2e3acd902f
commit b510b064cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 434 additions and 199 deletions

View File

@ -0,0 +1,87 @@
package org.thoughtcrime.securesms.calls
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.provider.Settings
import androidx.core.content.ContextCompat.getSystemService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity.SENSOR_SERVICE
import org.thoughtcrime.securesms.webrtc.Orientation
import kotlin.math.asin
class OrientationManager(private val context: Context): SensorEventListener {
private var sensorManager: SensorManager? = null
private var rotationVectorSensor: Sensor? = null
private val _orientation = MutableStateFlow(Orientation.UNKNOWN)
val orientation: StateFlow<Orientation> = _orientation
fun startOrientationListener(){
// create the sensor manager if it's still null
if(sensorManager == null) {
sensorManager = context.getSystemService(SENSOR_SERVICE) as SensorManager
}
if(rotationVectorSensor == null) {
rotationVectorSensor = sensorManager!!.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
}
sensorManager?.registerListener(this, rotationVectorSensor, SensorManager.SENSOR_DELAY_UI)
}
fun stopOrientationListener(){
sensorManager?.unregisterListener(this)
}
fun destroy(){
stopOrientationListener()
sensorManager = null
rotationVectorSensor = null
_orientation.value = Orientation.UNKNOWN
}
override fun onSensorChanged(event: SensorEvent) {
if (event.sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
// if auto-rotate is off, bail and send UNKNOWN
if (!isAutoRotateOn()) {
_orientation.value = Orientation.UNKNOWN
return
}
// Get the quaternion from the rotation vector sensor
val quaternion = FloatArray(4)
SensorManager.getQuaternionFromVector(quaternion, event.values)
// Calculate Euler angles from the quaternion
val pitch = asin(2.0 * (quaternion[0] * quaternion[2] - quaternion[3] * quaternion[1]))
// Convert radians to degrees
val pitchDegrees = Math.toDegrees(pitch).toFloat()
// Determine the device's orientation based on the pitch and roll values
val currentOrientation = when {
pitchDegrees > 45 -> Orientation.LANDSCAPE
pitchDegrees < -45 -> Orientation.REVERSED_LANDSCAPE
else -> Orientation.PORTRAIT
}
if (currentOrientation != _orientation.value) {
_orientation.value = currentOrientation
}
}
}
//Function to check if Android System Auto-rotate is on or off
private fun isAutoRotateOn(): Boolean {
return Settings.System.getInt(
context.contentResolver,
Settings.System.ACCELEROMETER_ROTATION, 0
) == 1
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
}

View File

@ -5,11 +5,17 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Outline
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorManager
import android.media.AudioManager
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.MenuItem
import android.view.OrientationEventListener
import android.view.View
import android.view.ViewOutlineProvider
import android.view.WindowManager
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
@ -21,7 +27,6 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import android.provider.Settings
import kotlinx.coroutines.launch
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityWebrtcBinding
@ -43,8 +48,10 @@ import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_OUTGOING
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_PRE_INIT
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_RECONNECTING
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_RINGING
import org.thoughtcrime.securesms.webrtc.Orientation
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.EARPIECE
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.SPEAKER_PHONE
import kotlin.math.asin
@AndroidEntryPoint
class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
@ -71,16 +78,13 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
}
private var hangupReceiver: BroadcastReceiver? = null
private val rotationListener by lazy {
object : OrientationEventListener(this) {
override fun onOrientationChanged(orientation: Int) {
if ((orientation + 15) % 90 < 30) {
viewModel.deviceRotation = orientation
// updateControlsRotation(orientation.quadrantRotation() * -1)
}
}
}
}
/**
* We need to track the device's orientation so we can calculate whether or not to rotate the video streams
* This works a lot better than using `OrientationEventListener > onOrientationChanged'
* which gives us a rotation angle that doesn't take into account pitch vs roll, so tipping the device from front to back would
* trigger the video rotation logic, while we really only want it when the device is in portrait or landscape.
*/
private var orientationManager = OrientationManager(this)
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
@ -102,13 +106,6 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
// Only enable auto-rotate if system auto-rotate is enabled
if (isAutoRotateOn()) {
rotationListener.enable()
} else {
rotationListener.disable()
}
binding = ActivityWebrtcBinding.inflate(layoutInflater)
setContentView(binding.root)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
@ -136,6 +133,10 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(false)
}
binding.floatingRendererContainer.setOnClickListener {
viewModel.swapVideos()
}
binding.microphoneButton.setOnClickListener {
val audioEnabledIntent =
WebRtcCallService.microphoneIntent(this, !viewModel.microphoneEnabled)
@ -174,7 +175,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.onAllGranted {
val intent = WebRtcCallService.cameraEnabled(this, !viewModel.videoEnabled)
val intent = WebRtcCallService.cameraEnabled(this, !viewModel.videoState.value.userVideoEnabled)
startService(intent)
}
.execute()
@ -191,14 +192,44 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
onBackPressed()
}
lifecycleScope.launch {
orientationManager.orientation.collect { orientation ->
viewModel.deviceOrientation = orientation
updateControlsRotation()
}
}
clipFloatingInsets()
}
//Function to check if Android System Auto-rotate is on or off
private fun isAutoRotateOn(): Boolean {
return Settings.System.getInt(
contentResolver,
Settings.System.ACCELEROMETER_ROTATION, 0
) == 1
/**
* Makes sure the floating video inset has clipped rounded corners, included with the video stream itself
*/
private fun clipFloatingInsets() {
// clip the video inset with rounded corners
val videoInsetProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
// all corners
outline.setRoundRect(
0, 0, view.width, view.height,
resources.getDimensionPixelSize(R.dimen.video_inset_radius).toFloat()
)
}
}
binding.floatingRendererContainer.outlineProvider = videoInsetProvider
binding.floatingRendererContainer.clipToOutline = true
}
override fun onResume() {
super.onResume()
orientationManager.startOrientationListener()
}
override fun onPause() {
super.onPause()
orientationManager.stopOrientationListener()
}
override fun onDestroy() {
@ -206,7 +237,8 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
hangupReceiver?.let { receiver ->
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
}
rotationListener.disable()
orientationManager.destroy()
}
private fun answerCall() {
@ -214,15 +246,31 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
ContextCompat.startForegroundService(this, answerIntent)
}
private fun updateControlsRotation(newRotation: Int) {
private fun updateControlsRotation() {
with (binding) {
val rotation = newRotation.toFloat()
remoteRecipient.rotation = rotation
speakerPhoneButton.rotation = rotation
microphoneButton.rotation = rotation
enableCameraButton.rotation = rotation
switchCameraButton.rotation = rotation
endCallButton.rotation = rotation
val rotation = when(viewModel.deviceOrientation){
Orientation.LANDSCAPE -> -90f
Orientation.REVERSED_LANDSCAPE -> 90f
else -> 0f
}
remoteRecipient.animate().cancel()
remoteRecipient.animate().rotation(rotation).start()
speakerPhoneButton.animate().cancel()
speakerPhoneButton.animate().rotation(rotation).start()
microphoneButton.animate().cancel()
microphoneButton.animate().rotation(rotation).start()
enableCameraButton.animate().cancel()
enableCameraButton.animate().rotation(rotation).start()
switchCameraButton.animate().cancel()
switchCameraButton.animate().rotation(rotation).start()
endCallButton.animate().cancel()
endCallButton.animate().rotation(rotation).start()
}
}
@ -346,34 +394,43 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
}
}
// handle video state
launch {
viewModel.localVideoEnabledState.collect { isEnabled ->
binding.localRenderer.removeAllViews()
if (isEnabled) {
viewModel.localRenderer?.let { surfaceView ->
surfaceView.setZOrderOnTop(true)
viewModel.videoState.collect { state ->
binding.floatingRenderer.removeAllViews()
binding.fullscreenRenderer.removeAllViews()
// Mirror the video preview of the person making the call to prevent disorienting them
surfaceView.setMirror(true)
// the floating video inset (empty or not) should be shown
// the moment we have either of the video streams
val showFloatingContainer = state.userVideoEnabled || state.remoteVideoEnabled
binding.floatingRendererContainer.isVisible = showFloatingContainer
binding.swapViewIcon.isVisible = showFloatingContainer
binding.localRenderer.addView(surfaceView)
// handle fullscreen video window
if(state.showFullscreenVideo()){
viewModel.fullscreenRenderer?.let { surfaceView ->
binding.fullscreenRenderer.addView(surfaceView)
binding.fullscreenRenderer.isVisible = true
binding.remoteRecipient.isVisible = false
}
} else {
binding.fullscreenRenderer.isVisible = false
binding.remoteRecipient.isVisible = true
}
binding.localRenderer.isVisible = isEnabled
binding.enableCameraButton.isSelected = isEnabled
}
}
launch {
viewModel.remoteVideoEnabledState.collect { isEnabled ->
binding.remoteRenderer.removeAllViews()
if (isEnabled) {
viewModel.remoteRenderer?.let { surfaceView ->
binding.remoteRenderer.addView(surfaceView)
// handle floating video window
if(state.showFloatingVideo()){
viewModel.floatingRenderer?.let { surfaceView ->
binding.floatingRenderer.addView(surfaceView)
binding.floatingRenderer.isVisible = true
binding.swapViewIcon.bringToFront()
}
} else {
binding.floatingRenderer.isVisible = false
}
binding.remoteRenderer.isVisible = isEnabled
binding.remoteRecipient.isVisible = !isEnabled
// handle buttons
binding.enableCameraButton.isSelected = state.userVideoEnabled
}
}
}
@ -388,7 +445,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
override fun onStop() {
super.onStop()
uiJob?.cancel()
binding.remoteRenderer.removeAllViews()
binding.localRenderer.removeAllViews()
binding.fullscreenRenderer.removeAllViews()
binding.floatingRenderer.removeAllViews()
}
}

View File

@ -81,6 +81,7 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
const val EXTRA_RECIPIENT_ADDRESS = "RECIPIENT_ID"
const val EXTRA_ENABLED = "ENABLED"
const val EXTRA_AUDIO_COMMAND = "AUDIO_COMMAND"
const val EXTRA_SWAPPED = "is_video_swapped"
const val EXTRA_MUTE = "mute_value"
const val EXTRA_AVAILABLE = "enabled_value"
const val EXTRA_REMOTE_DESCRIPTION = "remote_description"

View File

@ -34,7 +34,7 @@ fun AppTheme(
) {
val extraColors = LocalContext.current.run {
ExtraColors(
settingsBackground = getColorFromTheme(R.attr.colorSettingsBackground),
settingsBackground = getColorFromTheme(R.attr.backgroundSecondary),
prominentButtonColor = getColorFromTheme(R.attr.prominentButtonColor),
)
}

View File

@ -6,11 +6,14 @@ import android.telephony.TelephonyManager
import androidx.core.content.ContextCompat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind
@ -51,6 +54,7 @@ import org.webrtc.MediaStream
import org.webrtc.PeerConnection
import org.webrtc.PeerConnection.IceConnectionState
import org.webrtc.PeerConnectionFactory
import org.webrtc.RendererCommon
import org.webrtc.RtpReceiver
import org.webrtc.SessionDescription
import org.webrtc.SurfaceViewRenderer
@ -105,10 +109,15 @@ class CallManager(
private val _audioEvents = MutableStateFlow(AudioEnabled(false))
val audioEvents = _audioEvents.asSharedFlow()
private val _videoEvents = MutableStateFlow(VideoEnabled(false))
val videoEvents = _videoEvents.asSharedFlow()
private val _remoteVideoEvents = MutableStateFlow(VideoEnabled(false))
val remoteVideoEvents = _remoteVideoEvents.asSharedFlow()
private val _videoState: MutableStateFlow<VideoState> = MutableStateFlow(
VideoState(
swapped = false,
userVideoEnabled = false,
remoteVideoEnabled = false
)
)
val videoState = _videoState
private val stateProcessor = StateProcessor(CallState.Idle)
@ -151,9 +160,9 @@ class CallManager(
private val outgoingIceDebouncer = Debouncer(200L)
var localRenderer: SurfaceViewRenderer? = null
var floatingRenderer: SurfaceViewRenderer? = null
var remoteRotationSink: RemoteRotationVideoProxySink? = null
var remoteRenderer: SurfaceViewRenderer? = null
var fullscreenRenderer: SurfaceViewRenderer? = null
private var peerConnectionFactory: PeerConnectionFactory? = null
fun clearPendingIceUpdates() {
@ -216,20 +225,18 @@ class CallManager(
Util.runOnMainSync {
val base = EglBase.create()
eglBase = base
localRenderer = SurfaceViewRenderer(context).apply {
// setScalingType(SCALE_ASPECT_FIT)
}
floatingRenderer = SurfaceViewRenderer(context)
floatingRenderer?.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
fullscreenRenderer = SurfaceViewRenderer(context)
fullscreenRenderer?.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
remoteRenderer = SurfaceViewRenderer(context).apply {
// setScalingType(SCALE_ASPECT_FIT)
}
remoteRotationSink = RemoteRotationVideoProxySink()
localRenderer?.init(base.eglBaseContext, null)
localRenderer?.setMirror(localCameraState.activeDirection == CameraState.Direction.FRONT)
remoteRenderer?.init(base.eglBaseContext, null)
remoteRotationSink!!.setSink(remoteRenderer!!)
floatingRenderer?.init(base.eglBaseContext, null)
fullscreenRenderer?.init(base.eglBaseContext, null)
remoteRotationSink!!.setSink(fullscreenRenderer!!)
val encoderFactory = DefaultVideoEncoderFactory(base.eglBaseContext, true, true)
val decoderFactory = DefaultVideoDecoderFactory(base.eglBaseContext)
@ -363,7 +370,8 @@ class CallManager(
val byteArray = ByteArray(buffer.data.remaining()) { buffer.data[it] }
val json = Json.parseToJsonElement(byteArray.decodeToString()) as JsonObject
if (json.containsKey("video")) {
_remoteVideoEvents.value = VideoEnabled((json["video"] as JsonPrimitive).boolean)
_videoState.update { it.copy(remoteVideoEnabled = json["video"]?.jsonPrimitive?.boolean ?: false) }
handleMirroring()
} else if (json.containsKey("hangup")) {
peerConnectionObservers.forEach(WebRtcListener::onHangup)
}
@ -383,13 +391,13 @@ class CallManager(
peerConnection?.dispose()
peerConnection = null
localRenderer?.release()
floatingRenderer?.release()
remoteRotationSink?.release()
remoteRenderer?.release()
fullscreenRenderer?.release()
eglBase?.release()
localRenderer = null
remoteRenderer = null
floatingRenderer = null
fullscreenRenderer = null
eglBase = null
localCameraState = CameraState.UNKNOWN
@ -399,8 +407,11 @@ class CallManager(
pendingOffer = null
callStartTime = -1
_audioEvents.value = AudioEnabled(false)
_videoEvents.value = VideoEnabled(false)
_remoteVideoEvents.value = VideoEnabled(false)
_videoState.value = VideoState(
swapped = false,
userVideoEnabled = false,
remoteVideoEnabled = false
)
pendingOutgoingIceUpdates.clear()
pendingIncomingIceUpdates.clear()
}
@ -411,7 +422,7 @@ class CallManager(
// If the camera we've switched to is the front one then mirror it to match what someone
// would see when looking in the mirror rather than the left<-->right flipped version.
localRenderer?.setMirror(localCameraState.activeDirection == CameraState.Direction.FRONT)
handleMirroring()
}
fun onPreOffer(callId: UUID, recipient: Recipient, onSuccess: () -> Unit) {
@ -469,7 +480,7 @@ class CallManager(
val recipient = recipient ?: return Promise.ofFail(NullPointerException("recipient is null"))
val offer = pendingOffer ?: return Promise.ofFail(NullPointerException("pendingOffer is null"))
val factory = peerConnectionFactory ?: return Promise.ofFail(NullPointerException("peerConnectionFactory is null"))
val local = localRenderer ?: return Promise.ofFail(NullPointerException("localRenderer is null"))
val local = floatingRenderer ?: return Promise.ofFail(NullPointerException("localRenderer is null"))
val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null"))
val connection = PeerConnectionWrapper(
context,
@ -515,7 +526,7 @@ class CallManager(
?: return Promise.ofFail(NullPointerException("recipient is null"))
val factory = peerConnectionFactory
?: return Promise.ofFail(NullPointerException("peerConnectionFactory is null"))
val local = localRenderer
val local = floatingRenderer
?: return Promise.ofFail(NullPointerException("localRenderer is null"))
val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null"))
@ -609,13 +620,58 @@ class CallManager(
}
}
fun swapVideos() {
// update the state
_videoState.update { it.copy(swapped = !it.swapped) }
handleMirroring()
if (_videoState.value.swapped) {
peerConnection?.rotationVideoSink?.setSink(fullscreenRenderer)
floatingRenderer?.let{remoteRotationSink?.setSink(it) }
} else {
peerConnection?.rotationVideoSink?.apply {
setSink(floatingRenderer)
}
fullscreenRenderer?.let{ remoteRotationSink?.setSink(it) }
}
}
fun handleSetMuteAudio(muted: Boolean) {
_audioEvents.value = AudioEnabled(!muted)
peerConnection?.setAudioEnabled(!muted)
}
/**
* Returns the renderer currently showing the user's video, not the contact's
*/
private fun getUserRenderer() = if(_videoState.value.swapped) fullscreenRenderer else floatingRenderer
/**
* Returns the renderer currently showing the contact's video, not the user's
*/
private fun getRemoteRenderer() = if(_videoState.value.swapped) floatingRenderer else fullscreenRenderer
/**
* Makes sure the user's renderer applies mirroring if necessary
*/
private fun handleMirroring() {
val videoState = _videoState.value
// if we have user video and the camera is front facing, make sure to mirror stream
if(videoState.userVideoEnabled) {
getUserRenderer()?.setMirror(isCameraFrontFacing())
}
// the remote video is never mirrored
if(videoState.remoteVideoEnabled){
getRemoteRenderer()?.setMirror(false)
}
}
fun handleSetMuteVideo(muted: Boolean, lockManager: LockManager) {
_videoEvents.value = VideoEnabled(!muted)
_videoState.update { it.copy(userVideoEnabled = !muted) }
handleMirroring()
val connection = peerConnection ?: return
connection.setVideoEnabled(!muted)
dataChannel?.let { channel ->
@ -651,9 +707,18 @@ class CallManager(
}
}
fun setDeviceRotation(newRotation: Int) {
peerConnection?.setDeviceRotation(newRotation)
remoteRotationSink?.rotation = newRotation
fun setDeviceOrientation(orientation: Orientation) {
// set rotation to the video based on the device's orientation and the camera facing direction
val rotation = when (orientation) {
Orientation.PORTRAIT -> 0
Orientation.LANDSCAPE -> if (isCameraFrontFacing()) 90 else -90
Orientation.REVERSED_LANDSCAPE -> 270
else -> 0
}
// apply the rotation to the streams
peerConnection?.setDeviceRotation(rotation)
remoteRotationSink?.rotation = rotation
}
fun handleWiredHeadsetChanged(present: Boolean) {
@ -721,7 +786,7 @@ class CallManager(
connection.setCommunicationMode()
setAudioEnabled(true)
dataChannel?.let { channel ->
val toSend = if (!_videoEvents.value.isEnabled) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON
val toSend = if (_videoState.value.userVideoEnabled) VIDEO_ENABLED_JSON else VIDEO_DISABLED_JSON
val buffer = DataChannel.Buffer(ByteBuffer.wrap(toSend.toString().encodeToByteArray()), false)
channel.send(buffer)
}
@ -750,6 +815,8 @@ class CallManager(
fun isInitiator(): Boolean = peerConnection?.isInitiator() == true
fun isCameraFrontFacing() = localCameraState.activeDirection != CameraState.Direction.BACK
interface WebRtcListener: PeerConnection.Observer {
fun onHangup()
}

View File

@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.webrtc
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
@ -29,16 +31,11 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
UNTRUSTED_IDENTITY,
}
val localRenderer: SurfaceViewRenderer?
get() = callManager.localRenderer
val floatingRenderer: SurfaceViewRenderer?
get() = callManager.floatingRenderer
val remoteRenderer: SurfaceViewRenderer?
get() = callManager.remoteRenderer
private var _videoEnabled: Boolean = false
val videoEnabled: Boolean
get() = _videoEnabled
val fullscreenRenderer: SurfaceViewRenderer?
get() = callManager.fullscreenRenderer
private var _microphoneEnabled: Boolean = true
@ -59,18 +56,13 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
get() = callManager.audioEvents.map { it.isEnabled }
.onEach { _microphoneEnabled = it }
val localVideoEnabledState
get() = callManager.videoEvents
.map { it.isEnabled }
.onEach { _videoEnabled = it }
val videoState: StateFlow<VideoState>
get() = callManager.videoState
val remoteVideoEnabledState
get() = callManager.remoteVideoEvents.map { it.isEnabled }
var deviceRotation: Int = 0
var deviceOrientation: Orientation = Orientation.UNKNOWN
set(value) {
field = value
callManager.setDeviceRotation(value)
callManager.setDeviceOrientation(value)
}
val currentCallState
@ -85,4 +77,7 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
val callStartTime: Long
get() = callManager.callStartTime
fun swapVideos() {
callManager.swapVideos()
}
}

View File

@ -0,0 +1,8 @@
package org.thoughtcrime.securesms.webrtc
enum class Orientation {
PORTRAIT,
LANDSCAPE,
REVERSED_LANDSCAPE,
UNKNOWN
}

View File

@ -41,7 +41,7 @@ class PeerConnectionWrapper(private val context: Context,
private val mediaStream: MediaStream
private val videoSource: VideoSource?
private val videoTrack: VideoTrack?
private val rotationVideoSink = RotationVideoSink()
public val rotationVideoSink = RotationVideoSink()
val readyForIce
get() = peerConnection?.localDescription != null && peerConnection?.remoteDescription != null
@ -103,7 +103,7 @@ class PeerConnectionWrapper(private val context: Context,
context,
rotationVideoSink
)
rotationVideoSink.mirrored = newCamera.activeDirection == CameraState.Direction.FRONT
rotationVideoSink.setSink(localRenderer)
newVideoTrack.setEnabled(false)
mediaStream.addTrack(newVideoTrack)

View File

@ -0,0 +1,17 @@
package org.thoughtcrime.securesms.webrtc
data class VideoState (
val swapped: Boolean,
val userVideoEnabled: Boolean,
val remoteVideoEnabled: Boolean
){
fun showFloatingVideo(): Boolean {
return userVideoEnabled && !swapped ||
remoteVideoEnabled && swapped
}
fun showFullscreenVideo(): Boolean {
return userVideoEnabled && swapped ||
remoteVideoEnabled && !swapped
}
}

View File

@ -1,11 +0,0 @@
package org.thoughtcrime.securesms.webrtc.data
// get the video rotation from a specific rotation, locked into 90 degree
// chunks offset by 45 degrees
fun Int.quadrantRotation() = when (this % 360) {
in 315 .. 360,
in 0 until 45 -> 0
in 45 until 135 -> 90
in 135 until 225 -> 180
else -> 270
}

View File

@ -1,6 +1,6 @@
package org.thoughtcrime.securesms.webrtc.video
import org.thoughtcrime.securesms.webrtc.data.quadrantRotation
import org.webrtc.VideoFrame
import org.webrtc.VideoSink
@ -14,8 +14,7 @@ class RemoteRotationVideoProxySink: VideoSink {
val thisSink = targetSink ?: return
val thisFrame = frame ?: return
val quadrantRotation = rotation.quadrantRotation()
val modifiedRotation = thisFrame.rotation - quadrantRotation
val modifiedRotation = thisFrame.rotation - rotation
val newFrame = VideoFrame(thisFrame.buffer, modifiedRotation, thisFrame.timestampNs)
thisSink.onFrame(newFrame)

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.webrtc.video
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.webrtc.data.quadrantRotation
import org.webrtc.CapturerObserver
import org.webrtc.VideoFrame
import org.webrtc.VideoProcessor
@ -12,7 +11,6 @@ import java.util.concurrent.atomic.AtomicBoolean
class RotationVideoSink: CapturerObserver, VideoProcessor {
var rotation: Int = 0
var mirrored = false
private val capturing = AtomicBoolean(false)
private var capturerObserver = SoftReference<CapturerObserver>(null)
@ -31,13 +29,14 @@ class RotationVideoSink: CapturerObserver, VideoProcessor {
val observer = capturerObserver.get()
if (videoFrame == null || observer == null || !capturing.get()) return
val quadrantRotation = rotation.quadrantRotation()
val newFrame = VideoFrame(videoFrame.buffer, (videoFrame.rotation + quadrantRotation * if (mirrored && quadrantRotation in listOf(90,270)) -1 else 1) % 360, videoFrame.timestampNs)
val localFrame = VideoFrame(videoFrame.buffer, videoFrame.rotation * if (mirrored && quadrantRotation in listOf(90,270)) -1 else 1, videoFrame.timestampNs)
// cater for frame rotation so that the video is always facing up as we rotate pas a certain point
val newFrame = VideoFrame(videoFrame.buffer, videoFrame.rotation - rotation, videoFrame.timestampNs)
// the frame we are sending to our contact needs to cater for rotation
observer.onFrameCaptured(newFrame)
sink.get()?.onFrame(localFrame)
// the frame we see on the user's phone doesn't require changes
sink.get()?.onFrame(videoFrame)
}
override fun setSink(sink: VideoSink?) {

View File

@ -4,6 +4,6 @@
android:color="?android:colorControlHighlight">
<item>
<color android:color="?colorCellBackground" />
<color android:color="?colorPrimary" />
</item>
</ripple>

View File

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?dialog_background_color" />
<solid android:color="?backgroundSecondary" />
<corners
android:topLeftRadius="@dimen/dialog_corner_radius"

View File

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/dialog_background_color" />
<solid android:color="?backgroundSecondary" />
<corners android:radius="?dialogCornerRadius" />

View File

@ -6,6 +6,6 @@
android:insetBottom="16dp">
<shape android:shape="rectangle">
<corners android:radius="2dp" />
<solid android:color="?dialog_background_color" />
<solid android:color="?backgroundSecondary" />
</shape>
</inset>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M4,7.59l5,-5c0.78,-0.78 2.05,-0.78 2.83,0L20.24,11h-2.83L10.4,4L5.41,9H8v2H2V5h2V7.59zM20,19h2v-6h-6v2h2.59l-4.99,5l-7.01,-7H3.76l8.41,8.41c0.78,0.78 2.05,0.78 2.83,0l5,-5V19z"/>
</vector>

View File

@ -7,5 +7,5 @@
android:pathData="M19.907,7.674H19.907H4.54H4.54C4.317,7.674 4.095,7.719 3.888,7.806L3.888,7.806C3.681,7.893 3.491,8.023 3.334,8.189C3.176,8.355 3.054,8.554 2.978,8.775L3.922,9.097L2.978,8.775C2.903,8.996 2.877,9.231 2.904,9.465L2.904,9.465L2.904,9.469L4.555,23.412C4.555,23.413 4.555,23.413 4.555,23.414C4.603,23.823 4.807,24.189 5.111,24.447C5.415,24.705 5.798,24.84 6.187,24.84H6.188H18.26H18.26C18.649,24.84 19.032,24.705 19.336,24.447C19.64,24.189 19.844,23.823 19.892,23.414C19.892,23.413 19.892,23.413 19.892,23.412L21.543,9.469L21.544,9.465C21.57,9.231 21.544,8.996 21.469,8.775L21.469,8.775C21.393,8.554 21.271,8.355 21.113,8.189C20.956,8.023 20.766,7.893 20.559,7.806L20.17,8.728L20.559,7.806C20.352,7.719 20.13,7.674 19.907,7.674ZM21.412,1.84H3.031C2.045,1.84 1.149,2.609 1.149,3.674V5.828C1.149,6.893 2.045,7.662 3.031,7.662H21.412C22.398,7.662 23.294,6.893 23.294,5.828V3.674C23.294,2.609 22.398,1.84 21.412,1.84Z"
android:strokeWidth="2"
android:fillColor="#FF3A3A"
android:strokeColor="?colorPrimaryDark"/>
android:strokeColor="?backgroundSecondary"/>
</vector>

View File

@ -6,7 +6,7 @@
android:bottom="@dimen/small_spacing"
>
<shape android:shape="rectangle">
<solid android:color="?colorSettingsBackground"/>
<solid android:color="?backgroundSecondary"/>
<corners android:bottomLeftRadius="?preferenceCornerRadius"
android:bottomRightRadius="?preferenceCornerRadius"/>
</shape>

View File

@ -4,7 +4,7 @@
<item android:left="@dimen/medium_spacing"
android:right="@dimen/medium_spacing">
<shape android:shape="rectangle">
<solid android:color="?colorSettingsBackground"/>
<solid android:color="?backgroundSecondary"/>
</shape>
</item>
<item android:gravity="bottom"

View File

@ -6,7 +6,7 @@
android:right="@dimen/medium_spacing"
android:bottom="@dimen/small_spacing">
<shape android:shape="rectangle">
<solid android:color="?colorSettingsBackground"/>
<solid android:color="?backgroundSecondary"/>
<corners android:radius="?preferenceCornerRadius"/>
</shape>
</item>

View File

@ -2,7 +2,7 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="?colorSettingsBackground"/>
<solid android:color="?backgroundSecondary"/>
<corners android:radius="?preferenceCornerRadius"/>
</shape>
</item>

View File

@ -6,7 +6,7 @@
android:top="@dimen/small_spacing"
>
<shape android:shape="rectangle">
<solid android:color="?colorSettingsBackground"/>
<solid android:color="?backgroundSecondary"/>
<corners android:topLeftRadius="?preferenceCornerRadius"
android:topRightRadius="?preferenceCornerRadius"/>
</shape>

View File

@ -25,7 +25,7 @@
app:cardElevation="0dp"
app:cardCornerRadius="@dimen/dialog_corner_radius"
android:layout_marginHorizontal="@dimen/medium_spacing"
app:cardBackgroundColor="?colorSettingsBackground"
app:cardBackgroundColor="?backgroundSecondary"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
@ -317,7 +317,7 @@
<androidx.cardview.widget.CardView
app:cardElevation="0dp"
android:elevation="0dp"
app:cardBackgroundColor="?colorSettingsBackground"
app:cardBackgroundColor="?backgroundSecondary"
app:cardCornerRadius="@dimen/dialog_corner_radius"
android:layout_margin="@dimen/medium_spacing"
android:layout_marginBottom="@dimen/massive_spacing"

View File

@ -10,7 +10,7 @@
app:layout_constraintBottom_toTopOf="@+id/unblockButton"
app:cardCornerRadius="?preferenceCornerRadius"
app:cardElevation="0dp"
app:cardBackgroundColor="?colorSettingsBackground"
app:cardBackgroundColor="?backgroundSecondary"
android:layout_marginHorizontal="@dimen/medium_spacing"
android:layout_marginVertical="@dimen/large_spacing"
android:layout_width="match_parent"

View File

@ -8,7 +8,7 @@
xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
android:id="@+id/remote_parent"
android:id="@+id/fullscreen_renderer_container"
android:background="@color/black"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -18,17 +18,17 @@
app:layout_constraintTop_toTopOf="parent">
<FrameLayout
android:id="@+id/remote_renderer"
android:id="@+id/fullscreen_renderer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</FrameLayout>
<ImageView
android:id="@+id/remote_recipient"
app:layout_constraintStart_toStartOf="@id/remote_parent"
app:layout_constraintEnd_toEndOf="@id/remote_parent"
app:layout_constraintTop_toTopOf="@id/remote_parent"
app:layout_constraintBottom_toBottomOf="@id/remote_parent"
app:layout_constraintStart_toStartOf="@id/fullscreen_renderer_container"
app:layout_constraintEnd_toEndOf="@id/fullscreen_renderer_container"
app:layout_constraintTop_toTopOf="@id/fullscreen_renderer_container"
app:layout_constraintBottom_toBottomOf="@id/fullscreen_renderer_container"
app:layout_constraintVertical_bias="0.4"
android:layout_width="@dimen/extra_large_profile_picture_size"
android:layout_height="@dimen/extra_large_profile_picture_size"/>
@ -111,6 +111,7 @@
android:layout_height="wrap_content"/>
<FrameLayout
android:id="@+id/floating_renderer_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintDimensionRatio="h,9:16"
@ -118,12 +119,21 @@
android:layout_marginVertical="@dimen/massive_spacing"
app:layout_constraintWidth_percent="0.2"
android:layout_height="0dp"
android:layout_width="0dp">
android:layout_width="0dp"
android:background="?backgroundSecondary">
<ImageView
android:id="@+id/videocam_off_icon"
android:src="@drawable/ic_baseline_videocam_off_24"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
app:tint="?android:textColorPrimary"/>
<FrameLayout
android:elevation="8dp"
android:id="@+id/local_renderer"
android:id="@+id/floating_renderer"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<com.github.ybq.android.spinkit.SpinKitView
android:id="@+id/local_loading_view"
style="@style/SpinKitView.Large.ThreeBounce"
@ -133,8 +143,20 @@
android:layout_gravity="center"
tools:visibility="visible"
android:visibility="gone" />
</FrameLayout>
<ImageView
android:id="@+id/swap_view_icon"
android:src="@drawable/ic_baseline_screen_rotation_alt_24"
app:layout_constraintTop_toTopOf="@id/floating_renderer_container"
app:layout_constraintEnd_toEndOf="@id/floating_renderer_container"
app:tint="?android:textColorPrimary"
android:layout_marginTop="@dimen/very_small_spacing"
android:layout_marginEnd="@dimen/very_small_spacing"
android:layout_width="14dp"
android:layout_height="14dp"/>
<ImageView
android:id="@+id/endCallButton"
android:background="@drawable/circle_tintable"

View File

@ -10,7 +10,7 @@
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
app:chipStrokeWidth="1dp"
app:chipStrokeColor="?elementBorderColor"
app:chipBackgroundColor="?dialog_background_color"
app:chipBackgroundColor="?backgroundSecondary"
app:chipMinTouchTargetSize="0dp"
app:chipStartPadding="4dp"
tools:ignore="TouchTargetSizeCheck"

View File

@ -85,8 +85,7 @@
android:textColor="?unreadIndicatorTextColor"
android:textSize="@dimen/very_small_font_size"
android:textStyle="bold"
tools:text="8"
tools:textColor="?android:textColorPrimary" />
tools:text="8"/>
</RelativeLayout>
@ -115,8 +114,7 @@
android:textColor="?unreadIndicatorTextColor"
android:textSize="@dimen/very_small_font_size"
android:textStyle="bold"
android:text="@"
tools:textColor="?android:textColorPrimary" />
android:text="@" />
</RelativeLayout>

View File

@ -61,7 +61,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:background="?colorPrimaryDark"
android:background="?backgroundSecondary"
app:SpinKit_Color="?android:textColorPrimary"
android:visibility="gone"/>

View File

@ -29,6 +29,7 @@
<attr name="ic_visibility_off" format="reference" />
<attr name="accentColor" format="reference|color"/>
<attr name="backgroundSecondary" format="reference|color"/>
<attr name="prominentButtonColor" format="reference|color"/>
<attr name="elementBorderColor" format="reference|color"/>
<attr name="conversation_background" format="reference|color"/>
@ -56,8 +57,6 @@
<attr name="emoji_tab_strip_background" format="color" />
<attr name="emoji_tab_indicator" format="color" />
<attr name="emoji_tab_underline" format="color" />
<attr name="emoji_tab_seperator" format="color" />
<attr name="emoji_drawer_background" format="color" />
<attr name="emoji_text_color" format="color" />
@ -97,7 +96,6 @@
<attr name="dialog_info_icon" format="reference" />
<attr name="dialog_alert_icon" format="reference" />
<attr name="dialog_background_color" format="reference|color" />
<attr name="conversation_menu_background_color" format="reference|color" />
<attr name="conversation_menu_cell_color" format="reference|color"/>
<attr name="conversation_menu_border_color" format="reference|color"/>
@ -152,7 +150,6 @@
<attr name="default_background_start" format="color|reference"/>
<attr name="default_background_end" format="color|reference"/>
<attr name="colorCellBackground" format="color|reference" />
<attr name="colorSettingsBackground" format="color|reference" />
<attr name="colorDividerBackground" format="color|reference" />
<attr name="outlineButtonBorder" format="color|reference" />
<attr name="outlineButtonText" format="color|reference" />

View File

@ -43,8 +43,6 @@
<color name="gray27">#ffbbbbbb</color>
<color name="gray50">#ff808080</color>
<color name="gray65">#ff595959</color>
<color name="gray70">#ff4d4d4d</color>
<color name="gray78">#ff383838</color>
<color name="transparent_black_6">#0f000000</color>
<color name="transparent_black_15">#26000000</color>
@ -126,7 +124,7 @@
<color name="classic_accent">#31F196</color>
<color name="classic_dark_0">#111111</color>
<color name="classic_dark_0">#000000</color>
<color name="classic_dark_1">#1B1B1B</color>
<color name="classic_dark_2">#2D2D2D</color>
<color name="classic_dark_3">#414141</color>

View File

@ -32,6 +32,7 @@
<dimen name="fake_chat_view_height">250dp</dimen>
<dimen name="setting_button_height">64dp</dimen>
<dimen name="dialog_corner_radius">8dp</dimen>
<dimen name="video_inset_radius">11dp</dimen>
<dimen name="dialog_button_corner_radius">4dp</dimen>
<dimen name="pn_option_corner_radius">8dp</dimen>
<dimen name="path_status_view_size">8dp</dimen>

View File

@ -24,8 +24,7 @@
<style name="ThemeOverlay.Session.AlertDialog" parent="ThemeOverlay.AppCompat.Dialog.Alert">
<item name="android:windowBackground">@drawable/default_dialog_background</item>
<item name="android:colorBackground">?attr/dialog_background_color</item>
<item name="dialog_background_color">?colorPrimary</item>
<item name="android:colorBackground">?backgroundSecondary</item>
<item name="android:colorBackgroundFloating">?colorPrimary</item>
<item name="backgroundTint">?colorPrimary</item>
<item name="android:backgroundDimEnabled">true</item>
@ -288,7 +287,7 @@
</style>
<style name="PopupMenu.MessageRequests" parent="@style/Widget.AppCompat.PopupMenu">
<item name="android:background">?attr/colorSettingsBackground</item>
<item name="android:background">?backgroundSecondary</item>
</style>
</resources>

View File

@ -3,7 +3,7 @@
<style name="Base.Theme.Session" parent="@style/Theme.AppCompat.DayNight">
<item name="actionModeBackground">?colorPrimary</item>
<item name="android:colorBackground">?colorPrimary</item>
<item name="dialog_background_color">?colorPrimary</item>
<item name="backgroundSecondary">@color/classic_dark_1</item>
<item name="theme_preview_incoming">?message_received_background_color</item>
<item name="theme_preview_outgoing">?message_sent_background_color</item>
<item name="theme_preview_background">?colorPrimary</item>
@ -33,7 +33,7 @@
<item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.MaterialComponents.SmallComponent</item>
<item name="elementBorderColor">?android:textColorSecondary</item>
<item name="colorOnSurface">?android:textColorPrimary</item>
<item name="colorSurface">?dialog_background_color</item>
<item name="colorSurface">?backgroundSecondary</item>
<item name="media_overview_toolbar_background">@color/transparent</item>
<item name="media_overview_header_foreground">?android:textColorPrimary</item>
<item name="menu_accept_icon">@drawable/ic_baseline_done_24</item>
@ -192,8 +192,6 @@
<item name="emoji_tab_strip_background">@color/compose_view_background</item>
<item name="emoji_tab_indicator">?colorAccent</item>
<item name="emoji_tab_underline">@color/gray78</item>
<item name="emoji_tab_seperator">@color/gray70</item>
<item name="emoji_drawer_background">@color/compose_text_view_background</item>
<item name="emoji_text_color">@color/white</item>
@ -248,7 +246,7 @@
</style>
<style name="Theme.TextSecure.Dialog.MediaSendProgress" parent="@android:style/Theme.Dialog">
<item name="android:colorBackground">?attr/dialog_background_color</item>
<item name="android:colorBackground">?backgroundSecondary</item>
<item name="android:windowNoTitle">true</item>
</style>
@ -316,6 +314,7 @@
<item name="sessionLogoTint">@color/classic_dark_6</item>
<item name="colorPrimary">@color/classic_dark_0</item>
<item name="colorPrimaryDark">@color/classic_dark_0</item>
<item name="backgroundSecondary">@color/classic_dark_1</item>
<item name="colorControlNormal">?android:textColorPrimary</item>
<item name="colorControlActivated">?colorAccent</item>
<item name="android:textColorPrimary">@color/classic_dark_6</item>
@ -325,12 +324,10 @@
<item name="android:textColorHint">@color/gray27</item>
<item name="android:windowBackground">?colorPrimary</item>
<item name="android:navigationBarColor">@color/navigation_bar</item>
<item name="dialog_background_color">@color/classic_dark_1</item>
<item name="bottomSheetDialogTheme">@style/Classic.Dark.BottomSheet</item>
<item name="actionMenuTextColor">?android:textColorPrimary</item>
<item name="popupTheme">?actionBarPopupTheme</item>
<item name="colorCellBackground">@color/classic_dark_1</item>
<item name="colorSettingsBackground">@color/classic_dark_1</item>
<item name="colorDividerBackground">@color/classic_dark_3</item>
<item name="android:colorControlHighlight">@color/classic_dark_3</item>
<item name="colorControlHighlight">@color/classic_dark_3</item>
@ -353,10 +350,10 @@
<item name="home_gradient_start">#00000000</item>
<item name="home_gradient_end">@color/classic_dark_1</item>
<item name="conversation_pinned_background_color">?colorCellBackground</item>
<item name="conversation_unread_background_color">@color/classic_dark_2</item>
<item name="conversation_unread_background_color">@color/classic_dark_1</item>
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
<item name="unreadIndicatorBackgroundColor">@color/classic_dark_3</item>
<item name="unreadIndicatorTextColor">@color/classic_dark_6</item>
<item name="unreadIndicatorBackgroundColor">?colorAccent</item>
<item name="unreadIndicatorTextColor">@color/classic_dark_0</item>
<!-- New conversation button -->
<item name="conversation_color_non_main">@color/classic_dark_2</item>
@ -368,7 +365,7 @@
<item name="conversationMenuSearchBackgroundColor">@color/classic_dark_0</item>
<!-- Conversation -->
<item name="message_received_background_color">@color/classic_dark_3</item>
<item name="message_received_background_color">@color/classic_dark_2</item>
<item name="message_received_text_color">@color/classic_dark_6</item>
<item name="message_sent_background_color">?colorAccent</item>
<item name="message_sent_text_color">@color/classic_dark_0</item>
@ -394,7 +391,7 @@
<!-- Main styles -->
<item name="sessionLogoTint">@color/classic_light_0</item>
<item name="colorPrimary">@color/classic_light_6</item>
<item name="dialog_background_color">@color/classic_light_5</item>
<item name="backgroundSecondary">@color/classic_light_5</item>
<item name="colorPrimaryDark">@color/classic_light_6</item>
<item name="colorControlNormal">?android:textColorPrimary</item>
<item name="colorControlActivated">?colorAccent</item>
@ -406,7 +403,6 @@
<item name="android:windowBackground">?colorPrimary</item>
<item name="android:navigationBarColor">@color/classic_light_navigation_bar</item>
<item name="colorCellBackground">@color/classic_light_6</item>
<item name="colorSettingsBackground">@color/classic_light_5</item>
<item name="colorDividerBackground">@color/classic_light_3</item>
<item name="android:colorControlHighlight">@color/classic_light_3</item>
<item name="colorControlHighlight">@color/classic_light_3</item>
@ -441,7 +437,7 @@
<item name="conversation_pinned_background_color">?colorCellBackground</item>
<item name="conversation_unread_background_color">@color/classic_light_6</item>
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
<item name="unreadIndicatorBackgroundColor">@color/classic_light_3</item>
<item name="unreadIndicatorBackgroundColor">?colorAccent</item>
<item name="unreadIndicatorTextColor">@color/classic_light_0</item>
<!-- New conversation button -->
<item name="conversation_color_non_main">@color/classic_light_4</item>
@ -454,7 +450,7 @@
<item name="conversationMenuSearchBackgroundColor">@color/classic_light_6</item>
<!-- Conversation -->
<item name="message_received_background_color">@color/classic_light_3</item>
<item name="message_received_background_color">@color/classic_light_4</item>
<item name="message_received_text_color">@color/classic_light_0</item>
<item name="message_sent_background_color">?colorAccent</item>
<item name="message_sent_text_color">@color/classic_light_0</item>
@ -482,6 +478,7 @@
<item name="sessionLogoTint">@color/ocean_dark_7</item>
<item name="colorPrimary">@color/ocean_dark_2</item>
<item name="colorPrimaryDark">@color/ocean_dark_2</item>
<item name="backgroundSecondary">@color/ocean_dark_1</item>
<item name="colorControlNormal">@color/ocean_dark_7</item>
<item name="colorControlActivated">?colorAccent</item>
<item name="android:textColorPrimary">@color/ocean_dark_7</item>
@ -494,7 +491,6 @@
<item name="default_background_end">?colorPrimary</item>
<item name="default_background_start">?colorPrimaryDark</item>
<item name="colorCellBackground">@color/ocean_dark_3</item>
<item name="colorSettingsBackground">@color/ocean_dark_1</item>
<item name="colorDividerBackground">@color/ocean_dark_4</item>
<item name="android:colorControlHighlight">@color/ocean_dark_4</item>
<item name="colorControlHighlight">@color/ocean_dark_4</item>
@ -520,7 +516,7 @@
<item name="home_gradient_start">#00000000</item>
<item name="home_gradient_end">@color/ocean_dark_3</item>
<item name="conversation_pinned_background_color">?colorCellBackground</item>
<item name="conversation_unread_background_color">@color/ocean_dark_4</item>
<item name="conversation_unread_background_color">@color/ocean_dark_3</item>
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
<item name="unreadIndicatorBackgroundColor">?colorAccent</item>
<item name="unreadIndicatorTextColor">@color/ocean_dark_0</item>
@ -565,6 +561,7 @@
<item name="sessionLogoTint">@color/ocean_light_1</item>
<item name="colorPrimary">@color/ocean_light_7</item>
<item name="colorPrimaryDark">@color/ocean_light_6</item>
<item name="backgroundSecondary">@color/ocean_light_6</item>
<item name="colorControlNormal">@color/ocean_light_1</item>
<item name="colorControlActivated">?colorAccent</item>
<item name="android:textColorPrimary">@color/ocean_light_1</item>
@ -577,7 +574,6 @@
<item name="default_background_end">@color/ocean_light_7</item>
<item name="default_background_start">@color/ocean_light_6</item>
<item name="colorCellBackground">@color/ocean_light_5</item>
<item name="colorSettingsBackground">@color/ocean_light_6</item>
<item name="colorDividerBackground">@color/ocean_light_3</item>
<item name="android:colorControlHighlight">@color/ocean_light_4</item>
<item name="colorControlHighlight">@color/ocean_light_4</item>
@ -641,7 +637,7 @@
<item name="scroll_to_bottom_button_border">?input_bar_button_background_opaque_border</item>
<item name="conversation_unread_count_indicator_background">?colorAccent</item>
<item name="conversation_pinned_background_color">?colorCellBackground</item>
<item name="conversation_unread_background_color">@color/ocean_light_6</item>
<item name="conversation_unread_background_color">@color/ocean_light_5</item>
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
<item name="message_selected">@color/ocean_light_5</item>
</style>
@ -655,6 +651,7 @@
<item name="sessionLogoTint">@color/classic_dark_6</item>
<item name="colorPrimary">@color/classic_dark_0</item>
<item name="colorPrimaryDark">@color/classic_dark_0</item>
<item name="backgroundSecondary">@color/classic_dark_1</item>
<item name="colorControlNormal">?android:textColorPrimary</item>
<item name="colorControlActivated">?colorAccent</item>
<item name="android:colorControlHighlight">?colorAccent</item>
@ -665,12 +662,10 @@
<item name="android:textColorHint">@color/gray27</item>
<item name="android:windowBackground">?colorPrimary</item>
<item name="android:navigationBarColor">@color/compose_view_background</item>
<item name="dialog_background_color">@color/classic_dark_1</item>
<item name="bottomSheetDialogTheme">@style/Classic.Dark.BottomSheet</item>
<item name="actionMenuTextColor">?android:textColorPrimary</item>
<item name="popupTheme">?actionBarPopupTheme</item>
<item name="colorCellBackground">@color/classic_dark_1</item>
<item name="colorSettingsBackground">@color/classic_dark_1</item>
<item name="colorDividerBackground">@color/classic_dark_3</item>
<item name="actionBarPopupTheme">@style/Dark.Popup</item>
<item name="actionBarWidgetTheme">@null</item>
@ -688,10 +683,10 @@
<item name="home_gradient_start">#00000000</item>
<item name="home_gradient_end">@color/classic_dark_1</item>
<item name="conversation_pinned_background_color">?colorCellBackground</item>
<item name="conversation_unread_background_color">@color/classic_dark_2</item>
<item name="conversation_unread_background_color">@color/classic_dark_1</item>
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
<item name="unreadIndicatorBackgroundColor">@color/classic_dark_3</item>
<item name="unreadIndicatorTextColor">@color/classic_dark_6</item>
<item name="unreadIndicatorBackgroundColor">?colorAccent</item>
<item name="unreadIndicatorTextColor">@color/classic_dark_0</item>
<!-- New conversation button -->
<item name="conversation_color_non_main">@color/classic_dark_2</item>
@ -703,7 +698,7 @@
<item name="conversationMenuSearchBackgroundColor">@color/classic_dark_0</item>
<!-- Conversation -->
<item name="message_received_background_color">@color/classic_dark_3</item>
<item name="message_received_background_color">@color/classic_dark_2</item>
<item name="message_received_text_color">@color/classic_dark_6</item>
<item name="message_sent_background_color">?colorAccent</item>
<item name="message_sent_text_color">@color/classic_dark_0</item>

View File

@ -53,8 +53,6 @@
<attr name="emoji_tab_strip_background" format="color" />
<attr name="emoji_tab_indicator" format="color" />
<attr name="emoji_tab_underline" format="color" />
<attr name="emoji_tab_seperator" format="color" />
<attr name="emoji_drawer_background" format="color" />
<attr name="emoji_text_color" format="color" />
@ -93,7 +91,6 @@
<attr name="dialog_info_icon" format="reference" />
<attr name="dialog_alert_icon" format="reference" />
<attr name="dialog_background_color" format="reference|color" />
<attr name="conversation_icon_attach_audio" format="reference"/>
<attr name="conversation_icon_attach_video" format="reference" />

View File

@ -40,7 +40,6 @@
<color name="gray27">#ffbbbbbb</color>
<color name="gray50">#ff808080</color>
<color name="gray65">#ff595959</color>
<color name="gray70">#ff4d4d4d</color>
<color name="gray78">#ff383838</color>
<color name="transparent_black_30">#30000000</color>