Call Link single-user request sheet.
This commit is contained in:
parent
e41accf52d
commit
92b0ebb6f6
7 changed files with 350 additions and 0 deletions
|
@ -73,6 +73,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.participantslist.CallParticipantsListDialog;
|
||||
import org.thoughtcrime.securesms.components.webrtc.requests.CallLinkIncomingRequestSheet;
|
||||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
|
@ -1088,6 +1089,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
public void onLaunchPendingRequestsSheet() {
|
||||
new PendingParticipantsBottomSheet().show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLaunchRecipientSheet(@NonNull Recipient pendingRecipient) {
|
||||
CallLinkIncomingRequestSheet.show(getSupportFragmentManager(), pendingRecipient.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private class WindowLayoutInfoConsumer implements Consumer<WindowLayoutInfo> {
|
||||
|
|
|
@ -53,6 +53,8 @@ class PendingParticipantsView @JvmOverloads constructor(
|
|||
|
||||
val firstRecipient: Recipient = unresolvedPendingParticipants.first()
|
||||
avatar.setAvatar(firstRecipient)
|
||||
avatar.setOnClickListener { listener?.onLaunchRecipientSheet(firstRecipient) }
|
||||
|
||||
name.text = firstRecipient.getShortDisplayName(context)
|
||||
|
||||
allow.setOnClickListener { listener?.onAllowPendingRecipient(firstRecipient) }
|
||||
|
@ -70,6 +72,11 @@ class PendingParticipantsView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
interface Listener {
|
||||
/**
|
||||
* Display the sheet containing the request for the top level participant
|
||||
*/
|
||||
fun onLaunchRecipientSheet(pendingRecipient: Recipient)
|
||||
|
||||
/**
|
||||
* Given recipient should be admitted to the call
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.thoughtcrime.securesms.components.webrtc.requests
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.contacts.paged.GroupsInCommon
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
class CallLinkIncomingRequestRepository {
|
||||
|
||||
fun getGroupsInCommon(recipientId: RecipientId): Observable<GroupsInCommon> {
|
||||
return Recipient.observable(recipientId).flatMapSingle { recipient ->
|
||||
if (recipient.hasGroupsInCommon()) {
|
||||
Single.fromCallable {
|
||||
val groupsInCommon = SignalDatabase.groups.getGroupsContainingMember(recipient.id, true)
|
||||
val total = groupsInCommon.size
|
||||
val names = groupsInCommon.take(2).map { it.title!! }
|
||||
GroupsInCommon(total, names)
|
||||
}.observeOn(Schedulers.io())
|
||||
} else {
|
||||
Single.just(GroupsInCommon(0, listOf()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.webrtc.requests
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
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.rxjava3.subscribeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
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.os.bundleOf
|
||||
import androidx.fragment.app.FragmentManager
|
||||
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.getParcelableCompat
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
|
||||
/**
|
||||
* Displayed when the user presses the user avatar in the call link join request
|
||||
* bar.
|
||||
*/
|
||||
class CallLinkIncomingRequestSheet : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
companion object {
|
||||
private const val RECIPIENT_ID = "recipient_id"
|
||||
|
||||
@JvmStatic
|
||||
fun show(fragmentManager: FragmentManager, recipientId: RecipientId) {
|
||||
CallLinkIncomingRequestSheet().apply {
|
||||
arguments = bundleOf(
|
||||
RECIPIENT_ID to recipientId
|
||||
)
|
||||
}.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isDarkTheme(): Boolean = true
|
||||
|
||||
private val recipientId: RecipientId by lazy {
|
||||
requireArguments().getParcelableCompat(RECIPIENT_ID, RecipientId::class.java)!!
|
||||
}
|
||||
|
||||
private val viewModel by viewModel {
|
||||
CallLinkIncomingRequestViewModel(recipientId)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
val state = viewModel.observeState(LocalContext.current).subscribeAsState(initial = CallLinkIncomingRequestState())
|
||||
if (state.value.recipient == Recipient.UNKNOWN) {
|
||||
return
|
||||
}
|
||||
|
||||
CallLinkIncomingRequestSheetContent(
|
||||
state = state.value,
|
||||
onApproveEntry = this::onApproveEntry,
|
||||
onDenyEntry = this::onDenyEntry
|
||||
)
|
||||
}
|
||||
|
||||
private fun onApproveEntry() {
|
||||
ApplicationDependencies.getSignalCallManager().setCallLinkJoinRequestAccepted(recipientId)
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
private fun onDenyEntry() {
|
||||
ApplicationDependencies.getSignalCallManager().setCallLinkJoinRequestRejected(recipientId)
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun CallLinkIncomingRequestSheetContentPreview() {
|
||||
SignalTheme(isDarkMode = true) {
|
||||
Surface {
|
||||
CallLinkIncomingRequestSheetContent(
|
||||
state = CallLinkIncomingRequestState(
|
||||
name = "Miles Morales",
|
||||
subtitle = "+1 (555) 555-5555",
|
||||
groupsInCommon = "Member of Webheads",
|
||||
isSystemContact = true
|
||||
),
|
||||
onApproveEntry = {},
|
||||
onDenyEntry = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CallLinkIncomingRequestSheetContent(
|
||||
state: CallLinkIncomingRequestState,
|
||||
onApproveEntry: () -> Unit,
|
||||
onDenyEntry: () -> Unit
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
item { BottomSheets.Handle() }
|
||||
item { Avatar(state.recipient) }
|
||||
item {
|
||||
Title(
|
||||
recipientName = state.name,
|
||||
isSystemContact = state.isSystemContact
|
||||
)
|
||||
}
|
||||
|
||||
if (state.subtitle.isNotEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = state.subtitle,
|
||||
modifier = Modifier.padding(4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.groupsInCommon.isNotEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = state.groupsInCommon,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(6.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Dividers.Default()
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = stringResource(id = R.string.CallLinkIncomingRequestSheet__approve_entry),
|
||||
icon = ImageVector.vectorResource(R.drawable.symbol_check_circle_24),
|
||||
onClick = onApproveEntry
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = stringResource(id = R.string.CallLinkIncomingRequestSheet__deny_entry),
|
||||
icon = ImageVector.vectorResource(R.drawable.symbol_x_circle_24),
|
||||
onClick = onDenyEntry
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.size(32.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Avatar(
|
||||
recipient: Recipient
|
||||
) {
|
||||
if (LocalInspectionMode.current) {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.padding(top = 13.dp)
|
||||
.size(80.dp)
|
||||
.background(color = Color.Red, shape = CircleShape)
|
||||
)
|
||||
} else {
|
||||
AndroidView(
|
||||
factory = ::AvatarImageView,
|
||||
modifier = Modifier
|
||||
.size(80.dp)
|
||||
.padding(top = 13.dp)
|
||||
) {
|
||||
it.setAvatarUsingProfile(recipient)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Title(
|
||||
recipientName: String,
|
||||
isSystemContact: Boolean
|
||||
) {
|
||||
if (isSystemContact) {
|
||||
Row(modifier = Modifier.padding(top = 12.dp)) {
|
||||
Text(
|
||||
text = recipientName,
|
||||
style = MaterialTheme.typography.headlineMedium
|
||||
)
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.symbol_person_circle_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(start = 6.dp)
|
||||
.align(CenterVertically)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = recipientName,
|
||||
style = MaterialTheme.typography.headlineMedium
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.webrtc.requests
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
data class CallLinkIncomingRequestState(
|
||||
val recipient: Recipient = Recipient.UNKNOWN,
|
||||
val name: String = "",
|
||||
val isSystemContact: Boolean = false,
|
||||
val subtitle: String = "",
|
||||
@Stable val groupsInCommon: String = ""
|
||||
)
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.webrtc.requests
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
|
||||
class CallLinkIncomingRequestViewModel(
|
||||
private val recipientId: RecipientId
|
||||
) : ViewModel() {
|
||||
|
||||
private val repository = CallLinkIncomingRequestRepository()
|
||||
private val store = RxStore(CallLinkIncomingRequestState())
|
||||
private val disposables = CompositeDisposable().apply {
|
||||
add(store)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.dispose()
|
||||
}
|
||||
|
||||
fun observeState(context: Context): Flowable<CallLinkIncomingRequestState> {
|
||||
disposables += store.update(Recipient.observable(recipientId).toFlowable(BackpressureStrategy.LATEST)) { r, s ->
|
||||
s.copy(
|
||||
recipient = r,
|
||||
name = r.getShortDisplayName(context),
|
||||
subtitle = r.e164.orElse(""),
|
||||
isSystemContact = r.isSystemContact
|
||||
)
|
||||
}
|
||||
|
||||
disposables += store.update(repository.getGroupsInCommon(recipientId).toFlowable(BackpressureStrategy.LATEST)) { g, s ->
|
||||
s.copy(groupsInCommon = g.toDisplayText(context))
|
||||
}
|
||||
|
||||
return store.stateFlowable
|
||||
}
|
||||
}
|
|
@ -6099,6 +6099,12 @@
|
|||
<!-- Displayed when we copy the call link to the clipboard -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__copied_to_clipboard">Copied to clipboard</string>
|
||||
|
||||
<!-- CallLinkIncomingRequestSheet -->
|
||||
<!-- Displayed as line item in sheet for approving or denying a single user -->
|
||||
<string name="CallLinkIncomingRequestSheet__approve_entry">Approve entry</string>
|
||||
<!-- Displayed as line item in sheet for approving or denying a single user -->
|
||||
<string name="CallLinkIncomingRequestSheet__deny_entry">Deny entry</string>
|
||||
|
||||
<!-- EditCallLinkNameDialogFragment -->
|
||||
<!-- App bar title for editing a call name -->
|
||||
<string name="EditCallLinkNameDialogFragment__edit_call_name">Edit call name</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue