Implement proper in-call status for call links.

This commit is contained in:
Alex Hart
2023-08-10 10:41:09 -03:00
parent ee1291c816
commit 13853c708e
8 changed files with 76 additions and 40 deletions

View File

@@ -47,8 +47,10 @@ 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.kotlin.subscribeBy
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@@ -119,7 +121,11 @@ class CallLinkInfoSheet : ComposeBottomSheetDialogFragment() {
@Composable
override fun SheetContent() {
val callLinkDetailsState by callLinkDetailsViewModel.state
val callParticipantsState by webRtcCallViewModel.callParticipantsState.observeAsState()
val callParticipantsState by webRtcCallViewModel.callParticipantsState
.toFlowable(BackpressureStrategy.LATEST)
.toLiveData()
.observeAsState()
val participants: ImmutableList<CallParticipant> = if (callParticipantsState?.callState == WebRtcViewModel.State.CALL_CONNECTED) {
listOf(CallParticipant(recipient = Recipient.self())) + (callParticipantsState?.allRemoteParticipants?.map { it } ?: emptyList())
} else {

View File

@@ -16,7 +16,12 @@ sealed interface InCallStatus {
data class ElapsedTime(val elapsedTime: Long) : InCallStatus
/**
* The number of users requesting to join a call.
* The number of users requesting to join a call link.
*/
data class PendingUsers(val pendingUserCount: Int) : InCallStatus
data class PendingCallLinkUsers(val pendingUserCount: Int) : InCallStatus
/**
* The number of users in a call link.
*/
data class JoinedCallLinkUsers(val joinedUserCount: Int) : InCallStatus
}

View File

@@ -60,7 +60,7 @@ public class WebRtcCallViewModel extends ViewModel {
private final SingleLiveEvent<Event> events = new SingleLiveEvent<>();
private final BehaviorSubject<Long> elapsed = BehaviorSubject.createDefault(-1L);
private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live());
private final DefaultValueLiveData<CallParticipantsState> participantsState = new DefaultValueLiveData<>(CallParticipantsState.STARTING_STATE);
private final BehaviorSubject<CallParticipantsState> participantsState = BehaviorSubject.createDefault(CallParticipantsState.STARTING_STATE);
private final SingleLiveEvent<CallParticipantListUpdate> callParticipantListUpdate = new SingleLiveEvent<>();
private final MutableLiveData<Collection<RecipientId>> identityChangedRecipients = new MutableLiveData<>(Collections.emptyList());
private final LiveData<SafetyNumberChangeEvent> safetyNumberChangeEvent = LiveDataUtil.combineLatest(isInPipMode, identityChangedRecipients, SafetyNumberChangeEvent::new);
@@ -68,11 +68,11 @@ public class WebRtcCallViewModel extends ViewModel {
private final LiveData<List<GroupMemberEntry.FullMember>> groupMembers = Transformations.switchMap(groupRecipient, r -> Transformations.distinctUntilChanged(new LiveGroup(r.requireGroupId()).getFullMembers()));
private final LiveData<List<GroupMemberEntry.FullMember>> groupMembersChanged = LiveDataUtil.skip(groupMembers, 1);
private final LiveData<Integer> groupMemberCount = Transformations.map(groupMembers, List::size);
private final LiveData<Boolean> shouldShowSpeakerHint = Transformations.map(participantsState, this::shouldShowSpeakerHint);
private final Observable<Boolean> shouldShowSpeakerHint = participantsState.map(this::shouldShowSpeakerHint);
private final LiveData<Orientation> orientation;
private final MutableLiveData<Boolean> isLandscapeEnabled = new MutableLiveData<>();
private final LiveData<Integer> controlsRotation;
private final Observer<List<GroupMemberEntry.FullMember>> groupMemberStateUpdater = m -> participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), m));
private final Observer<List<GroupMemberEntry.FullMember>> groupMemberStateUpdater = m -> participantsState.onNext(CallParticipantsState.update(participantsState.getValue(), m));
private final MutableLiveData<WebRtcEphemeralState> ephemeralState = new MutableLiveData<>();
private final BehaviorSubject<PendingParticipantCollection> pendingParticipants = BehaviorSubject.create();
@@ -135,7 +135,7 @@ public class WebRtcCallViewModel extends ViewModel {
public void setFoldableState(@NonNull WebRtcControls.FoldableState foldableState) {
this.foldableState.postValue(foldableState);
ThreadUtil.runOnMain(() -> participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), foldableState)));
ThreadUtil.runOnMain(() -> participantsState.onNext(CallParticipantsState.update(participantsState.getValue(), foldableState)));
}
public LiveData<Event> getEvents() {
@@ -148,22 +148,31 @@ public class WebRtcCallViewModel extends ViewModel {
return Observable.combineLatest(
elapsedTime,
pendingParticipants,
(time, participants) -> {
Set<PendingParticipantCollection.Entry> pending = participants.getUnresolvedPendingParticipants();
if (pending.isEmpty()) {
participantsState,
(time, pendingParticipants, participantsState) -> {
if (!getRecipient().get().isCallLink()) {
return new InCallStatus.ElapsedTime(time);
}
Set<PendingParticipantCollection.Entry> pending = pendingParticipants.getUnresolvedPendingParticipants();
if (!pending.isEmpty()) {
return new InCallStatus.PendingCallLinkUsers(pending.size());
} else {
return new InCallStatus.PendingUsers(pending.size());
return new InCallStatus.JoinedCallLinkUsers((int) participantsState.getParticipantCount().orElse(0));
}
}
).distinctUntilChanged();
}
public LiveData<CallParticipantsState> getCallParticipantsState() {
public Observable<CallParticipantsState> getCallParticipantsState() {
return participantsState;
}
public @Nullable CallParticipantsState getCallParticipantsStateSnapshot() {
return participantsState.getValue();
}
public LiveData<CallParticipantListUpdate> getCallParticipantListUpdate() {
return callParticipantListUpdate;
}
@@ -180,7 +189,7 @@ public class WebRtcCallViewModel extends ViewModel {
return groupMemberCount;
}
public LiveData<Boolean> shouldShowSpeakerHint() {
public Observable<Boolean> shouldShowSpeakerHint() {
return shouldShowSpeakerHint;
}
@@ -216,7 +225,7 @@ public class WebRtcCallViewModel extends ViewModel {
public void setIsInPipMode(boolean isInPipMode) {
this.isInPipMode.setValue(isInPipMode);
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), isInPipMode));
participantsState.onNext(CallParticipantsState.update(participantsState.getValue(), isInPipMode));
}
public void setIsLandscapeEnabled(boolean isLandscapeEnabled) {
@@ -239,7 +248,7 @@ public class WebRtcCallViewModel extends ViewModel {
events.setValue(new Event.ShowSwipeToSpeakerHint());
}
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), page));
participantsState.onNext(CallParticipantsState.update(participantsState.getValue(), page));
}
public void onLocalPictureInPictureClicked() {
@@ -248,8 +257,8 @@ public class WebRtcCallViewModel extends ViewModel {
return;
}
participantsState.setValue(CallParticipantsState.setExpanded(participantsState.getValue(),
state.getLocalRenderState() != WebRtcLocalRenderState.EXPANDED));
participantsState.onNext(CallParticipantsState.setExpanded(participantsState.getValue(),
state.getLocalRenderState() != WebRtcLocalRenderState.EXPANDED));
}
public void onDismissedVideoTooltip() {
@@ -271,7 +280,7 @@ public class WebRtcCallViewModel extends ViewModel {
boolean wasScreenSharing = state.getFocusedParticipant().isScreenSharing();
CallParticipantsState newState = CallParticipantsState.update(state, webRtcViewModel, enableVideo);
participantsState.setValue(newState);
participantsState.onNext(newState);
if (switchOnFirstScreenShare && !wasScreenSharing && newState.getFocusedParticipant().isScreenSharing()) {
switchOnFirstScreenShare = false;
events.setValue(new Event.SwitchToSpeaker());

View File

@@ -18,6 +18,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.annimon.stream.OptionalLong;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
@@ -34,6 +35,8 @@ public class CallParticipantsListDialog extends BottomSheetDialogFragment {
private RecyclerView participantList;
private CallParticipantsListAdapter adapter;
private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable();
public static void show(@NonNull FragmentManager manager) {
CallParticipantsListDialog fragment = new CallParticipantsListDialog();
@@ -69,14 +72,13 @@ public class CallParticipantsListDialog extends BottomSheetDialogFragment {
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
final WebRtcCallViewModel viewModel = new ViewModelProvider(requireActivity()).get(WebRtcCallViewModel.class);
initializeList();
viewModel.getCallParticipantsState().observe(getViewLifecycleOwner(), this::updateList);
lifecycleDisposable.bindTo(getViewLifecycleOwner());
lifecycleDisposable.add(viewModel.getCallParticipantsState().subscribe(this::updateList));
}
private void initializeList() {