Ensure call links UX is still available post new calling features.
This commit is contained in:
parent
b55a9f253e
commit
624f863da4
15 changed files with 287 additions and 411 deletions
|
@ -57,14 +57,12 @@ import org.signal.core.util.logging.Log;
|
|||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
|
||||
import org.thoughtcrime.securesms.components.webrtc.CallLinkInfoSheet;
|
||||
import org.thoughtcrime.securesms.components.webrtc.CallLinkProfileKeySender;
|
||||
import org.thoughtcrime.securesms.components.webrtc.CallOverflowPopupWindow;
|
||||
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow;
|
||||
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
|
||||
import org.thoughtcrime.securesms.components.webrtc.CallStateUpdatePopupWindow;
|
||||
import org.thoughtcrime.securesms.components.webrtc.CallToastPopupWindow;
|
||||
import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoViewModel;
|
||||
import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeNotificationUtil;
|
||||
import org.thoughtcrime.securesms.components.webrtc.InCallStatus;
|
||||
import org.thoughtcrime.securesms.components.webrtc.PendingParticipantsBottomSheet;
|
||||
|
@ -76,6 +74,7 @@ import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
|
|||
import org.thoughtcrime.securesms.components.webrtc.WebRtcControls;
|
||||
import org.thoughtcrime.securesms.components.webrtc.WifiToCellularPopupWindow;
|
||||
import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoController;
|
||||
import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoViewModel;
|
||||
import org.thoughtcrime.securesms.components.webrtc.participantslist.CallParticipantsListDialog;
|
||||
import org.thoughtcrime.securesms.components.webrtc.requests.CallLinkIncomingRequestSheet;
|
||||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
||||
|
@ -84,7 +83,6 @@ import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
|||
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet;
|
||||
|
@ -198,7 +196,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
initializeViewModel(isLandscapeEnabled);
|
||||
initializePictureInPictureParams();
|
||||
|
||||
controlsAndInfo = new ControlsAndInfoController(callScreen, callOverflowPopupWindow, viewModel, controlsAndInfoViewModel);
|
||||
controlsAndInfo = new ControlsAndInfoController(this, callScreen, callOverflowPopupWindow, viewModel, controlsAndInfoViewModel);
|
||||
controlsAndInfo.addVisibilityListener(new FadeCallback());
|
||||
|
||||
fullscreenHelper.showAndHideWithSystemUI(getWindow(), findViewById(R.id.webrtc_call_view_toolbar_text), findViewById(R.id.webrtc_call_view_toolbar_no_text));
|
||||
|
@ -868,6 +866,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
|
||||
viewModel.setRecipient(event.getRecipient());
|
||||
callScreen.setRecipient(event.getRecipient());
|
||||
controlsAndInfoViewModel.setRecipient(event.getRecipient());
|
||||
|
||||
switch (event.getState()) {
|
||||
case CALL_PRE_JOIN:
|
||||
|
@ -1102,13 +1101,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
|
||||
@Override
|
||||
public void onCallInfoClicked() {
|
||||
LiveRecipient liveRecipient = viewModel.getRecipient();
|
||||
|
||||
if (liveRecipient.get().isCallLink()) {
|
||||
CallLinkInfoSheet.show(getSupportFragmentManager(), liveRecipient.get().requireCallLinkRoomId());
|
||||
} else {
|
||||
controlsAndInfo.showCallInfo();
|
||||
}
|
||||
controlsAndInfo.showCallInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -119,7 +119,8 @@ fun SignalCallRow(
|
|||
.align(CenterVertically)
|
||||
) {
|
||||
Text(
|
||||
text = callLink.state.name.ifEmpty { stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__signal_call) }
|
||||
text = callLink.state.name.ifEmpty { stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__signal_call) },
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Text(
|
||||
text = callUrl,
|
||||
|
|
|
@ -92,6 +92,7 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
|
|||
Text(
|
||||
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__create_call_link),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
|
|
@ -9,7 +9,6 @@ import androidx.compose.runtime.Immutable
|
|||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
|
||||
@Immutable
|
||||
@Deprecated("Merge with ControlsAndInfoState")
|
||||
data class CallLinkDetailsState(
|
||||
val displayRevocationDialog: Boolean = false,
|
||||
val callLink: CallLinkTable.CallLink? = null
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
|||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.UpdateCallLinkResult
|
||||
|
||||
@Deprecated("Merge with ControlsAndInfoViewModel")
|
||||
class CallLinkDetailsViewModel(
|
||||
callLinkRoomId: CallLinkRoomId,
|
||||
repository: CallLinkDetailsRepository = CallLinkDetailsRepository(),
|
||||
|
|
|
@ -1,372 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.webrtc
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.os.BundleCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.toLiveData
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.signal.core.ui.BottomSheets
|
||||
import org.signal.core.ui.Dividers
|
||||
import org.signal.core.ui.Rows
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.concurrent.addTo
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.ringrtc.CallLinkState.Restrictions
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.calls.links.CallLinks
|
||||
import org.thoughtcrime.securesms.calls.links.EditCallLinkNameDialogFragment
|
||||
import org.thoughtcrime.securesms.calls.links.EditCallLinkNameDialogFragmentArgs
|
||||
import org.thoughtcrime.securesms.calls.links.SignalCallRow
|
||||
import org.thoughtcrime.securesms.calls.links.details.CallLinkDetailsViewModel
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.events.CallParticipant
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.UpdateCallLinkResult
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
|
||||
/**
|
||||
* Displays information about the in-progress CallLink call from
|
||||
* within WebRtcActivity. If the user is able to modify call link
|
||||
* state, provides options to do so.
|
||||
*/
|
||||
@Deprecated("Merge with CallInfoView")
|
||||
class CallLinkInfoSheet : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = Log.tag(CallLinkInfoSheet::class.java)
|
||||
private const val CALL_LINK_ROOM_ID = "call_link_room_id"
|
||||
|
||||
@JvmStatic
|
||||
fun show(fragmentManager: FragmentManager, callLinkRoomId: CallLinkRoomId) {
|
||||
CallLinkInfoSheet().apply {
|
||||
arguments = bundleOf(CALL_LINK_ROOM_ID to callLinkRoomId)
|
||||
}.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
override val forceDarkTheme = true
|
||||
|
||||
private val webRtcCallViewModel: WebRtcCallViewModel by activityViewModels()
|
||||
private val callLinkDetailsViewModel: CallLinkDetailsViewModel by viewModels(factoryProducer = {
|
||||
CallLinkDetailsViewModel.Factory(BundleCompat.getParcelable(requireArguments(), CALL_LINK_ROOM_ID, CallLinkRoomId::class.java)!!)
|
||||
})
|
||||
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
lifecycleDisposable.bindTo(viewLifecycleOwner)
|
||||
parentFragmentManager.setFragmentResultListener(EditCallLinkNameDialogFragment.RESULT_KEY, viewLifecycleOwner) { resultKey, bundle ->
|
||||
if (bundle.containsKey(resultKey)) {
|
||||
setName(bundle.getString(resultKey)!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
val callLinkDetailsState by callLinkDetailsViewModel.state
|
||||
|
||||
val participants: List<CallParticipant> by webRtcCallViewModel.callParticipantsState
|
||||
.toFlowable(BackpressureStrategy.LATEST)
|
||||
.map { state -> state.allRemoteParticipants }
|
||||
.toLiveData()
|
||||
.observeAsState(emptyList())
|
||||
|
||||
val onEditNameClicked: () -> Unit = remember(callLinkDetailsState) {
|
||||
{
|
||||
EditCallLinkNameDialogFragment().apply {
|
||||
arguments = EditCallLinkNameDialogFragmentArgs.Builder(callLinkDetailsState.callLink?.state?.name ?: "").build().toBundle()
|
||||
}.show(parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
||||
val callLink = callLinkDetailsState.callLink
|
||||
if (callLink != null) {
|
||||
Sheet(
|
||||
callLink = callLink,
|
||||
participants = participants,
|
||||
onShareLinkClicked = this::shareLink,
|
||||
onEditNameClicked = onEditNameClicked,
|
||||
onToggleAdminApprovalClicked = this::onApproveAllMembersChanged,
|
||||
onBlock = this::onBlockParticipant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onBlockParticipant(callParticipant: CallParticipant) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setMessage(getString(R.string.CallLinkInfoSheet__remove_s_from_the_call, callParticipant.recipient.getShortDisplayName(requireContext())))
|
||||
.setPositiveButton(R.string.CallLinkInfoSheet__remove) { _, _ ->
|
||||
ApplicationDependencies.getSignalCallManager().removeFromCallLink(callParticipant)
|
||||
}
|
||||
.setNeutralButton(R.string.CallLinkInfoSheet__block_from_call) { _, _ ->
|
||||
ApplicationDependencies.getSignalCallManager().blockFromCallLink(callParticipant)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun onApproveAllMembersChanged(checked: Boolean) {
|
||||
callLinkDetailsViewModel.setApproveAllMembers(checked)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy(onSuccess = {
|
||||
if (it !is UpdateCallLinkResult.Success) {
|
||||
Log.w(TAG, "Failed to change restrictions. $it")
|
||||
toastFailure()
|
||||
}
|
||||
}, onError = handleError("onApproveAllMembersChanged"))
|
||||
.addTo(lifecycleDisposable)
|
||||
}
|
||||
|
||||
private fun shareLink() {
|
||||
val mimeType = Intent.normalizeMimeType("text/plain")
|
||||
val shareIntent = ShareCompat.IntentBuilder(requireContext())
|
||||
.setText(CallLinks.url(callLinkDetailsViewModel.rootKeySnapshot))
|
||||
.setType(mimeType)
|
||||
.createChooserIntent()
|
||||
|
||||
try {
|
||||
startActivity(shareIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(requireContext(), R.string.CreateCallLinkBottomSheetDialogFragment__failed_to_open_share_sheet, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setName(name: String) {
|
||||
callLinkDetailsViewModel.setName(name)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy(
|
||||
onSuccess = {
|
||||
if (it !is UpdateCallLinkResult.Success) {
|
||||
Log.w(TAG, "Failed to set name. $it")
|
||||
toastFailure()
|
||||
}
|
||||
},
|
||||
onError = handleError("setName")
|
||||
)
|
||||
.addTo(lifecycleDisposable)
|
||||
}
|
||||
|
||||
private fun handleError(method: String): (throwable: Throwable) -> Unit {
|
||||
return {
|
||||
Log.w(TAG, "Failure during $method", it)
|
||||
toastFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toastFailure() {
|
||||
Toast.makeText(requireContext(), R.string.CallLinkDetailsFragment__couldnt_save_changes, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SheetPreview() {
|
||||
SignalTheme(isDarkMode = true) {
|
||||
Surface {
|
||||
Sheet(
|
||||
callLink = CallLinkTable.CallLink(
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = CallLinkRoomId.fromBytes(byteArrayOf(1, 2, 3, 4, 5)),
|
||||
credentials = CallLinkCredentials(
|
||||
linkKeyBytes = byteArrayOf(1, 2, 3, 4, 5),
|
||||
adminPassBytes = byteArrayOf(1, 2, 3, 4, 5)
|
||||
),
|
||||
state = SignalCallLinkState()
|
||||
),
|
||||
participants = listOf(CallParticipant(recipient = Recipient.UNKNOWN)).toImmutableList(),
|
||||
onShareLinkClicked = {},
|
||||
onEditNameClicked = {},
|
||||
onToggleAdminApprovalClicked = {},
|
||||
onBlock = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Sheet(
|
||||
callLink: CallLinkTable.CallLink,
|
||||
participants: List<CallParticipant>,
|
||||
onShareLinkClicked: () -> Unit,
|
||||
onEditNameClicked: () -> Unit,
|
||||
onToggleAdminApprovalClicked: (Boolean) -> Unit,
|
||||
onBlock: (CallParticipant) -> Unit
|
||||
) {
|
||||
LazyColumn(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
item {
|
||||
BottomSheets.Handle()
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.CallLinkInfoSheet__call_info),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(vertical = 24.dp)
|
||||
)
|
||||
|
||||
SignalCallRow(callLink = callLink, onJoinClicked = null)
|
||||
Rows.TextRow(
|
||||
text = stringResource(id = R.string.CallLinkDetailsFragment__share_link),
|
||||
icon = ImageVector.vectorResource(id = R.drawable.symbol_link_24),
|
||||
iconModifier = Modifier
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = CircleShape
|
||||
)
|
||||
.size(42.dp)
|
||||
.padding(9.dp),
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minHeight = 64.dp)
|
||||
.clickable(onClick = onShareLinkClicked)
|
||||
)
|
||||
}
|
||||
|
||||
items(participants, { it.callParticipantId }, { null }) {
|
||||
CallLinkMemberRow(
|
||||
callParticipant = it,
|
||||
isSelfAdmin = callLink.credentials?.adminPassBytes != null,
|
||||
onBlockClicked = onBlock
|
||||
)
|
||||
}
|
||||
|
||||
if (callLink.credentials?.adminPassBytes != null) {
|
||||
item {
|
||||
Dividers.Default()
|
||||
Rows.TextRow(
|
||||
text = stringResource(id = R.string.CallLinkDetailsFragment__add_call_name),
|
||||
modifier = Modifier.clickable(onClick = onEditNameClicked)
|
||||
)
|
||||
Rows.ToggleRow(
|
||||
checked = callLink.state.restrictions == Restrictions.ADMIN_APPROVAL,
|
||||
text = stringResource(id = R.string.CallLinkDetailsFragment__approve_all_members),
|
||||
onCheckChanged = onToggleAdminApprovalClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun CallLinkMemberRowPreview() {
|
||||
SignalTheme(isDarkMode = true) {
|
||||
Surface {
|
||||
CallLinkMemberRow(
|
||||
CallParticipant(recipient = Recipient.UNKNOWN),
|
||||
isSelfAdmin = true,
|
||||
{}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CallLinkMemberRow(
|
||||
callParticipant: CallParticipant,
|
||||
isSelfAdmin: Boolean,
|
||||
onBlockClicked: (CallParticipant) -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(Rows.defaultPadding())
|
||||
) {
|
||||
val recipient by ((if (LocalInspectionMode.current) Observable.just(Recipient.UNKNOWN) else Recipient.observable(callParticipant.recipient.id)))
|
||||
.toFlowable(BackpressureStrategy.LATEST)
|
||||
.toLiveData()
|
||||
.observeAsState(initial = callParticipant.recipient)
|
||||
|
||||
if (LocalInspectionMode.current) {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.background(color = Color.Red, shape = CircleShape)
|
||||
)
|
||||
} else {
|
||||
AndroidView(
|
||||
factory = ::AvatarImageView,
|
||||
modifier = Modifier.size(40.dp)
|
||||
) {
|
||||
it.setAvatarUsingProfile(recipient)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(24.dp))
|
||||
|
||||
Text(
|
||||
text = recipient.getShortDisplayName(LocalContext.current),
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
|
||||
if (isSelfAdmin && !recipient.isSelf) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.symbol_minus_circle_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.clickable(onClick = { onBlockClicked(callParticipant) })
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -840,6 +840,12 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
|
|||
ConstraintSet.TOP,
|
||||
ViewUtil.dpToPx(layoutPositions.reactionBottomMargin));
|
||||
|
||||
constraintSet.connect(pendingParticipantsViewStub.getId(),
|
||||
ConstraintSet.BOTTOM,
|
||||
layoutPositions.reactionBottomViewId,
|
||||
ConstraintSet.TOP,
|
||||
ViewUtil.dpToPx(layoutPositions.reactionBottomMargin));
|
||||
|
||||
constraintSet.applyTo(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rxjava3.subscribeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -46,9 +47,12 @@ import androidx.lifecycle.toLiveData
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import org.signal.core.ui.Dividers
|
||||
import org.signal.core.ui.Rows
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.ringrtc.CallLinkState
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.calls.links.SignalCallRow
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
|
@ -65,7 +69,12 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
|||
object CallInfoView {
|
||||
|
||||
@Composable
|
||||
fun View(webRtcCallViewModel: WebRtcCallViewModel, controlsAndInfoViewModel: ControlsAndInfoViewModel, modifier: Modifier) {
|
||||
fun View(
|
||||
webRtcCallViewModel: WebRtcCallViewModel,
|
||||
controlsAndInfoViewModel: ControlsAndInfoViewModel,
|
||||
callbacks: Callbacks,
|
||||
modifier: Modifier
|
||||
) {
|
||||
val participantsState: ParticipantsState by webRtcCallViewModel.callParticipantsState
|
||||
.toFlowable(BackpressureStrategy.LATEST)
|
||||
.map { state ->
|
||||
|
@ -85,14 +94,35 @@ object CallInfoView {
|
|||
|
||||
val controlAndInfoState: ControlAndInfoState by controlsAndInfoViewModel.state
|
||||
|
||||
val onEditNameClicked: () -> Unit = remember(controlAndInfoState) {
|
||||
{
|
||||
callbacks.onEditNameClicked(controlAndInfoState.callLink?.state?.name ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
SignalTheme(
|
||||
isDarkMode = true
|
||||
) {
|
||||
Surface {
|
||||
CallInfo(participantsState = participantsState, controlAndInfoState = controlAndInfoState, modifier = modifier)
|
||||
CallInfo(
|
||||
participantsState = participantsState,
|
||||
controlAndInfoState = controlAndInfoState,
|
||||
onShareLinkClicked = callbacks::onShareLinkClicked,
|
||||
onEditNameClicked = onEditNameClicked,
|
||||
onToggleAdminApprovalClicked = callbacks::onToggleAdminApprovalClicked,
|
||||
onBlock = callbacks::onBlock,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Callbacks {
|
||||
fun onShareLinkClicked()
|
||||
fun onEditNameClicked(name: String)
|
||||
fun onToggleAdminApprovalClicked(checked: Boolean)
|
||||
fun onBlock(callParticipant: CallParticipant)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
@ -103,7 +133,11 @@ private fun CallInfoPreview() {
|
|||
val remoteParticipants = listOf(CallParticipant(recipient = Recipient.UNKNOWN))
|
||||
CallInfo(
|
||||
participantsState = ParticipantsState(remoteParticipants = remoteParticipants, raisedHands = remoteParticipants.map { GroupCallRaiseHandEvent(it.recipient, System.currentTimeMillis()) }),
|
||||
controlAndInfoState = ControlAndInfoState()
|
||||
controlAndInfoState = ControlAndInfoState(),
|
||||
onShareLinkClicked = { },
|
||||
onEditNameClicked = { },
|
||||
onToggleAdminApprovalClicked = { },
|
||||
onBlock = { }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +147,10 @@ private fun CallInfoPreview() {
|
|||
private fun CallInfo(
|
||||
participantsState: ParticipantsState,
|
||||
controlAndInfoState: ControlAndInfoState,
|
||||
onShareLinkClicked: () -> Unit,
|
||||
onEditNameClicked: () -> Unit,
|
||||
onToggleAdminApprovalClicked: (Boolean) -> Unit,
|
||||
onBlock: (CallParticipant) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
|
@ -134,6 +172,29 @@ private fun CallInfo(
|
|||
)
|
||||
}
|
||||
|
||||
if (controlAndInfoState.callLink != null) {
|
||||
item {
|
||||
SignalCallRow(callLink = controlAndInfoState.callLink, onJoinClicked = null)
|
||||
|
||||
Rows.TextRow(
|
||||
text = stringResource(id = R.string.CallLinkDetailsFragment__share_link),
|
||||
icon = ImageVector.vectorResource(id = R.drawable.symbol_link_24),
|
||||
iconModifier = Modifier
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = CircleShape
|
||||
)
|
||||
.size(42.dp)
|
||||
.padding(9.dp),
|
||||
onClick = onShareLinkClicked,
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minHeight = 64.dp)
|
||||
)
|
||||
|
||||
Dividers.Default()
|
||||
}
|
||||
}
|
||||
|
||||
if (participantsState.raisedHands.isNotEmpty()) {
|
||||
item {
|
||||
Box(
|
||||
|
@ -157,21 +218,30 @@ private fun CallInfo(
|
|||
) {
|
||||
HandRaisedRow(recipient = it.sender)
|
||||
}
|
||||
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 24.dp)
|
||||
.defaultMinSize(minHeight = 52.dp)
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
Text(
|
||||
text = getCallSheetLabel(participantsState),
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
var includeAdminControlsDivider = true
|
||||
if (controlAndInfoState.callLink == null || participantsState.isOngoing()) {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 24.dp)
|
||||
.defaultMinSize(minHeight = 52.dp)
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
Text(
|
||||
text = getCallSheetLabel(participantsState),
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
includeAdminControlsDivider = false
|
||||
}
|
||||
|
||||
if (!participantsState.inCallLobby || participantsState.isOngoing()) {
|
||||
|
@ -182,8 +252,8 @@ private fun CallInfo(
|
|||
) {
|
||||
CallParticipantRow(
|
||||
callParticipant = it,
|
||||
isSelfAdmin = false,
|
||||
onBlockClicked = {}
|
||||
isSelfAdmin = controlAndInfoState.isSelfAdmin() && !participantsState.inCallLobby,
|
||||
onBlockClicked = onBlock
|
||||
)
|
||||
}
|
||||
} else if (participantsState.isGroupCall()) {
|
||||
|
@ -197,7 +267,7 @@ private fun CallInfo(
|
|||
isSelfAdmin = false
|
||||
)
|
||||
}
|
||||
} else {
|
||||
} else if (controlAndInfoState.callLink == null) {
|
||||
item {
|
||||
CallParticipantRow(
|
||||
initialRecipient = participantsState.callRecipient,
|
||||
|
@ -213,6 +283,24 @@ private fun CallInfo(
|
|||
}
|
||||
}
|
||||
|
||||
if (controlAndInfoState.callLink?.credentials?.adminPassBytes != null) {
|
||||
item {
|
||||
if (includeAdminControlsDivider) {
|
||||
Dividers.Default()
|
||||
}
|
||||
|
||||
Rows.TextRow(
|
||||
text = stringResource(id = R.string.CallLinkDetailsFragment__add_call_name),
|
||||
onClick = onEditNameClicked
|
||||
)
|
||||
Rows.ToggleRow(
|
||||
checked = controlAndInfoState.callLink.state.restrictions == CallLinkState.Restrictions.ADMIN_APPROVAL,
|
||||
text = stringResource(id = R.string.CallLinkDetailsFragment__approve_all_members),
|
||||
onCheckChanged = onToggleAdminApprovalClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.size(48.dp))
|
||||
}
|
||||
|
|
|
@ -6,8 +6,14 @@
|
|||
package org.thoughtcrime.securesms.components.webrtc.controls
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
|
||||
@Immutable
|
||||
data class ControlAndInfoState(
|
||||
val callLink: CallLinkTable.CallLink? = null,
|
||||
val resetScrollState: Long = 0
|
||||
)
|
||||
) {
|
||||
fun isSelfAdmin(): Boolean {
|
||||
return callLink?.credentials?.adminPassBytes != null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
|
||||
package org.thoughtcrime.securesms.components.webrtc.controls
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Handler
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.Px
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -20,6 +23,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
|||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.constraintlayout.widget.Guideline
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
|
@ -27,18 +31,30 @@ import androidx.transition.TransitionSet
|
|||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehaviorHack
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.shape.CornerFamily
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.google.android.material.shape.ShapeAppearanceModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.kotlin.addTo
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.dp
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.WebRtcCallActivity
|
||||
import org.thoughtcrime.securesms.calls.links.CallLinks
|
||||
import org.thoughtcrime.securesms.calls.links.EditCallLinkNameDialogFragment
|
||||
import org.thoughtcrime.securesms.calls.links.EditCallLinkNameDialogFragmentArgs
|
||||
import org.thoughtcrime.securesms.components.InsetAwareConstraintLayout
|
||||
import org.thoughtcrime.securesms.components.webrtc.CallOverflowPopupWindow
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcControls
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.events.CallParticipant
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.UpdateCallLinkResult
|
||||
import org.thoughtcrime.securesms.util.padding
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
import kotlin.math.max
|
||||
|
@ -48,13 +64,16 @@ import kotlin.time.Duration.Companion.seconds
|
|||
* Brain for rendering the call controls and info within a bottom sheet.
|
||||
*/
|
||||
class ControlsAndInfoController(
|
||||
private val webRtcCallActivity: WebRtcCallActivity,
|
||||
private val webRtcCallView: WebRtcCallView,
|
||||
private val overflowPopupWindow: CallOverflowPopupWindow,
|
||||
private val viewModel: WebRtcCallViewModel,
|
||||
private val controlsAndInfoViewModel: ControlsAndInfoViewModel
|
||||
) : Disposable {
|
||||
) : CallInfoView.Callbacks, Disposable {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ControlsAndInfoController::class.java)
|
||||
|
||||
private const val CONTROL_FADE_OUT_START = 0f
|
||||
private const val CONTROL_FADE_OUT_DONE = 0.23f
|
||||
private const val INFO_FADE_IN_START = CONTROL_FADE_OUT_DONE
|
||||
|
@ -96,7 +115,7 @@ class ControlsAndInfoController(
|
|||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
val nestedScrollInterop = rememberNestedScrollInteropConnection()
|
||||
CallInfoView.View(viewModel, controlsAndInfoViewModel, Modifier.nestedScroll(nestedScrollInterop))
|
||||
CallInfoView.View(viewModel, controlsAndInfoViewModel, this@ControlsAndInfoController, Modifier.nestedScroll(nestedScrollInterop))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +135,7 @@ class ControlsAndInfoController(
|
|||
.setTopRightCorner(CornerFamily.ROUNDED, 18.dp.toFloat())
|
||||
.build()
|
||||
).apply {
|
||||
fillColor = ColorStateList.valueOf(ContextCompat.getColor(webRtcCallView.context, R.color.signal_colorSurface))
|
||||
fillColor = ColorStateList.valueOf(ContextCompat.getColor(webRtcCallActivity, R.color.signal_colorSurface))
|
||||
}
|
||||
|
||||
behavior.isHideable = true
|
||||
|
@ -181,6 +200,14 @@ class ControlsAndInfoController(
|
|||
overflowPopupWindow.setOnDismissListener {
|
||||
hide(delay = HIDE_CONTROL_DELAY)
|
||||
}
|
||||
|
||||
webRtcCallActivity
|
||||
.supportFragmentManager
|
||||
.setFragmentResultListener(EditCallLinkNameDialogFragment.RESULT_KEY, webRtcCallActivity) { resultKey, bundle ->
|
||||
if (bundle.containsKey(resultKey)) {
|
||||
setName(bundle.getString(resultKey)!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onControlTopChanged(composeViewSize: Int = raiseHandComposeView.height) {
|
||||
|
@ -340,6 +367,77 @@ class ControlsAndInfoController(
|
|||
return disposables.isDisposed
|
||||
}
|
||||
|
||||
override fun onShareLinkClicked() {
|
||||
val mimeType = Intent.normalizeMimeType("text/plain")
|
||||
val shareIntent = ShareCompat.IntentBuilder(webRtcCallActivity)
|
||||
.setText(CallLinks.url(controlsAndInfoViewModel.rootKeySnapshot))
|
||||
.setType(mimeType)
|
||||
.createChooserIntent()
|
||||
|
||||
try {
|
||||
webRtcCallActivity.startActivity(shareIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(webRtcCallActivity, R.string.CreateCallLinkBottomSheetDialogFragment__failed_to_open_share_sheet, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEditNameClicked(name: String) {
|
||||
EditCallLinkNameDialogFragment().apply {
|
||||
arguments = EditCallLinkNameDialogFragmentArgs.Builder(name).build().toBundle()
|
||||
}.show(webRtcCallActivity.supportFragmentManager, null)
|
||||
}
|
||||
|
||||
override fun onToggleAdminApprovalClicked(checked: Boolean) {
|
||||
controlsAndInfoViewModel.setApproveAllMembers(checked)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy(onSuccess = {
|
||||
if (it !is UpdateCallLinkResult.Success) {
|
||||
Log.w(TAG, "Failed to change restrictions. $it")
|
||||
toastFailure()
|
||||
}
|
||||
}, onError = handleError("onApproveAllMembersChanged"))
|
||||
.addTo(disposables)
|
||||
}
|
||||
|
||||
override fun onBlock(callParticipant: CallParticipant) {
|
||||
MaterialAlertDialogBuilder(webRtcCallActivity)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setMessage(webRtcCallView.resources.getString(R.string.CallLinkInfoSheet__remove_s_from_the_call, callParticipant.recipient.getShortDisplayName(webRtcCallActivity)))
|
||||
.setPositiveButton(R.string.CallLinkInfoSheet__remove) { _, _ ->
|
||||
ApplicationDependencies.getSignalCallManager().removeFromCallLink(callParticipant)
|
||||
}
|
||||
.setNeutralButton(R.string.CallLinkInfoSheet__block_from_call) { _, _ ->
|
||||
ApplicationDependencies.getSignalCallManager().blockFromCallLink(callParticipant)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun setName(name: String) {
|
||||
controlsAndInfoViewModel.setName(name)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy(
|
||||
onSuccess = {
|
||||
if (it !is UpdateCallLinkResult.Success) {
|
||||
Log.w(TAG, "Failed to set name. $it")
|
||||
toastFailure()
|
||||
}
|
||||
},
|
||||
onError = handleError("setName")
|
||||
)
|
||||
.addTo(disposables)
|
||||
}
|
||||
|
||||
private fun handleError(method: String): (throwable: Throwable) -> Unit {
|
||||
return {
|
||||
Log.w(TAG, "Failure during $method", it)
|
||||
toastFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toastFailure() {
|
||||
Toast.makeText(webRtcCallActivity, R.string.CallLinkDetailsFragment__couldnt_save_changes, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
private fun ConstraintSet.setControlConstraints(@IdRes viewId: Int, visible: Boolean, @Px horizontalMargins: Int) {
|
||||
setVisibility(viewId, if (visible) View.VISIBLE else View.GONE)
|
||||
setMargin(viewId, ConstraintSet.START, horizontalMargins)
|
||||
|
|
|
@ -8,15 +8,66 @@ package org.thoughtcrime.securesms.components.webrtc.controls
|
|||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.ringrtc.CallLinkState
|
||||
import org.thoughtcrime.securesms.calls.links.CallLinks
|
||||
import org.thoughtcrime.securesms.calls.links.UpdateCallLinkRepository
|
||||
import org.thoughtcrime.securesms.calls.links.details.CallLinkDetailsRepository
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.UpdateCallLinkResult
|
||||
|
||||
/**
|
||||
* Provides a view model communicating with the Controls and Info view, [CallInfoView].
|
||||
*/
|
||||
class ControlsAndInfoViewModel : ViewModel() {
|
||||
class ControlsAndInfoViewModel(
|
||||
private val repository: CallLinkDetailsRepository = CallLinkDetailsRepository(),
|
||||
private val mutationRepository: UpdateCallLinkRepository = UpdateCallLinkRepository()
|
||||
) : ViewModel() {
|
||||
private val disposables = CompositeDisposable()
|
||||
private var callRecipientId: RecipientId? = null
|
||||
private val _state = mutableStateOf(ControlAndInfoState())
|
||||
val state: State<ControlAndInfoState> = _state
|
||||
|
||||
val rootKeySnapshot: ByteArray
|
||||
get() = state.value.callLink?.credentials?.linkKeyBytes ?: error("Call link not loaded yet.")
|
||||
|
||||
fun setRecipient(recipient: Recipient) {
|
||||
if (recipient.isCallLink && callRecipientId != recipient.id) {
|
||||
callRecipientId = recipient.id
|
||||
disposables += repository.refreshCallLinkState(recipient.requireCallLinkRoomId())
|
||||
disposables += CallLinks.watchCallLink(recipient.requireCallLinkRoomId()).subscribeBy {
|
||||
_state.value = _state.value.copy(callLink = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
disposables.dispose()
|
||||
}
|
||||
|
||||
fun resetScrollState() {
|
||||
_state.value = _state.value.copy(resetScrollState = System.currentTimeMillis())
|
||||
}
|
||||
|
||||
fun setApproveAllMembers(approveAllMembers: Boolean): Single<UpdateCallLinkResult> {
|
||||
val credentials = _state.value.callLink?.credentials ?: error("User cannot change the name of this call.")
|
||||
return mutationRepository.setCallRestrictions(credentials, if (approveAllMembers) CallLinkState.Restrictions.ADMIN_APPROVAL else CallLinkState.Restrictions.NONE)
|
||||
}
|
||||
|
||||
fun setName(name: String): Single<UpdateCallLinkResult> {
|
||||
val credentials = _state.value.callLink?.credentials ?: error("User cannot change the name of this call.")
|
||||
return mutationRepository.setCallName(credentials, name)
|
||||
}
|
||||
|
||||
class Factory : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.cast(ControlsAndInfoViewModel()) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ sealed interface UpdateCallLinkResult {
|
|||
val state: SignalCallLinkState
|
||||
) : UpdateCallLinkResult
|
||||
|
||||
class Failure(
|
||||
data class Failure(
|
||||
val status: Short
|
||||
) : UpdateCallLinkResult
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@ public class Stub<T extends View> {
|
|||
this.viewStub = viewStub;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return (viewStub != null) ? viewStub.getId() : view.getId();
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (view == null) {
|
||||
//noinspection unchecked
|
||||
|
|
|
@ -373,7 +373,7 @@
|
|||
android:layout_marginBottom="32dp"
|
||||
android:inflatedId="@+id/call_screen_pending_recipients_view"
|
||||
android:layout="@layout/call_screen_pending_participants_view"
|
||||
app:layout_constraintBottom_toTopOf="@id/call_screen_footer_gradient_barrier"
|
||||
app:layout_constraintBottom_toTopOf="@id/call_screen_above_controls_guideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
|
|
@ -76,6 +76,7 @@ object Rows {
|
|||
fun ToggleRow(
|
||||
checked: Boolean,
|
||||
text: String,
|
||||
textColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
onCheckChanged: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
@ -86,6 +87,7 @@ object Rows {
|
|||
) {
|
||||
Text(
|
||||
text = text,
|
||||
color = textColor,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(CenterVertically)
|
||||
|
|
Loading…
Add table
Reference in a new issue