Feature/calls kee updates (#1007)

* SES-3469 Fixing shortcut activity

* Reduce the time shown from 00:00:00 to 00:00 expand as needed when call exceeds the time which can be shown in 00:00

* Moved the voice setting to the top of the privacy page, and allowed for auto toggle of settings

* Disabling the switch camera butotn when not in video

* Moving logic into VM and adding step counter

* comments

* PR feedback - not exposing a mutable set
pull/1710/head
ThomasSession 1 month ago committed by GitHub
parent 0445ebeb57
commit 01c3003b36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,54 +0,0 @@
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.TaskStackBuilder;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.Toast;
import org.session.libsession.utilities.Address;
import org.thoughtcrime.securesms.home.HomeActivity;
import org.session.libsession.utilities.recipients.Recipient;
import org.thoughtcrime.securesms.util.CommunicationActions;
import network.loki.messenger.R;
public class ShortcutLauncherActivity extends AppCompatActivity {
private static final String KEY_SERIALIZED_ADDRESS = "serialized_address";
public static Intent createIntent(@NonNull Context context, @NonNull Address address) {
Intent intent = new Intent(context, ShortcutLauncherActivity.class);
intent.setAction(Intent.ACTION_MAIN);
intent.putExtra(KEY_SERIALIZED_ADDRESS, address.toString());
return intent;
}
@SuppressLint("StaticFieldLeak")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String serializedAddress = getIntent().getStringExtra(KEY_SERIALIZED_ADDRESS);
if (serializedAddress == null) {
Toast.makeText(this, R.string.invalidShortcut, Toast.LENGTH_SHORT).show();
startActivity(new Intent(this, HomeActivity.class));
finish();
return;
}
Address address = Address.fromSerialized(serializedAddress);
Recipient recipient = Recipient.from(this, address, true);
TaskStackBuilder backStack = TaskStackBuilder.create(this)
.addNextIntent(new Intent(this, HomeActivity.class));
CommunicationActions.startConversation(this, recipient, null, backStack);
finish();
}
}

@ -0,0 +1,68 @@
package org.thoughtcrime.securesms
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.TaskStackBuilder
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import network.loki.messenger.R
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Address.Companion.fromSerialized
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.home.HomeActivity
import org.thoughtcrime.securesms.util.CommunicationActions
class ShortcutLauncherActivity : AppCompatActivity() {
@SuppressLint("StaticFieldLeak")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val serializedAddress = intent.getStringExtra(KEY_SERIALIZED_ADDRESS)
if (serializedAddress == null) {
Toast.makeText(this, R.string.invalidShortcut, Toast.LENGTH_SHORT).show()
startActivity(Intent(this, HomeActivity::class.java))
finish()
return
}
val backStack = TaskStackBuilder.create(this)
.addNextIntent(Intent(this, HomeActivity::class.java))
// start the appropriate conversation activity and finish this one
lifecycleScope.launch(Dispatchers.Default) {
val context = this@ShortcutLauncherActivity
val address = fromSerialized(serializedAddress)
val recipient = Recipient.from(context, address, true)
val threadId = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient)
val intent = Intent(context, ConversationActivityV2::class.java)
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
backStack.addNextIntent(intent)
backStack.startActivities()
finish()
}
}
companion object {
private const val KEY_SERIALIZED_ADDRESS = "serialized_address"
fun createIntent(context: Context, address: Address): Intent {
val intent = Intent(context, ShortcutLauncherActivity::class.java)
intent.setAction(Intent.ACTION_MAIN)
intent.putExtra(KEY_SERIALIZED_ADDRESS, address.toString())
return intent
}
}
}

@ -240,7 +240,7 @@ object ConversationMenuHelper {
button(R.string.sessionSettings, R.string.AccessibilityId_sessionSettings) { button(R.string.sessionSettings, R.string.AccessibilityId_sessionSettings) {
val intent = Intent(context, PrivacySettingsActivity::class.java) val intent = Intent(context, PrivacySettingsActivity::class.java)
// allow the screen to auto scroll to the appropriate toggle // allow the screen to auto scroll to the appropriate toggle
intent.putExtra(PrivacySettingsActivity.SCROLL_KEY, CALL_NOTIFICATIONS_ENABLED) intent.putExtra(PrivacySettingsActivity.SCROLL_AND_TOGGLE_KEY, CALL_NOTIFICATIONS_ENABLED)
context.startActivity(intent) context.startActivity(intent)
} }
cancelButton() cancelButton()

@ -198,7 +198,7 @@ class ControlMessageView : LinearLayout {
button(R.string.sessionSettings) { button(R.string.sessionSettings) {
val intent = Intent(context, PrivacySettingsActivity::class.java) val intent = Intent(context, PrivacySettingsActivity::class.java)
// allow the screen to auto scroll to the appropriate toggle // allow the screen to auto scroll to the appropriate toggle
intent.putExtra(PrivacySettingsActivity.SCROLL_KEY, CALL_NOTIFICATIONS_ENABLED) intent.putExtra(PrivacySettingsActivity.SCROLL_AND_TOGGLE_KEY, CALL_NOTIFICATIONS_ENABLED)
context.startActivity(intent) context.startActivity(intent)
} }
cancelButton() cancelButton()

@ -1,15 +1,21 @@
package org.thoughtcrime.securesms.preferences package org.thoughtcrime.securesms.preferences
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.ScreenLockActionBarActivity
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class PrivacySettingsActivity : ScreenLockActionBarActivity() { class PrivacySettingsActivity : ScreenLockActionBarActivity() {
companion object{ companion object{
const val SCROLL_KEY = "privacy_scroll_key" const val SCROLL_KEY = "privacy_scroll_key"
const val SCROLL_AND_TOGGLE_KEY = "privacy_scroll_and_toggle_key"
} }
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
@ -22,6 +28,9 @@ class PrivacySettingsActivity : ScreenLockActionBarActivity() {
if(intent.hasExtra(SCROLL_KEY)) { if(intent.hasExtra(SCROLL_KEY)) {
fragment.scrollToKey(intent.getStringExtra(SCROLL_KEY)!!) fragment.scrollToKey(intent.getStringExtra(SCROLL_KEY)!!)
} else if(intent.hasExtra(SCROLL_AND_TOGGLE_KEY)) {
fragment.scrollAndAutoToggle(intent.getStringExtra(SCROLL_AND_TOGGLE_KEY)!!)
} }
} }
} }

@ -5,10 +5,13 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceCategory import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceDataStore import androidx.preference.PreferenceDataStore
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import network.loki.messenger.BuildConfig import network.loki.messenger.BuildConfig
import network.loki.messenger.R import network.loki.messenger.R
@ -27,7 +30,11 @@ import org.thoughtcrime.securesms.util.IntentUtils
@AndroidEntryPoint @AndroidEntryPoint
class PrivacySettingsPreferenceFragment : CorrectedPreferenceFragment() { class PrivacySettingsPreferenceFragment : CorrectedPreferenceFragment() {
@Inject lateinit var configFactory: ConfigFactory @Inject
lateinit var configFactory: ConfigFactory
@Inject
lateinit var textSecurePreferences: TextSecurePreferences
override fun onCreate(paramBundle: Bundle?) { override fun onCreate(paramBundle: Bundle?) {
super.onCreate(paramBundle) super.onCreate(paramBundle)
@ -73,6 +80,25 @@ class PrivacySettingsPreferenceFragment : CorrectedPreferenceFragment() {
scrollToPreference(key) scrollToPreference(key)
} }
fun scrollAndAutoToggle(key: String){
lifecycleScope.launch {
scrollToKey(key)
delay(500) // slight delay to make the transition less jarring
// Find the preference based on the provided key.
val pref = findPreference<Preference>(key)
// auto toggle for prefs that are switches
pref?.let {
// Check if it's a switch preference so we can toggle its checked state.
if (it is SwitchPreferenceCompat) {
// force set to true here, and call the onPreferenceChangeListener
// defined further up so that custom behaviours are still applied
// Invoke the onPreferenceChangeListener with the new value.
it.onPreferenceChangeListener?.onPreferenceChange(it, true)
}
}
}
}
private fun setCall(isEnabled: Boolean) { private fun setCall(isEnabled: Boolean) {
(findPreference<Preference>(TextSecurePreferences.CALL_NOTIFICATIONS_ENABLED) as SwitchPreferenceCompat?)!!.isChecked = (findPreference<Preference>(TextSecurePreferences.CALL_NOTIFICATIONS_ENABLED) as SwitchPreferenceCompat?)!!.isChecked =
isEnabled isEnabled

@ -1,7 +1,7 @@
package org.thoughtcrime.securesms.webrtc package org.thoughtcrime.securesms.webrtc
import android.content.Context import android.content.Context
import android.content.Intent import androidx.annotation.StringRes
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -10,10 +10,26 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import network.loki.messenger.R
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.UsernameUtils import org.session.libsession.utilities.UsernameUtils
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.ViewUtil
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_ANSWER_INCOMING
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_ANSWER_OUTGOING
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_CONNECTED
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_DISCONNECTED
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_HANDLING_ICE
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_OFFER_INCOMING
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_OFFER_OUTGOING
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_PRE_OFFER_INCOMING
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_PRE_OFFER_OUTGOING
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_RECONNECTING
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_SENDING_ICE
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.NETWORK_FAILURE
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.RECIPIENT_UNAVAILABLE
import org.webrtc.SurfaceViewRenderer import org.webrtc.SurfaceViewRenderer
import javax.inject.Inject import javax.inject.Inject
@ -69,13 +85,106 @@ class CallViewModel @Inject constructor(
} }
val currentCallState get() = callManager.currentCallState val currentCallState get() = callManager.currentCallState
val callState: StateFlow<CallState> = callManager.callStateEvents.combine(rtcCallBridge.hasAcceptedCall){
state, accepted -> CallState(state = state, hasAcceptedCall = accepted) val initialCallState = CallState("", "", false, false, false)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), CallState(State.CALL_INITIALIZING, false)) val initialAccumulator = CallAccumulator(emptySet(), initialCallState)
val callState: StateFlow<CallState> = callManager.callStateEvents
.combine(rtcCallBridge.hasAcceptedCall) { state, accepted ->
Pair(state, accepted)
}
.scan(initialAccumulator) { acc, (state, accepted) ->
// reset the set on preoffers
val newSteps = if (state in listOf(
CALL_PRE_OFFER_OUTGOING,
CALL_PRE_OFFER_INCOMING
)
) {
setOf(state)
} else {
acc.callSteps + state
}
val callTitle = when (state) {
CALL_PRE_OFFER_OUTGOING, CALL_PRE_OFFER_INCOMING,
CALL_OFFER_OUTGOING, CALL_OFFER_INCOMING ->
context.getString(R.string.callsRinging)
CALL_ANSWER_INCOMING, CALL_ANSWER_OUTGOING ->
context.getString(R.string.callsConnecting)
CALL_CONNECTED -> ""
CALL_RECONNECTING -> context.getString(R.string.callsReconnecting)
RECIPIENT_UNAVAILABLE, CALL_DISCONNECTED ->
context.getString(R.string.callsEnded)
NETWORK_FAILURE -> context.getString(R.string.callsErrorStart)
else -> acc.callState.callLabelTitle // keep previous title
}
val callSubtitle = when (state) {
CALL_PRE_OFFER_OUTGOING -> constructCallLabel(R.string.creatingCall, newSteps.size)
CALL_PRE_OFFER_INCOMING -> constructCallLabel(R.string.receivingPreOffer, newSteps.size)
CALL_OFFER_OUTGOING -> constructCallLabel(R.string.sendingCallOffer, newSteps.size)
CALL_OFFER_INCOMING -> constructCallLabel(R.string.receivingCallOffer, newSteps.size)
CALL_ANSWER_OUTGOING, CALL_ANSWER_INCOMING -> constructCallLabel(R.string.receivedAnswer, newSteps.size)
CALL_SENDING_ICE -> constructCallLabel(R.string.sendingConnectionCandidates, newSteps.size)
CALL_HANDLING_ICE -> constructCallLabel(R.string.handlingConnectionCandidates, newSteps.size)
else -> ""
}
val showCallControls = state in listOf(
CALL_CONNECTED,
CALL_PRE_OFFER_OUTGOING,
CALL_OFFER_OUTGOING,
CALL_ANSWER_OUTGOING,
CALL_ANSWER_INCOMING
) || (state in listOf(
CALL_PRE_OFFER_INCOMING,
CALL_OFFER_INCOMING,
CALL_HANDLING_ICE,
CALL_SENDING_ICE
) && accepted)
val showEndCallButton = showCallControls || state == CALL_RECONNECTING
val showPreCallButtons = state in listOf(
CALL_PRE_OFFER_INCOMING,
CALL_OFFER_INCOMING,
CALL_HANDLING_ICE,
CALL_SENDING_ICE
) && !accepted
val newCallState = CallState(
callLabelTitle = callTitle,
callLabelSubtitle = callSubtitle,
showCallButtons = showCallControls,
showPreCallButtons = showPreCallButtons,
showEndCallButton = showEndCallButton
)
CallAccumulator(newSteps, newCallState)
}
.map { it.callState }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialCallState)
val recipient get() = callManager.recipientEvents val recipient get() = callManager.recipientEvents
val callStartTime: Long get() = callManager.callStartTime val callStartTime: Long get() = callManager.callStartTime
data class CallAccumulator(
val callSteps: Set<CallViewModel.State>,
val callState: CallState
)
private val MAX_CALL_STEPS: Int = 5
private fun constructCallLabel(@StringRes label: Int, stepsCount: Int): String {
return if(ViewUtil.isLtr(context)) {
"${context.getString(label)} $stepsCount/$MAX_CALL_STEPS"
} else {
"$MAX_CALL_STEPS/$stepsCount ${context.getString(label)}"
}
}
fun swapVideos() = callManager.swapVideos() fun swapVideos() = callManager.swapVideos()
fun toggleMute() = callManager.toggleMuteAudio() fun toggleMute() = callManager.toggleMuteAudio()
@ -100,8 +209,10 @@ class CallViewModel @Inject constructor(
fun getCurrentUsername() = usernameUtils.getCurrentUsernameWithAccountIdFallback() fun getCurrentUsername() = usernameUtils.getCurrentUsernameWithAccountIdFallback()
data class CallState( data class CallState(
val state: State, val callLabelTitle: String?,
val hasAcceptedCall: Boolean val callLabelSubtitle: String,
val showCallButtons: Boolean,
val showPreCallButtons: Boolean,
val showEndCallButton: Boolean
) )
} }

@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.IntentFilter import android.content.IntentFilter
import android.content.res.ColorStateList
import android.graphics.Outline import android.graphics.Outline
import android.media.AudioManager import android.media.AudioManager
import android.os.Build import android.os.Build
@ -15,6 +16,7 @@ import android.view.View
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.core.content.IntentCompat import androidx.core.content.IntentCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -27,13 +29,12 @@ import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityWebrtcBinding import network.loki.messenger.databinding.ActivityWebrtcBinding
import org.apache.commons.lang3.time.DurationFormatUtils import org.apache.commons.lang3.time.DurationFormatUtils
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.truncateIdForDisplay import org.session.libsession.utilities.getColorFromAttr
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.ScreenLockActionBarActivity
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.conversation.v2.ViewUtil
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_ANSWER_INCOMING import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_ANSWER_INCOMING
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_ANSWER_OUTGOING import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_ANSWER_OUTGOING
@ -49,6 +50,7 @@ import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_SENDING_ICE
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.NETWORK_FAILURE import org.thoughtcrime.securesms.webrtc.CallViewModel.State.NETWORK_FAILURE
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.RECIPIENT_UNAVAILABLE import org.thoughtcrime.securesms.webrtc.CallViewModel.State.RECIPIENT_UNAVAILABLE
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.SPEAKER_PHONE import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.SPEAKER_PHONE
import java.time.Duration
@AndroidEntryPoint @AndroidEntryPoint
class WebRtcCallActivity : ScreenLockActionBarActivity() { class WebRtcCallActivity : ScreenLockActionBarActivity() {
@ -61,8 +63,6 @@ class WebRtcCallActivity : ScreenLockActionBarActivity() {
const val EXTRA_RECIPIENT_ADDRESS = "RECIPIENT_ID" const val EXTRA_RECIPIENT_ADDRESS = "RECIPIENT_ID"
private const val CALL_DURATION_FORMAT = "HH:mm:ss"
fun getCallActivityIntent(context: Context): Intent{ fun getCallActivityIntent(context: Context): Intent{
return Intent(context, WebRtcCallActivity::class.java) return Intent(context, WebRtcCallActivity::class.java)
.setFlags(FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) .setFlags(FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
@ -74,14 +74,12 @@ class WebRtcCallActivity : ScreenLockActionBarActivity() {
private var uiJob: Job? = null private var uiJob: Job? = null
private var hangupReceiver: BroadcastReceiver? = null private var hangupReceiver: BroadcastReceiver? = null
//todo PHONE TEMP STRINGS THAT WILL NEED TO BE REPLACED WITH CS STRINGS - putting them all here to easily discard them later private val CALL_DURATION_FORMAT_HOURS = "HH:mm:ss"
val TEMP_SEND_PRE_OFFER = "Creating Call" private val CALL_DURATION_FORMAT_MINS = "mm:ss"
val TEMP_RECEIVE_PRE_OFFER = "Receiving Pre Offer" private val ONE_HOUR: Long = Duration.ofHours(1).toMillis()
val TEMP_SENDING_OFFER = "Sending Call Offer"
val TEMP_RECEIVING_OFFER = "Receiving Call Offer" private val buttonColorEnabled by lazy { getColor(R.color.white) }
val TEMP_SENDING_CANDIDATES = "Sending Connection Candidates" private val buttonColorDisabled by lazy { getColorFromAttr(R.attr.disabled) }
val TEMP_RECEIVED_ANSWER = "Received Answer"
val TEMP_HANDLING_CANDIDATES = "Handling Connection Candidates"
/** /**
* We need to track the device's orientation so we can calculate whether or not to rotate the video streams * We need to track the device's orientation so we can calculate whether or not to rotate the video streams
@ -290,69 +288,18 @@ class WebRtcCallActivity : ScreenLockActionBarActivity() {
} }
} }
private fun updateControls(state: CallViewModel.State, hasAcceptedCall: Boolean) { private fun updateControls(callState: CallViewModel.CallState) {
with(binding) { with(binding) {
// set up title and subtitle // set up title and subtitle
callTitle.text = when (state) { callTitle.text = callState.callLabelTitle ?: callTitle.text // keep existing text if null
CALL_PRE_OFFER_OUTGOING, CALL_PRE_OFFER_INCOMING,
CALL_OFFER_OUTGOING, CALL_OFFER_INCOMING,
-> getString(R.string.callsRinging)
CALL_ANSWER_INCOMING,
CALL_ANSWER_OUTGOING,
-> getString(R.string.callsConnecting)
CALL_CONNECTED -> ""
CALL_RECONNECTING -> getString(R.string.callsReconnecting) callSubtitle.text = callState.callLabelSubtitle
RECIPIENT_UNAVAILABLE,
CALL_DISCONNECTED -> getString(R.string.callsEnded)
NETWORK_FAILURE -> getString(R.string.callsErrorStart)
else -> callTitle.text
}
callSubtitle.text = when (state) {
CALL_PRE_OFFER_OUTGOING -> TEMP_SEND_PRE_OFFER
CALL_PRE_OFFER_INCOMING -> TEMP_RECEIVE_PRE_OFFER
CALL_OFFER_OUTGOING -> TEMP_SENDING_OFFER
CALL_OFFER_INCOMING -> TEMP_RECEIVING_OFFER
CALL_ANSWER_OUTGOING, CALL_ANSWER_INCOMING -> TEMP_RECEIVED_ANSWER
CALL_SENDING_ICE -> TEMP_SENDING_CANDIDATES
CALL_HANDLING_ICE -> TEMP_HANDLING_CANDIDATES
else -> ""
}
callSubtitle.isVisible = callSubtitle.text.isNotEmpty() callSubtitle.isVisible = callSubtitle.text.isNotEmpty()
// buttons visibility // buttons visibility
val showCallControls = state in listOf( controlGroup.isVisible = callState.showCallButtons
CALL_CONNECTED, endCallButton.isVisible = callState.showEndCallButton
CALL_PRE_OFFER_OUTGOING, incomingControlGroup.isVisible = callState.showPreCallButtons
CALL_OFFER_OUTGOING,
CALL_ANSWER_OUTGOING,
CALL_ANSWER_INCOMING,
) || (state in listOf(
CALL_PRE_OFFER_INCOMING,
CALL_OFFER_INCOMING,
CALL_HANDLING_ICE,
CALL_SENDING_ICE
) && hasAcceptedCall)
controlGroup.isVisible = showCallControls
endCallButton.isVisible = showCallControls || state == CALL_RECONNECTING
incomingControlGroup.isVisible =
state in listOf(
CALL_PRE_OFFER_INCOMING,
CALL_OFFER_INCOMING,
CALL_HANDLING_ICE,
CALL_SENDING_ICE
) && !hasAcceptedCall
} }
} }
@ -371,7 +318,7 @@ class WebRtcCallActivity : ScreenLockActionBarActivity() {
launch { launch {
viewModel.callState.collect { data -> viewModel.callState.collect { data ->
updateControls(data.state, data.hasAcceptedCall) updateControls(data)
} }
} }
@ -400,9 +347,12 @@ class WebRtcCallActivity : ScreenLockActionBarActivity() {
val startTime = viewModel.callStartTime val startTime = viewModel.callStartTime
if (startTime != -1L) { if (startTime != -1L) {
if(viewModel.currentCallState == CALL_CONNECTED) { if(viewModel.currentCallState == CALL_CONNECTED) {
val duration = System.currentTimeMillis() - startTime
// apply format based on whether the call is more than 1h long
val durationFormat = if (duration > ONE_HOUR) CALL_DURATION_FORMAT_HOURS else CALL_DURATION_FORMAT_MINS
binding.callTitle.text = DurationFormatUtils.formatDuration( binding.callTitle.text = DurationFormatUtils.formatDuration(
System.currentTimeMillis() - startTime, duration,
CALL_DURATION_FORMAT durationFormat
) )
} }
} }
@ -458,6 +408,12 @@ class WebRtcCallActivity : ScreenLockActionBarActivity() {
// handle buttons // handle buttons
binding.enableCameraButton.isSelected = state.userVideoEnabled binding.enableCameraButton.isSelected = state.userVideoEnabled
binding.switchCameraButton.isEnabled = state.userVideoEnabled
binding.switchCameraButton.imageTintList =
ColorStateList.valueOf(
if(state.userVideoEnabled) buttonColorEnabled
else buttonColorDisabled
)
} }
} }
} }

@ -21,6 +21,7 @@
<attr name="onInvertedBackgroundPrimary" format="reference|color"/> <attr name="onInvertedBackgroundPrimary" format="reference|color"/>
<attr name="warning" format="reference|color"/> <attr name="warning" format="reference|color"/>
<attr name="danger" format="reference|color"/> <attr name="danger" format="reference|color"/>
<attr name="disabled" format="reference|color"/>
<attr name="backgroundSecondary" format="reference|color"/> <attr name="backgroundSecondary" format="reference|color"/>
<attr name="prominentButtonColor" format="reference|color"/> <attr name="prominentButtonColor" format="reference|color"/>
<attr name="textColorAlert" format="reference|color"/> <attr name="textColorAlert" format="reference|color"/>

@ -219,6 +219,7 @@
<item name="colorControlActivated">?colorAccent</item> <item name="colorControlActivated">?colorAccent</item>
<item name="warning">@color/accent_orange</item> <item name="warning">@color/accent_orange</item>
<item name="danger">@color/danger_dark</item> <item name="danger">@color/danger_dark</item>
<item name="disabled">@color/classic_light_1</item>
<item name="android:textColorPrimary">@color/classic_dark_6</item> <item name="android:textColorPrimary">@color/classic_dark_6</item>
<item name="android:textColorSecondary">?android:textColorPrimary</item> <item name="android:textColorSecondary">?android:textColorPrimary</item>
<item name="android:textColorTertiary">@color/classic_dark_5</item> <item name="android:textColorTertiary">@color/classic_dark_5</item>
@ -303,6 +304,7 @@
<item name="colorControlActivated">?colorAccent</item> <item name="colorControlActivated">?colorAccent</item>
<item name="warning">@color/rust</item> <item name="warning">@color/rust</item>
<item name="danger">@color/danger_light</item> <item name="danger">@color/danger_light</item>
<item name="disabled">@color/classic_dark_5</item>
<item name="android:textColorPrimary">@color/classic_light_0</item> <item name="android:textColorPrimary">@color/classic_light_0</item>
<item name="android:textColorSecondary">@color/classic_light_1</item> <item name="android:textColorSecondary">@color/classic_light_1</item>
<item name="android:textColorTertiary">@color/classic_light_1</item> <item name="android:textColorTertiary">@color/classic_light_1</item>
@ -395,6 +397,7 @@
<item name="colorControlActivated">?colorAccent</item> <item name="colorControlActivated">?colorAccent</item>
<item name="warning">@color/accent_orange</item> <item name="warning">@color/accent_orange</item>
<item name="danger">@color/danger_dark</item> <item name="danger">@color/danger_dark</item>
<item name="disabled">@color/classic_light_1</item>
<item name="android:textColorPrimary">@color/ocean_dark_7</item> <item name="android:textColorPrimary">@color/ocean_dark_7</item>
<item name="android:textColorSecondary">@color/ocean_dark_5</item> <item name="android:textColorSecondary">@color/ocean_dark_5</item>
<item name="android:textColorTertiary">@color/ocean_dark_5</item> <item name="android:textColorTertiary">@color/ocean_dark_5</item>
@ -482,6 +485,7 @@
<item name="colorControlActivated">?colorAccent</item> <item name="colorControlActivated">?colorAccent</item>
<item name="warning">@color/rust</item> <item name="warning">@color/rust</item>
<item name="danger">@color/danger_light</item> <item name="danger">@color/danger_light</item>
<item name="disabled">@color/classic_dark_5</item>
<item name="android:textColorPrimary">@color/ocean_light_1</item> <item name="android:textColorPrimary">@color/ocean_light_1</item>
<item name="android:textColorSecondary">@color/ocean_light_2</item> <item name="android:textColorSecondary">@color/ocean_light_2</item>
<item name="android:textColorTertiary">@color/ocean_light_2</item> <item name="android:textColorTertiary">@color/ocean_light_2</item>
@ -596,6 +600,7 @@
<item name="elementBorderColor">@color/classic_dark_3</item> <item name="elementBorderColor">@color/classic_dark_3</item>
<item name="warning">@color/accent_orange</item> <item name="warning">@color/accent_orange</item>
<item name="danger">@color/danger_dark</item> <item name="danger">@color/danger_dark</item>
<item name="disabled">@color/classic_light_1</item>
<item name="textColorAlert">@color/classic_dark_6</item> <item name="textColorAlert">@color/classic_dark_6</item>
<!-- Home screen --> <!-- Home screen -->

@ -2,6 +2,14 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/callsVoiceAndVideoBeta">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_call_notifications_enabled"
android:title="@string/callsVoiceAndVideo"
android:summary="@string/callsVoiceAndVideoToggleDescription"/>
</PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:title="@string/screenSecurity"> android:title="@string/screenSecurity">
@ -46,14 +54,6 @@
android:title="@string/linkPreviewsSend" /> android:title="@string/linkPreviewsSend" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/callsVoiceAndVideoBeta">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_call_notifications_enabled"
android:title="@string/callsVoiceAndVideo"
android:summary="@string/callsVoiceAndVideoToggleDescription"/>
</PreferenceCategory>
<!-- <PreferenceCategory android:layout="@layout/preference_divider"/> <!-- <PreferenceCategory android:layout="@layout/preference_divider"/>
<PreferenceCategory android:title="@string/preferences_communication__category_sealed_sender"> <PreferenceCategory android:title="@string/preferences_communication__category_sealed_sender">

@ -62,6 +62,8 @@ object SnodeAPI {
get() = SnodeModule.shared.broadcaster get() = SnodeModule.shared.broadcaster
private var snodeFailureCount: MutableMap<Snode, Int> = mutableMapOf() private var snodeFailureCount: MutableMap<Snode, Int> = mutableMapOf()
// the list of "generic" nodes we use to make non swarm specific api calls
internal var snodePool: Set<Snode> internal var snodePool: Set<Snode>
get() = database.getSnodePool() get() = database.getSnodePool()
set(newValue) { database.setSnodePool(newValue) } set(newValue) { database.setSnodePool(newValue) }
@ -306,6 +308,7 @@ object SnodeAPI {
} }
}.unwrap() }.unwrap()
// the list of snodes that represent the swarm for that pubkey
fun getSwarm(publicKey: String): Promise<Set<Snode>, Exception> = fun getSwarm(publicKey: String): Promise<Set<Snode>, Exception> =
database.getSwarm(publicKey)?.takeIf { it.size >= minimumSwarmSnodeCount }?.let(Promise.Companion::of) database.getSwarm(publicKey)?.takeIf { it.size >= minimumSwarmSnodeCount }?.let(Promise.Companion::of)
?: getRandomSnode().bind { ?: getRandomSnode().bind {

Loading…
Cancel
Save