diff --git a/.github/workflows/build-prod-app.yml b/.github/workflows/build-prod-app.yml index b42866dfe69..f682e36fc1f 100644 --- a/.github/workflows/build-prod-app.yml +++ b/.github/workflows/build-prod-app.yml @@ -114,7 +114,7 @@ jobs: build-flavour: prod build-variant: compatrelease - name: Attach APK and version file to release - uses: softprops/action-gh-release@v2.1.0 + uses: softprops/action-gh-release@v2.2.0 with: files: | app/build/outputs/apk/prodCompatrelease/*.apk diff --git a/.github/workflows/generate-changelog.yml b/.github/workflows/generate-changelog.yml index a239d508807..05955c37a68 100644 --- a/.github/workflows/generate-changelog.yml +++ b/.github/workflows/generate-changelog.yml @@ -48,7 +48,7 @@ jobs: npx generate-changelog@1.8.0 -t "$PREVIOUS_TAG...$CURRENT_TAG" - name: 'Attach changelog to tag' - uses: softprops/action-gh-release@v2.1.0 + uses: softprops/action-gh-release@v2.2.0 env: GITHUB_TOKEN: ${{ secrets.ANDROID_BOB_GH_TOKEN }} with: 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 f9877a1fa6d..ec577f2c221 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 @@ -177,6 +177,8 @@ fun OngoingCallScreen( clearVideoPreview = sharedCallingViewModel::clearVideoPreview, onCollapse = onCollapse, requestVideoStreams = ongoingCallViewModel::requestVideoStreams, + onSelectedParticipant = ongoingCallViewModel::onSelectedParticipant, + selectedParticipantForFullScreen = ongoingCallViewModel.selectedParticipant, hideDoubleTapToast = ongoingCallViewModel::hideDoubleTapToast, onCameraPermissionPermanentlyDenied = onCameraPermissionPermanentlyDenied, participants = sharedCallingViewModel.participantsState, @@ -289,6 +291,8 @@ private fun OngoingCallContent( hideDoubleTapToast: () -> Unit, onCameraPermissionPermanentlyDenied: () -> Unit, requestVideoStreams: (participants: List) -> Unit, + onSelectedParticipant: (selectedParticipant: SelectedParticipant) -> Unit, + selectedParticipantForFullScreen: SelectedParticipant, participants: PersistentList, inPictureInPictureMode: Boolean, currentUserId: UserId, @@ -303,7 +307,6 @@ private fun OngoingCallContent( ) var shouldOpenFullScreen by remember { mutableStateOf(false) } - var selectedParticipantForFullScreen by remember { mutableStateOf(SelectedParticipant()) } WireBottomSheetScaffold( sheetDragHandle = null, @@ -391,11 +394,14 @@ private fun OngoingCallContent( selectedParticipant = selectedParticipantForFullScreen, height = this@BoxWithConstraints.maxHeight - dimensions().spacing4x, closeFullScreen = { + onSelectedParticipant(SelectedParticipant()) shouldOpenFullScreen = !shouldOpenFullScreen }, onBackButtonClicked = { + onSelectedParticipant(SelectedParticipant()) shouldOpenFullScreen = !shouldOpenFullScreen }, + requestVideoStreams = requestVideoStreams, setVideoPreview = setVideoPreview, clearVideoPreview = clearVideoPreview, participants = participants @@ -412,7 +418,7 @@ private fun OngoingCallContent( requestVideoStreams = requestVideoStreams, currentUserId = currentUserId, onDoubleTap = { selectedParticipant -> - selectedParticipantForFullScreen = selectedParticipant + onSelectedParticipant(selectedParticipant) shouldOpenFullScreen = !shouldOpenFullScreen }, ) @@ -580,6 +586,8 @@ fun PreviewOngoingCallContent(participants: PersistentList) { participants = participants, inPictureInPictureMode = false, currentUserId = UserId("userId", "domain"), + onSelectedParticipant = {}, + selectedParticipantForFullScreen = SelectedParticipant(), ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallViewModel.kt index 8e07e40bc1b..84c6e1a57d1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallViewModel.kt @@ -28,8 +28,10 @@ import com.wire.android.appLogger import com.wire.android.datastore.GlobalDataStore import com.wire.android.di.CurrentAccount import com.wire.android.ui.calling.model.UICallParticipant +import com.wire.android.ui.calling.ongoing.fullscreen.SelectedParticipant import com.wire.kalium.logic.data.call.Call import com.wire.kalium.logic.data.call.CallClient +import com.wire.kalium.logic.data.call.CallQuality import com.wire.kalium.logic.data.call.VideoState import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId @@ -63,6 +65,8 @@ class OngoingCallViewModel @AssistedInject constructor( var state by mutableStateOf(OngoingCallState()) private set + var selectedParticipant by mutableStateOf(SelectedParticipant()) + private set init { viewModelScope.launch { @@ -124,7 +128,11 @@ class OngoingCallViewModel @AssistedInject constructor( .also { if (it.isNotEmpty()) { val clients: List = it.map { uiParticipant -> - CallClient(uiParticipant.id.toString(), uiParticipant.clientId) + CallClient( + userId = uiParticipant.id.toString(), + clientId = uiParticipant.clientId, + quality = mapQualityStream(uiParticipant) + ) } requestVideoStreams(conversationId, clients) } @@ -132,12 +140,20 @@ class OngoingCallViewModel @AssistedInject constructor( } } + private fun mapQualityStream(uiParticipant: UICallParticipant): CallQuality { + return if (uiParticipant.clientId == selectedParticipant.clientId) { + CallQuality.HIGH + } else { + CallQuality.LOW + } + } + private fun startDoubleTapToastDisplayCountDown() { doubleTapIndicatorCountDownTimer?.cancel() doubleTapIndicatorCountDownTimer = object : CountDownTimer(DOUBLE_TAP_TOAST_DISPLAY_TIME, COUNT_DOWN_INTERVAL) { override fun onTick(p0: Long) { - appLogger.i("startDoubleTapToastDisplayCountDown: $p0") + appLogger.d("$TAG - startDoubleTapToastDisplayCountDown: $p0") } override fun onFinish() { @@ -171,10 +187,16 @@ class OngoingCallViewModel @AssistedInject constructor( } } + fun onSelectedParticipant(selectedParticipant: SelectedParticipant) { + appLogger.d("$TAG - Selected participant: ${selectedParticipant.toLogString()}") + this.selectedParticipant = selectedParticipant + } + companion object { const val DOUBLE_TAP_TOAST_DISPLAY_TIME = 7000L const val COUNT_DOWN_INTERVAL = 1000L const val DELAY_TO_SHOW_DOUBLE_TAP_TOAST = 500L + const val TAG = "OngoingCallViewModel" } @AssistedFactory diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/FullScreenTile.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/FullScreenTile.kt index 7edbd0d7ba3..cee64f83032 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/FullScreenTile.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/FullScreenTile.kt @@ -60,6 +60,7 @@ fun FullScreenTile( closeFullScreen: (offset: Offset) -> Unit, onBackButtonClicked: () -> Unit, setVideoPreview: (View) -> Unit, + requestVideoStreams: (participants: List) -> Unit, clearVideoPreview: () -> Unit, modifier: Modifier = Modifier, contentPadding: Dp = dimensions().spacing4x, @@ -119,6 +120,10 @@ fun FullScreenTile( } ) } + + LaunchedEffect(selectedParticipant.userId) { + requestVideoStreams(listOf(it)) + } } } @@ -139,6 +144,7 @@ fun PreviewFullScreenTile() = WireTheme { closeFullScreen = {}, onBackButtonClicked = {}, setVideoPreview = {}, + requestVideoStreams = {}, clearVideoPreview = {}, participants = participants, ) diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/SelectedParticipant.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/SelectedParticipant.kt index 95238a33c27..af1a1d87f45 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/SelectedParticipant.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/fullscreen/SelectedParticipant.kt @@ -17,10 +17,16 @@ */ package com.wire.android.ui.calling.ongoing.fullscreen +import com.wire.kalium.logger.obfuscateId import com.wire.kalium.logic.data.user.UserId data class SelectedParticipant( val userId: UserId = UserId("", ""), val clientId: String = "", val isSelfUser: Boolean = false -) +) { + + fun toLogString(): String { + return "SelectedParticipant(userId=${userId.toLogString()}, clientId=${clientId.obfuscateId()}, isSelfUser=$isSelfUser)" + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptionsViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptionsViewModel.kt index e98c20eb903..27fdf191eb9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptionsViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptionsViewModel.kt @@ -304,6 +304,9 @@ class DebugDataOptionsViewModelImpl } is MLSKeyPackageCountResult.Failure.Generic -> {} + MLSKeyPackageCountResult.Failure.NotEnabled -> { + state = state.copy(mlsErrorMessage = "Not Enabled!") + } } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsScreen.kt index 422d9647472..7a8894fe512 100644 --- a/app/src/main/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsScreen.kt @@ -204,6 +204,12 @@ fun DeviceDetailsContent( ) { state.device.mlsClientIdentity?.let { identity -> item { + FolderHeader( + name = stringResource(id = R.string.label_mls_signature, state.mlsCipherSuiteSignature.orEmpty()).uppercase(), + modifier = Modifier + .background(MaterialTheme.wireColorScheme.background) + .fillMaxWidth() + ) DeviceMLSSignatureItem(identity.thumbprint, screenState::copyMessage) HorizontalDivider(color = MaterialTheme.wireColorScheme.background) } diff --git a/app/src/main/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsViewModel.kt index f6d06b76b68..1ca4096174f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsViewModel.kt @@ -37,6 +37,7 @@ import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.data.client.ClientType import com.wire.kalium.logic.data.client.DeleteClientParam import com.wire.kalium.logic.data.conversation.ClientId +import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeyType import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.client.ClientFingerprintUseCase import com.wire.kalium.logic.feature.client.DeleteClientResult @@ -72,7 +73,7 @@ class DeviceDetailsViewModel @Inject constructor( private val fingerprintUseCase: ClientFingerprintUseCase, private val updateClientVerificationStatus: UpdateClientVerificationStatusUseCase, private val observeUserInfo: ObserveUserInfoUseCase, - private val e2eiCertificate: GetMLSClientIdentityUseCase, + private val mlsClientIdentity: GetMLSClientIdentityUseCase, private val breakSession: BreakSessionUseCase, isE2EIEnabledUseCase: IsE2EIEnabledUseCase ) : SavedStateViewModel(savedStateHandle) { @@ -133,7 +134,7 @@ class DeviceDetailsViewModel @Inject constructor( private fun getE2eiCertificate() { viewModelScope.launch { - state = e2eiCertificate(deviceId).fold({ + state = mlsClientIdentity(deviceId).fold({ state.copy(isE2eiCertificateActivated = false, isLoadingCertificate = false) }, { mlsClientIdentity -> state.copy( @@ -198,6 +199,9 @@ class DeviceDetailsViewModel @Inject constructor( isCurrentDevice = result.isCurrentClient, removeDeviceDialogState = RemoveDeviceDialogState.Hidden, canBeRemoved = !result.isCurrentClient && isSelfClient && result.client.type != ClientType.LegalHold, + mlsCipherSuiteSignature = MLSPublicKeyType.from( + result.client.mlsPublicKeys?.keys?.firstOrNull().orEmpty() + ).value.toString() ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/settings/devices/model/DeviceDetailsState.kt b/app/src/main/kotlin/com/wire/android/ui/settings/devices/model/DeviceDetailsState.kt index b68745619e9..96216d33068 100644 --- a/app/src/main/kotlin/com/wire/android/ui/settings/devices/model/DeviceDetailsState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/settings/devices/model/DeviceDetailsState.kt @@ -37,5 +37,6 @@ data class DeviceDetailsState( val isE2EICertificateEnrollSuccess: Boolean = false, val isE2EICertificateEnrollError: Boolean = false, val isE2EIEnabled: Boolean = false, - val startGettingE2EICertificate: Boolean = false + val startGettingE2EICertificate: Boolean = false, + val mlsCipherSuiteSignature: String? = null, ) diff --git a/app/src/test/kotlin/com/wire/android/ui/calling/OngoingCallViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/calling/OngoingCallViewModelTest.kt index 79e3abce3cb..92937700fdb 100644 --- a/app/src/test/kotlin/com/wire/android/ui/calling/OngoingCallViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/calling/OngoingCallViewModelTest.kt @@ -23,9 +23,11 @@ import com.wire.android.config.NavigationTestExtension import com.wire.android.datastore.GlobalDataStore import com.wire.android.ui.calling.model.UICallParticipant import com.wire.android.ui.calling.ongoing.OngoingCallViewModel +import com.wire.android.ui.calling.ongoing.fullscreen.SelectedParticipant import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.kalium.logic.data.call.Call import com.wire.kalium.logic.data.call.CallClient +import com.wire.kalium.logic.data.call.CallQuality import com.wire.kalium.logic.data.call.CallStatus import com.wire.kalium.logic.data.call.VideoState import com.wire.kalium.logic.data.conversation.Conversation @@ -170,6 +172,72 @@ class OngoingCallViewModelTest { } } + @Test + fun givenAUserIsSelected_whenRequestedFullScreen_thenSetTheUserAsSelected() = + runTest { + val (_, ongoingCallViewModel) = Arrangement() + .withCall(provideCall().copy(isCameraOn = true)) + .withShouldShowDoubleTapToastReturning(false) + .withSetVideoSendState() + .arrange() + + ongoingCallViewModel.onSelectedParticipant(selectedParticipant3) + + assertEquals(selectedParticipant3, ongoingCallViewModel.selectedParticipant) + } + + @Test + fun givenParticipantsList_WhenRequestingVideoStreamForFullScreenParticipant_ThenRequestItInHighQuality() = + runTest { + val expectedClients = listOf( + CallClient(participant1.id.toString(), participant1.clientId, false, CallQuality.LOW), + CallClient(participant3.id.toString(), participant3.clientId, false, CallQuality.HIGH) + ) + + val (arrangement, ongoingCallViewModel) = Arrangement() + .withCall(provideCall()) + .withShouldShowDoubleTapToastReturning(false) + .withSetVideoSendState() + .withRequestVideoStreams(conversationId, expectedClients) + .arrange() + + ongoingCallViewModel.onSelectedParticipant(selectedParticipant3) + ongoingCallViewModel.requestVideoStreams(participants) + + coVerify(exactly = 1) { + arrangement.requestVideoStreams( + conversationId, + expectedClients + ) + } + } + + @Test + fun givenParticipantsList_WhenRequestingVideoStreamForAllParticipant_ThenRequestItInLowQuality() = + runTest { + val expectedClients = listOf( + CallClient(participant1.id.toString(), participant1.clientId, false, CallQuality.LOW), + CallClient(participant3.id.toString(), participant3.clientId, false, CallQuality.LOW) + ) + + val (arrangement, ongoingCallViewModel) = Arrangement() + .withCall(provideCall()) + .withShouldShowDoubleTapToastReturning(false) + .withSetVideoSendState() + .withRequestVideoStreams(conversationId, expectedClients) + .arrange() + + ongoingCallViewModel.onSelectedParticipant(SelectedParticipant()) + ongoingCallViewModel.requestVideoStreams(participants) + + coVerify(exactly = 1) { + arrangement.requestVideoStreams( + conversationId, + expectedClients + ) + } + } + private class Arrangement { @MockK @@ -268,6 +336,7 @@ class OngoingCallViewModelTest { accentId = -1 ) val participants = listOf(participant1, participant2, participant3) + val selectedParticipant3 = SelectedParticipant(participant3.id, participant3.clientId, false) } private fun provideCall( diff --git a/app/src/test/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsViewModelTest.kt index 6b69dabbd6e..5e0ea113b3a 100644 --- a/app/src/test/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/settings/devices/DeviceDetailsViewModelTest.kt @@ -377,7 +377,7 @@ class DeviceDetailsViewModelTest { updateClientVerificationStatus = updateClientVerificationStatus, currentUserId = currentUserId, observeUserInfo = observeUserInfo, - e2eiCertificate = getE2eiCertificate, + mlsClientIdentity = getE2eiCertificate, isE2EIEnabledUseCase = isE2EIEnabledUseCase, breakSession = breakSession ) diff --git a/core/analytics-enabled/src/main/kotlin/com/wire/android/feature/analytics/AnonymousAnalyticsRecorderImpl.kt b/core/analytics-enabled/src/main/kotlin/com/wire/android/feature/analytics/AnonymousAnalyticsRecorderImpl.kt index 71fe3723667..6baadc5b295 100644 --- a/core/analytics-enabled/src/main/kotlin/com/wire/android/feature/analytics/AnonymousAnalyticsRecorderImpl.kt +++ b/core/analytics-enabled/src/main/kotlin/com/wire/android/feature/analytics/AnonymousAnalyticsRecorderImpl.kt @@ -86,7 +86,7 @@ class AnonymousAnalyticsRecorderImpl : AnonymousAnalyticsRecorder { override fun halt() = wrapCountlyRequest { isConfigured = false - Countly.sharedInstance().consent().removeConsentAll() + Countly.sharedInstance()?.consent()?.removeConsentAll() } override suspend fun setTrackingIdentifierWithMerge( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 557c6558a97..6dbdf6c2991 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -67,7 +67,7 @@ coil = "2.7.0" commonmark = "0.24.0" # Countly -countly = "24.4.0" +countly = "24.7.7" # RSS rss-parser = "6.0.7" diff --git a/kalium b/kalium index 0667f9b780a..1ca6dfc988e 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 0667f9b780a8262768b0c37af3d49d4f83c55701 +Subproject commit 1ca6dfc988eeccf550858620ae1dadf8e49555da