From 6c8d78f687b2a314a89be2c1c75149a786676f4d Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Fri, 3 May 2024 13:09:11 +0200 Subject: [PATCH 1/4] fix: Crash after fresh install (#2965) --- .../wire/android/ui/WireActivityViewModel.kt | 20 ++++++--- .../ui/calling/SharedCallingViewModel.kt | 2 +- .../android/ui/WireActivityViewModelTest.kt | 43 ++++++++++++++++++- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt index 847977bbc20..a7d7bb502b1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt @@ -19,6 +19,7 @@ package com.wire.android.ui import android.content.Intent +import androidx.annotation.VisibleForTesting import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -60,7 +61,6 @@ import com.wire.kalium.logic.data.logout.LogoutReason import com.wire.kalium.logic.data.sync.SyncState import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.appVersioning.ObserveIfAppUpdateRequiredUseCase -import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase import com.wire.kalium.logic.feature.client.ClearNewClientsForUserUseCase import com.wire.kalium.logic.feature.client.NewClientResult import com.wire.kalium.logic.feature.client.ObserveNewClientsUseCase @@ -78,6 +78,7 @@ import com.wire.kalium.logic.feature.user.webSocketStatus.ObservePersistentWebSo import com.wire.kalium.util.DateTimeUtil.toIsoDateTimeString import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -117,8 +118,7 @@ class WireActivityViewModel @Inject constructor( private val observeScreenshotCensoringConfigUseCaseProviderFactory: ObserveScreenshotCensoringConfigUseCaseProvider.Factory, private val globalDataStore: GlobalDataStore, private val observeIfE2EIRequiredDuringLoginUseCaseProviderFactory: ObserveIfE2EIRequiredDuringLoginUseCaseProvider.Factory, - private val workManager: WorkManager, - private val observeEstablishedCalls: ObserveEstablishedCallsUseCase + private val workManager: WorkManager ) : ViewModel() { var globalAppState: GlobalAppState by mutableStateOf(GlobalAppState()) @@ -257,7 +257,15 @@ class WireActivityViewModel @Inject constructor( return intent?.action == Intent.ACTION_SEND || intent?.action == Intent.ACTION_SEND_MULTIPLE } - private suspend fun canLoginThroughDeepLinks() = observeEstablishedCalls().first().isEmpty() + @VisibleForTesting + internal suspend fun canLoginThroughDeepLinks() = viewModelScope.async { + coreLogic.getGlobalScope().session.currentSession().takeIf { + it is CurrentSessionResult.Success + }?.let { + val currentUserId = (it as CurrentSessionResult.Success).accountInfo.userId + coreLogic.getSessionScope(currentUserId).calls.establishedCall().first().isEmpty() + } ?: true + } @Suppress("ComplexMethod") fun handleDeepLink( @@ -276,7 +284,7 @@ class WireActivityViewModel @Inject constructor( val result = intent?.data?.let { deepLinkProcessor(it) } when { result is DeepLinkResult.SSOLogin -> { - if (canLoginThroughDeepLinks()) { + if (canLoginThroughDeepLinks().await()) { onResult(result) } else { onCannotLoginDuringACall() @@ -284,7 +292,7 @@ class WireActivityViewModel @Inject constructor( } result is DeepLinkResult.MigrationLogin -> onResult(result) result is DeepLinkResult.CustomServerConfig -> { - if (canLoginThroughDeepLinks()) { + if (canLoginThroughDeepLinks().await()) { onCustomServerConfig(result) } else { onCannotLoginDuringACall() diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt index 5b5be55ef3e..0296206a34e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt @@ -207,7 +207,7 @@ class SharedCallingViewModel @AssistedInject constructor( if (callState.isCameraOn) { flipToFrontCamera(conversationId) } - if (callState.isCameraOn || callState.isSpeakerOn) { + if (callState.isSpeakerOn) { turnLoudSpeakerOff() } } diff --git a/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt index c50e580728d..2952d68bddf 100644 --- a/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt @@ -599,6 +599,44 @@ class WireActivityViewModelTest { assertEquals(ThemeOption.DARK, viewModel.globalAppState.themeOption) } + @Test + fun `given no active session, when canLoginThroughDeepLinks is called, then return true`() = + runTest { + val (_, viewModel) = Arrangement() + .withNoCurrentSession() + .arrange() + + val result = viewModel.canLoginThroughDeepLinks() + + result.await() `should be equal to` true + } + + @Test + fun `given an established call, when canLoginThroughDeepLinks is called, then return false`() = + runTest { + val (_, viewModel) = Arrangement() + .withSomeCurrentSession() + .withOngoingCall() + .arrange() + + val result = viewModel.canLoginThroughDeepLinks() + + result.await() `should be equal to` false + } + + @Test + fun `given no established call, when canLoginThroughDeepLinks is called, then return true`() = + runTest { + val (_, viewModel) = Arrangement() + .withNoCurrentSession() + .withNoOngoingCall() + .arrange() + + val result = viewModel.canLoginThroughDeepLinks() + + result.await() `should be equal to` true + } + private class Arrangement { init { @@ -722,8 +760,7 @@ class WireActivityViewModelTest { observeScreenshotCensoringConfigUseCaseProviderFactory = observeScreenshotCensoringConfigUseCaseProviderFactory, globalDataStore = globalDataStore, observeIfE2EIRequiredDuringLoginUseCaseProviderFactory = observeIfE2EIRequiredDuringLoginUseCaseProviderFactory, - workManager = workManager, - observeEstablishedCalls = observeEstablishedCalls + workManager = workManager ) } @@ -746,10 +783,12 @@ class WireActivityViewModelTest { } fun withNoOngoingCall(): Arrangement { + coEvery { coreLogic.getSessionScope(any()).calls.establishedCall } returns observeEstablishedCalls coEvery { observeEstablishedCalls() } returns flowOf(emptyList()) return this } fun withOngoingCall(): Arrangement { + coEvery { coreLogic.getSessionScope(any()).calls.establishedCall } returns observeEstablishedCalls coEvery { observeEstablishedCalls() } returns flowOf(listOf(ongoingCall)) return this } From 3027581e5b6bff49714549427b87dafc457be7f6 Mon Sep 17 00:00:00 2001 From: Vitor Hugo Schwaab Date: Fri, 3 May 2024 13:34:03 +0200 Subject: [PATCH 2/4] fix: properly cherry-pick when there are changes in submodule --- .github/workflows/cherry-pick-pr-to-newer-release-cycle.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cherry-pick-pr-to-newer-release-cycle.yml b/.github/workflows/cherry-pick-pr-to-newer-release-cycle.yml index 57f75c6072c..1d2759409b9 100644 --- a/.github/workflows/cherry-pick-pr-to-newer-release-cycle.yml +++ b/.github/workflows/cherry-pick-pr-to-newer-release-cycle.yml @@ -40,9 +40,9 @@ jobs: fetch-depth: 0 - name: Cherry pick to `develop` - uses: wireapp/action-auto-cherry-pick@v1.0.0 + uses: wireapp/action-auto-cherry-pick@v1.0.1 with: target-branch: develop - submodule-name: kalium + submodules-target-branch: develop pr-title-suffix: 🍒 - labels: cherry-pick + pr-labels: cherry-pick From 933883e213edb59761f25ec4562e4551580c1896 Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Fri, 3 May 2024 14:51:28 +0200 Subject: [PATCH 3/4] fix: finish CallActivity and remove task when call is terminated (WPB-6437) (#2963) --- .../android/notification/NotificationChannelsManager.kt | 1 - .../wire/android/ui/calling/incoming/IncomingCallScreen.kt | 6 +++--- .../wire/android/ui/calling/ongoing/OngoingCallScreen.kt | 4 ++-- .../wire/android/ui/calling/outgoing/OutgoingCallScreen.kt | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/notification/NotificationChannelsManager.kt b/app/src/main/kotlin/com/wire/android/notification/NotificationChannelsManager.kt index 2e484bd6bc5..d84ac75c312 100644 --- a/app/src/main/kotlin/com/wire/android/notification/NotificationChannelsManager.kt +++ b/app/src/main/kotlin/com/wire/android/notification/NotificationChannelsManager.kt @@ -143,7 +143,6 @@ class NotificationChannelsManager @Inject constructor( .setImportance(NotificationManagerCompat.IMPORTANCE_DEFAULT) .setSound(outgoingCallSoundUri, audioAttributes) .setShowBadge(false) - .setVibrationPattern(VIBRATE_PATTERN) .setGroup(groupId) .build() diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt index b1bff409bf5..d62a4a5c848 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt @@ -109,7 +109,7 @@ fun IncomingCallScreen( LaunchedEffect(incomingCallViewModel.incomingCallState.flowState) { when (incomingCallViewModel.incomingCallState.flowState) { is IncomingCallState.FlowState.CallClosed -> { - activity.finish() + activity.finishAndRemoveTask() } is IncomingCallState.FlowState.CallAccepted -> { @@ -132,7 +132,7 @@ fun IncomingCallScreen( (activity as CallActivity).openAppLockActivity() }, onCallRejected = { - activity.finish() + activity.finishAndRemoveTask() } ) }, @@ -150,7 +150,7 @@ fun IncomingCallScreen( } }, onMinimiseScreen = { - activity.finish() + activity.moveTaskToBack(true) } ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt index e7de9938899..78be3bf519a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt @@ -110,7 +110,7 @@ fun OngoingCallScreen( LaunchedEffect(ongoingCallViewModel.state.flowState) { when (ongoingCallViewModel.state.flowState) { OngoingCallState.FlowState.CallClosed -> { - activity.finish() + activity.finishAndRemoveTask() } OngoingCallState.FlowState.Default -> { /* do nothing */ @@ -134,7 +134,7 @@ fun OngoingCallScreen( shouldShowDoubleTapToast = ongoingCallViewModel.shouldShowDoubleTapToast, toggleSpeaker = sharedCallingViewModel::toggleSpeaker, toggleMute = sharedCallingViewModel::toggleMute, - hangUpCall = { sharedCallingViewModel.hangUpCall { activity.finish() } }, + hangUpCall = { sharedCallingViewModel.hangUpCall { activity.finishAndRemoveTask() } }, toggleVideo = sharedCallingViewModel::toggleVideo, flipCamera = sharedCallingViewModel::flipCamera, setVideoPreview = { diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/outgoing/OutgoingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/outgoing/OutgoingCallScreen.kt index 2b43016bfb0..cc5dc754814 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/outgoing/OutgoingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/outgoing/OutgoingCallScreen.kt @@ -75,7 +75,7 @@ fun OutgoingCallScreen( LaunchedEffect(outgoingCallViewModel.state.flowState) { when (outgoingCallViewModel.state.flowState) { OutgoingCallState.FlowState.CallClosed -> { - activity.finish() + activity.finishAndRemoveTask() } OutgoingCallState.FlowState.CallEstablished -> { @@ -105,7 +105,7 @@ fun OutgoingCallScreen( } }, onMinimiseScreen = { - activity.finish() + activity.moveTaskToBack(true) } ) } From a0ae53d88f7b9619c8d79b1c4dd1c514976709f7 Mon Sep 17 00:00:00 2001 From: Vitor Hugo Schwaab Date: Fri, 3 May 2024 16:50:38 +0200 Subject: [PATCH 4/4] fix: keep the composed message when replying [WPB-982] (#2969) --- .../state/MessageComposerStateHolder.kt | 1 - .../MessageComposerStateHolderTest.kt | 20 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/MessageComposerStateHolder.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/MessageComposerStateHolder.kt index f8e2eeb2ca0..43aed787412 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/MessageComposerStateHolder.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/MessageComposerStateHolder.kt @@ -109,7 +109,6 @@ class MessageComposerStateHolder( } fun toReply(message: UIMessage.Regular) { - messageCompositionHolder.clearMessage() messageCompositionHolder.setReply(message) messageCompositionInputStateHolder.toComposing() } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposerStateHolderTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposerStateHolderTest.kt index 3ef7e594b63..1717c275ba1 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposerStateHolderTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposerStateHolderTest.kt @@ -116,9 +116,7 @@ class MessageComposerStateHolderTest { runTest { // given // when - state.toReply( - message = mockMessageWithText - ) + state.toReply(mockMessageWithText) // then assertEquals( @@ -133,6 +131,22 @@ class MessageComposerStateHolderTest { ) } + @Test + fun `given some message was being composed, when setting toReply, then input continues with the current text`() = runTest { + // given + val currentTextField = TextFieldValue("Potato") + messageCompositionHolder.setMessageText(currentTextField, {}, {}, {}) + + // when + state.toReply(mockMessageWithText) + + // then + assertEquals( + currentTextField.text, + messageCompositionHolder.messageComposition.value.messageTextFieldValue.text + ) + } + @Test fun `given state, when input focus change to false, then clear focus`() = runTest { // given