From 853862c475d53db603c6d06f1f586fad28387b28 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 13 Dec 2023 10:13:15 -0500 Subject: [PATCH] Fix call info sheet scroll position after dismissing. --- .../securesms/WebRtcCallActivity.java | 6 ++- .../links/details/CallLinkDetailsState.kt | 1 + .../links/details/CallLinkDetailsViewModel.kt | 1 + .../components/webrtc/CallLinkInfoSheet.kt | 1 + .../webrtc/controls/CallInfoView.kt | 37 +++++++++++++------ .../webrtc/controls/ControlAndInfoState.kt | 13 +++++++ .../controls/ControlsAndInfoController.kt | 8 +++- .../controls/ControlsAndInfoViewModel.kt | 22 +++++++++++ 8 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlAndInfoState.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoViewModel.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java index a621a24980..4395a771a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -64,6 +64,7 @@ import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePo 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; @@ -150,6 +151,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan private TooltipPopup videoTooltip; private TooltipPopup switchCameraTooltip; private WebRtcCallViewModel viewModel; + private ControlsAndInfoViewModel controlsAndInfoViewModel; private boolean enableVideoIfAvailable; private boolean hasWarnedAboutBluetooth; private WindowLayoutInfoConsumer windowLayoutInfoConsumer; @@ -196,7 +198,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan initializeViewModel(isLandscapeEnabled); initializePictureInPictureParams(); - controlsAndInfo = new ControlsAndInfoController(callScreen, callOverflowPopupWindow, viewModel); + controlsAndInfo = new ControlsAndInfoController(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)); @@ -495,6 +497,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan .subscribe(callScreen::updatePendingParticipantsList); lifecycleDisposable.add(disposable); + + controlsAndInfoViewModel = new ViewModelProvider(this).get(ControlsAndInfoViewModel.class); } private void initializePictureInPictureParams() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsState.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsState.kt index 33d1f887e9..8a160829de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsState.kt @@ -9,6 +9,7 @@ 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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsViewModel.kt index 9bfdf9f1bc..366826cb89 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsViewModel.kt @@ -22,6 +22,7 @@ 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(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkInfoSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkInfoSheet.kt index 2f15f8f0eb..067ef31333 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkInfoSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkInfoSheet.kt @@ -86,6 +86,7 @@ import org.thoughtcrime.securesms.util.BottomSheetUtil * 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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt index 5c42d094bf..64f4515746 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt @@ -17,12 +17,14 @@ 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.lazy.rememberLazyListState 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.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.rxjava3.subscribeAsState @@ -58,8 +60,8 @@ import org.thoughtcrime.securesms.recipients.Recipient object CallInfoView { @Composable - fun View(webRtcCallViewModel: WebRtcCallViewModel, modifier: Modifier) { - val state: ParticipantsState by webRtcCallViewModel.callParticipantsState + fun View(webRtcCallViewModel: WebRtcCallViewModel, controlsAndInfoViewModel: ControlsAndInfoViewModel, modifier: Modifier) { + val participantsState: ParticipantsState by webRtcCallViewModel.callParticipantsState .toFlowable(BackpressureStrategy.LATEST) .map { state -> ParticipantsState( @@ -75,11 +77,13 @@ object CallInfoView { } .subscribeAsState(ParticipantsState()) + val controlAndInfoState: ControlAndInfoState by controlsAndInfoViewModel.state + SignalTheme( isDarkMode = true ) { Surface { - CallInfo(state = state, modifier = modifier) + CallInfo(participantsState = participantsState, controlAndInfoState = controlAndInfoState, modifier = modifier) } } } @@ -91,7 +95,8 @@ private fun CallInfoPreview() { SignalTheme(isDarkMode = true) { Surface { CallInfo( - state = ParticipantsState(remoteParticipants = listOf(CallParticipant(recipient = Recipient.UNKNOWN))) + participantsState = ParticipantsState(remoteParticipants = listOf(CallParticipant(recipient = Recipient.UNKNOWN))), + controlAndInfoState = ControlAndInfoState() ) } } @@ -99,10 +104,18 @@ private fun CallInfoPreview() { @Composable private fun CallInfo( - state: ParticipantsState, + participantsState: ParticipantsState, + controlAndInfoState: ControlAndInfoState, modifier: Modifier = Modifier ) { + val listState = rememberLazyListState() + + LaunchedEffect(controlAndInfoState.resetScrollState) { + listState.scrollToItem(0) + } + LazyColumn( + state = listState, horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier ) { @@ -123,15 +136,15 @@ private fun CallInfo( contentAlignment = Alignment.CenterStart ) { Text( - text = getCallSheetLabel(state), + text = getCallSheetLabel(participantsState), style = MaterialTheme.typography.titleSmall ) } } - if (!state.inCallLobby || state.isOngoing()) { + if (!participantsState.inCallLobby || participantsState.isOngoing()) { items( - items = state.participantsForList, + items = participantsState.participantsForList, key = { it.callParticipantId }, contentType = { null } ) { @@ -141,9 +154,9 @@ private fun CallInfo( onBlockClicked = {} ) } - } else if (state.isGroupCall()) { + } else if (participantsState.isGroupCall()) { items( - items = state.groupMembers, + items = participantsState.groupMembers, key = { it.member.id.toLong() }, contentType = { null } ) { @@ -155,8 +168,8 @@ private fun CallInfo( } else { item { CallParticipantRow( - initialRecipient = state.callRecipient, - name = state.callRecipient.getShortDisplayName(LocalContext.current), + initialRecipient = participantsState.callRecipient, + name = participantsState.callRecipient.getShortDisplayName(LocalContext.current), showIcons = false, isVideoEnabled = false, isMicrophoneEnabled = false, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlAndInfoState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlAndInfoState.kt new file mode 100644 index 0000000000..ad33a003c5 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlAndInfoState.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.components.webrtc.controls + +import androidx.compose.runtime.Immutable + +@Immutable +data class ControlAndInfoState( + val resetScrollState: Long = 0 +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoController.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoController.kt index a2ef5336c1..6ba6377c2d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoController.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoController.kt @@ -43,7 +43,8 @@ import kotlin.time.Duration.Companion.seconds class ControlsAndInfoController( private val webRtcCallView: WebRtcCallView, private val overflowPopupWindow: CallOverflowPopupWindow, - private val viewModel: WebRtcCallViewModel + private val viewModel: WebRtcCallViewModel, + private val controlsAndInfoViewModel: ControlsAndInfoViewModel ) : Disposable { companion object { @@ -83,7 +84,7 @@ class ControlsAndInfoController( setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { val nestedScrollInterop = rememberNestedScrollInteropConnection() - CallInfoView.View(viewModel, Modifier.nestedScroll(nestedScrollInterop)) + CallInfoView.View(viewModel, controlsAndInfoViewModel, Modifier.nestedScroll(nestedScrollInterop)) } } @@ -126,11 +127,14 @@ class ControlsAndInfoController( override fun onStateChanged(bottomSheet: View, newState: Int) { overflowPopupWindow.dismiss() if (newState == BottomSheetBehavior.STATE_COLLAPSED) { + controlsAndInfoViewModel.resetScrollState() if (controlState.isFadeOutEnabled) { hide(delay = HIDE_CONTROL_DELAY) } } else if (newState == BottomSheetBehavior.STATE_EXPANDED || newState == BottomSheetBehavior.STATE_DRAGGING) { cancelScheduledHide() + } else if (newState == BottomSheetBehavior.STATE_HIDDEN) { + controlsAndInfoViewModel.resetScrollState() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoViewModel.kt new file mode 100644 index 0000000000..4768f49165 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoViewModel.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.components.webrtc.controls + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel + +/** + * Provides a view model communicating with the Controls and Info view, [CallInfoView]. + */ +class ControlsAndInfoViewModel : ViewModel() { + private val _state = mutableStateOf(ControlAndInfoState()) + val state: State = _state + + fun resetScrollState() { + _state.value = _state.value.copy(resetScrollState = System.currentTimeMillis()) + } +}