Fix/video call rotation and avatars (#1548)

* Simplifying profile picture view

We don't need the isLarge option as the component's size is always set.
Using profilePictureView in the call screen.

* Swapping avatars between user and contact's

* Adding the user's avatar for when it  needs to be displayed

* Making sure we never invert the contact's landscape rotation

* Update app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt

Co-authored-by: Andrew <andrewgallasch@gmail.com>

* Update app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt

Co-authored-by: Andrew <andrewgallasch@gmail.com>

* Update app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt

Co-authored-by: Andrew <andrewgallasch@gmail.com>

---------

Co-authored-by: Andrew <andrewgallasch@gmail.com>
This commit is contained in:
ThomasSession 2024-07-17 15:16:12 +10:00 committed by GitHub
parent 3bac04c863
commit 01cd449794
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 89 additions and 92 deletions

View File

@ -33,6 +33,8 @@ import network.loki.messenger.databinding.ActivityWebrtcBinding
import org.apache.commons.lang3.time.DurationFormatUtils
import org.session.libsession.avatars.ProfileContactPhoto
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.truncateIdForDisplay
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
@ -200,6 +202,16 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
}
clipFloatingInsets()
// set up the user avatar
TextSecurePreferences.getLocalNumber(this)?.let{
val username = TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(it)
binding.userAvatar.apply {
publicKey = it
displayName = username
update()
}
}
}
/**
@ -254,8 +266,10 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
else -> 0f
}
remoteRecipient.animate().cancel()
remoteRecipient.animate().rotation(rotation).start()
userAvatar.animate().cancel()
userAvatar.animate().rotation(rotation).start()
contactAvatar.animate().cancel()
contactAvatar.animate().rotation(rotation).start()
speakerPhoneButton.animate().cancel()
speakerPhoneButton.animate().rotation(rotation).start()
@ -328,44 +342,20 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
launch {
viewModel.recipient.collect { latestRecipient ->
binding.contactAvatar.recycle()
if (latestRecipient.recipient != null) {
val publicKey = latestRecipient.recipient.address.serialize()
val displayName = getUserDisplayName(publicKey)
supportActionBar?.title = displayName
val signalProfilePicture = latestRecipient.recipient.contactPhoto
val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject
val sizeInPX =
resources.getDimensionPixelSize(R.dimen.extra_large_profile_picture_size)
binding.remoteRecipientName.text = displayName
if (signalProfilePicture != null && avatar != "0" && avatar != "") {
glide.clear(binding.remoteRecipient)
glide.load(signalProfilePicture)
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.circleCrop()
.error(
AvatarPlaceholderGenerator.generate(
this@WebRtcCallActivity,
sizeInPX,
publicKey,
displayName
)
)
.into(binding.remoteRecipient)
} else {
glide.clear(binding.remoteRecipient)
glide.load(
AvatarPlaceholderGenerator.generate(
this@WebRtcCallActivity,
sizeInPX,
publicKey,
displayName
)
)
.diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop()
.into(binding.remoteRecipient)
val contactPublicKey = latestRecipient.recipient.address.serialize()
val contactDisplayName = getUserDisplayName(contactPublicKey)
supportActionBar?.title = contactDisplayName
binding.remoteRecipientName.text = contactDisplayName
// sort out the contact's avatar
binding.contactAvatar.apply {
publicKey = contactPublicKey
displayName = contactDisplayName
update()
}
} else {
glide.clear(binding.remoteRecipient)
}
}
}
@ -400,22 +390,16 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
binding.floatingRenderer.removeAllViews()
binding.fullscreenRenderer.removeAllViews()
// 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
// handle fullscreen video window
if(state.showFullscreenVideo()){
viewModel.fullscreenRenderer?.let { surfaceView ->
binding.fullscreenRenderer.addView(surfaceView)
binding.fullscreenRenderer.isVisible = true
binding.remoteRecipient.isVisible = false
hideAvatar()
}
} else {
binding.fullscreenRenderer.isVisible = false
binding.remoteRecipient.isVisible = true
showAvatar(state.swapped)
}
// handle floating video window
@ -429,6 +413,15 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
binding.floatingRenderer.isVisible = false
}
// 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
// make sure to default to the contact's avatar if the floating container is not visible
if (!showFloatingContainer) showAvatar(false)
// handle buttons
binding.enableCameraButton.isSelected = state.userVideoEnabled
}
@ -436,6 +429,20 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
}
}
/**
* Shows the avatar image.
* If @showUserAvatar is true, the user's avatar is shown, otherwise the contact's avatar is shown.
*/
private fun showAvatar(showUserAvatar: Boolean) {
binding.userAvatar.isVisible = showUserAvatar
binding.contactAvatar.isVisible = !showUserAvatar
}
private fun hideAvatar() {
binding.userAvatar.isVisible = false
binding.contactAvatar.isVisible = false
}
private fun getUserDisplayName(publicKey: String): String {
val contact =
DatabaseComponent.get(this).sessionContactDatabase().getContactWithSessionID(publicKey)

View File

@ -33,7 +33,6 @@ class ProfilePictureView @JvmOverloads constructor(
var displayName: String? = null
var additionalPublicKey: String? = null
var additionalDisplayName: String? = null
var isLarge = false
private val profilePicturesCache = mutableMapOf<View, Recipient>()
private val unknownRecipientDrawable by lazy { ResourceContactPhoto(R.drawable.ic_profile_default)
@ -90,29 +89,25 @@ class ProfilePictureView @JvmOverloads constructor(
fun update() {
val publicKey = publicKey ?: return Log.w(TAG, "Could not find public key to update profile picture")
val additionalPublicKey = additionalPublicKey
// if we have a multi avatar setup
if (additionalPublicKey != null) {
setProfilePictureIfNeeded(binding.doubleModeImageView1, publicKey, displayName)
setProfilePictureIfNeeded(binding.doubleModeImageView2, additionalPublicKey, additionalDisplayName)
binding.doubleModeImageViewContainer.visibility = View.VISIBLE
} else {
// clear single image
glide.clear(binding.singleModeImageView)
binding.singleModeImageView.visibility = View.INVISIBLE
} else { // single image mode
setProfilePictureIfNeeded(binding.singleModeImageView, publicKey, displayName)
binding.singleModeImageView.visibility = View.VISIBLE
// clear multi image
glide.clear(binding.doubleModeImageView1)
glide.clear(binding.doubleModeImageView2)
binding.doubleModeImageViewContainer.visibility = View.INVISIBLE
}
if (additionalPublicKey == null && !isLarge) {
setProfilePictureIfNeeded(binding.singleModeImageView, publicKey, displayName)
binding.singleModeImageView.visibility = View.VISIBLE
} else {
glide.clear(binding.singleModeImageView)
binding.singleModeImageView.visibility = View.INVISIBLE
}
if (additionalPublicKey == null && isLarge) {
setProfilePictureIfNeeded(binding.largeSingleModeImageView, publicKey, displayName)
binding.largeSingleModeImageView.visibility = View.VISIBLE
} else {
glide.clear(binding.largeSingleModeImageView)
binding.largeSingleModeImageView.visibility = View.INVISIBLE
}
}
private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?) {

View File

@ -56,7 +56,6 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss()
with(binding) {
profilePictureView.publicKey = publicKey
profilePictureView.isLarge = true
profilePictureView.update(recipient)
nameTextViewContainer.visibility = View.VISIBLE
nameTextViewContainer.setOnClickListener {

View File

@ -123,7 +123,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
view.apply {
publicKey = hexEncodedPublicKey
displayName = getDisplayName()
isLarge = true
update()
}
}

View File

@ -61,6 +61,7 @@ import org.webrtc.SurfaceViewRenderer
import java.nio.ByteBuffer
import java.util.ArrayDeque
import java.util.UUID
import kotlin.math.abs
import org.thoughtcrime.securesms.webrtc.data.State as CallState
class CallManager(
@ -718,7 +719,7 @@ class CallManager(
// apply the rotation to the streams
peerConnection?.setDeviceRotation(rotation)
remoteRotationSink?.rotation = rotation
remoteRotationSink?.rotation = abs(rotation) // abs as we never need the remote video to be inverted
}
fun handleWiredHeadsetChanged(present: Boolean) {

View File

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
android:shape="oval">
<solid android:color="@color/profile_picture_background" />
<corners android:radius="40dp" />
</shape>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/profile_picture_background" />
<corners android:radius="23dp" />
</shape>

View File

@ -23,15 +23,27 @@
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</FrameLayout>
<ImageView
android:id="@+id/remote_recipient"
<org.thoughtcrime.securesms.components.ProfilePictureView
android:id="@+id/userAvatar"
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"/>
android:layout_height="@dimen/extra_large_profile_picture_size"
android:visibility="gone"/>
<org.thoughtcrime.securesms.components.ProfilePictureView
android:id="@+id/contactAvatar"
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" />
<ImageView
android:id="@+id/back_arrow"
@ -71,9 +83,9 @@
android:foregroundGravity="center"
android:visibility="gone"
app:SpinKit_Color="@color/core_white"
app:layout_constraintEnd_toEndOf="@+id/remote_recipient"
app:layout_constraintStart_toStartOf="@+id/remote_recipient"
app:layout_constraintTop_toBottomOf="@id/remote_recipient"
app:layout_constraintEnd_toEndOf="@+id/contactAvatar"
app:layout_constraintStart_toStartOf="@+id/contactAvatar"
app:layout_constraintTop_toBottomOf="@id/contactAvatar"
tools:visibility="visible" />
<TextView

View File

@ -27,17 +27,11 @@
</RelativeLayout>
<ImageView
android:scaleType="centerCrop"
android:id="@+id/singleModeImageView"
android:layout_width="@dimen/medium_profile_picture_size"
android:layout_height="@dimen/medium_profile_picture_size"
android:background="@drawable/profile_picture_view_medium_background" />
<ImageView
android:id="@+id/largeSingleModeImageView"
android:scaleType="centerCrop"
android:layout_width="@dimen/large_profile_picture_size"
android:layout_height="@dimen/large_profile_picture_size"
android:background="@drawable/profile_picture_view_large_background" />
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/profile_picture_view_background" />
</merge>