From 61b83580132917e59d02f9d57cb9c40279f7e340 Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Mon, 10 Oct 2022 10:19:01 +0200 Subject: [PATCH 001/237] new app layout home screen tests --- .../app/features/home/InvitesViewModelTest.kt | 109 +++++++++++ .../features/home/RoomsListViewModelTest.kt | 184 ++++++++++++++++++ .../app/test/fakes/FakeDrawableProvider.kt | 30 +++ .../fakes/FakeHomeLayoutPreferencesStore.kt | 43 ++++ .../vector/app/test/fakes/FakeRoomService.kt | 4 + .../im/vector/app/test/fakes/FakeSession.kt | 5 + .../app/test/fakes/FakeStringProvider.kt | 4 + .../vector/app/test/fakes/FakeUserService.kt | 32 +++ 8 files changed, 411 insertions(+) create mode 100644 vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt create mode 100644 vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeDrawableProvider.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeHomeLayoutPreferencesStore.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt diff --git a/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt b/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt new file mode 100644 index 0000000000..48bf232327 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home + +import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.features.home.room.list.home.invites.InvitesAction +import im.vector.app.features.home.room.list.home.invites.InvitesViewEvents +import im.vector.app.features.home.room.list.home.invites.InvitesViewModel +import im.vector.app.features.home.room.list.home.invites.InvitesViewState +import im.vector.app.test.fakes.FakeDrawableProvider +import im.vector.app.test.fakes.FakeSession +import im.vector.app.test.fakes.FakeStringProvider +import im.vector.app.test.fixtures.RoomSummaryFixture +import im.vector.app.test.test +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.matrix.android.sdk.api.session.room.model.Membership + +class InvitesViewModelTest { + + @get:Rule + val mavericksTestRule = MavericksTestRule() + + private val fakeSession = FakeSession() + private val fakeStringProvider = FakeStringProvider() + private val fakeDrawableProvider = FakeDrawableProvider() + + private var initialState = InvitesViewState() + private lateinit var viewModel: InvitesViewModel + + private val anInvite = RoomSummaryFixture.aRoomSummary("invite") + + @Before + fun setUp() { + mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt") + + every { + fakeSession.fakeRoomService.getPagedRoomSummariesLive( + queryParams = match { + it.memberships == listOf(Membership.INVITE) + }, + pagedListConfig = any(), + sortOrder = any() + ) + } returns mockk() + + viewModelWith(initialState) + } + + @Test + fun `when invite accepted then membership map is updated and open event posted`() = runTest { + val test = viewModel.test() + + viewModel.handle(InvitesAction.AcceptInvitation(anInvite)) + + test.assertEvents( + InvitesViewEvents.OpenRoom( + roomSummary = anInvite, + shouldCloseInviteView = false, + isInviteAlreadySelected = true + ) + ).finish() + } + + @Test + fun `when invite rejected then membership map is updated and open event posted`() = runTest { + coEvery { fakeSession.roomService().leaveRoom(any(), any()) } returns Unit + + viewModel.handle(InvitesAction.RejectInvitation(anInvite)) + + coVerify { + fakeSession.roomService().leaveRoom(anInvite.roomId) + } + } + + private fun viewModelWith(state: InvitesViewState) { + InvitesViewModel( + state, + session = fakeSession, + stringProvider = fakeStringProvider.instance, + drawableProvider = fakeDrawableProvider.instance, + + ).also { + viewModel = it + initialState = state + } + } +} diff --git a/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt b/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt new file mode 100644 index 0000000000..e2fef25aba --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home + +import android.widget.ImageView +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import arrow.core.Option +import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.R +import im.vector.app.core.platform.StateView +import im.vector.app.features.displayname.getBestName +import im.vector.app.features.home.room.list.home.HomeRoomListAction +import im.vector.app.features.home.room.list.home.HomeRoomListViewModel +import im.vector.app.features.home.room.list.home.HomeRoomListViewState +import im.vector.app.features.home.room.list.home.header.HomeRoomFilter +import im.vector.app.test.fakes.FakeAnalyticsTracker +import im.vector.app.test.fakes.FakeDrawableProvider +import im.vector.app.test.fakes.FakeHomeLayoutPreferencesStore +import im.vector.app.test.fakes.FakeSession +import im.vector.app.test.fakes.FakeSpaceStateHandler +import im.vector.app.test.fakes.FakeStringProvider +import im.vector.app.test.fixtures.RoomSummaryFixture.aRoomSummary +import im.vector.app.test.test +import io.mockk.every +import io.mockk.mockkStatic +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.matrix.android.sdk.api.query.SpaceFilter +import org.matrix.android.sdk.api.session.getUserOrDefault +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.FlowSession + +class RoomsListViewModelTest { + + @get:Rule + val mavericksTestRule = MavericksTestRule() + + @get:Rule + var rule = InstantTaskExecutorRule() + + private val fakeSession = FakeSession() + private val fakeAnalyticsTracker = FakeAnalyticsTracker() + private val fakeStringProvider = FakeStringProvider() + private val fakeDrawableProvider = FakeDrawableProvider() + private val fakeSpaceStateHandler = FakeSpaceStateHandler() + private val fakeHomeLayoutPreferencesStore = FakeHomeLayoutPreferencesStore() + + private var initialState = HomeRoomListViewState() + private lateinit var viewModel: HomeRoomListViewModel + private lateinit var fakeFLowSession: FlowSession + + @Before + fun setUp() { + mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt") + fakeFLowSession = fakeSession.givenFlowSession() + + every { fakeSpaceStateHandler.getSelectedSpaceFlow() } returns flowOf(Option.empty()) + every { fakeSpaceStateHandler.getCurrentSpace() } returns null + every { fakeFLowSession.liveRoomSummaries(any(), any()) } returns flowOf(emptyList()) + + val roomA = aRoomSummary("room_a") + val roomB = aRoomSummary("room_b") + val roomC = aRoomSummary("room_c") + val allRooms = listOf(roomA, roomB, roomC) + + every { + fakeFLowSession.liveRoomSummaries( + match { + it.roomCategoryFilter == null && + it.roomTagQueryFilter == null && + it.memberships == listOf(Membership.JOIN) && + it.spaceFilter is SpaceFilter.NoFilter + }, any() + ) + } returns flowOf(allRooms) + + viewModelWith(initialState) + } + + @Test + fun `when recents are enabled then updates state`() = runTest { + val fakeFLowSession = fakeSession.givenFlowSession() + every { fakeFLowSession.liveRoomSummaries(any()) } returns flowOf(emptyList()) + val test = viewModel.test() + + val roomA = aRoomSummary("room_a") + val roomB = aRoomSummary("room_b") + val roomC = aRoomSummary("room_c") + val recentRooms = listOf(roomA, roomB, roomC) + + every { fakeFLowSession.liveBreadcrumbs(any()) } returns flowOf(recentRooms) + fakeHomeLayoutPreferencesStore.givenRecentsEnabled(true) + + val userName = fakeSession.getUserOrDefault(fakeSession.myUserId).toMatrixItem().getBestName() + val allEmptyState = StateView.State.Empty( + title = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_title, userName), + message = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_message), + image = fakeDrawableProvider.instance.getDrawable(R.drawable.ill_empty_all_chats), + isBigImage = true + ) + + test.assertLatestState( + initialState.copy(emptyState = allEmptyState, headersData = initialState.headersData.copy(recents = recentRooms)) + ) + } + + @Test + fun `when filter tabs are enabled then updates state`() = runTest { + val test = viewModel.test() + + fakeHomeLayoutPreferencesStore.givenFiltersEnabled(true) + + val filtersData = mutableListOf( + HomeRoomFilter.ALL, + HomeRoomFilter.UNREADS + ) + + val userName = fakeSession.getUserOrDefault(fakeSession.myUserId).toMatrixItem().getBestName() + val allEmptyState = StateView.State.Empty( + title = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_title, userName), + message = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_message), + image = fakeDrawableProvider.instance.getDrawable(R.drawable.ill_empty_all_chats), + isBigImage = true + ) + + test.assertLatestState( + initialState.copy(emptyState = allEmptyState, headersData = initialState.headersData.copy(filtersList = filtersData)) + ) + } + + @Test + fun `when filter tab is selected then updates state`() = runTest { + val test = viewModel.test() + + val aFilter = HomeRoomFilter.UNREADS + viewModel.handle(HomeRoomListAction.ChangeRoomFilter(filter = aFilter)) + + val unreadsEmptyState = StateView.State.Empty( + title = fakeStringProvider.instance.getString(R.string.home_empty_no_unreads_title), + message = fakeStringProvider.instance.getString(R.string.home_empty_no_unreads_message), + image = fakeDrawableProvider.instance.getDrawable(R.drawable.ill_empty_unreads), + isBigImage = true, + imageScaleType = ImageView.ScaleType.CENTER_INSIDE + ) + + test.assertLatestState( + initialState.copy(emptyState = unreadsEmptyState, headersData = initialState.headersData.copy(currentFilter = aFilter)) + ) + } + + private fun viewModelWith(state: HomeRoomListViewState) { + HomeRoomListViewModel( + state, + session = fakeSession, + spaceStateHandler = fakeSpaceStateHandler, + preferencesStore = fakeHomeLayoutPreferencesStore.instance, + stringProvider = fakeStringProvider.instance, + drawableProvider = fakeDrawableProvider.instance, + analyticsTracker = fakeAnalyticsTracker + + ).also { + viewModel = it + initialState = state + } + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeDrawableProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeDrawableProvider.kt new file mode 100644 index 0000000000..26fa7af3f5 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeDrawableProvider.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import im.vector.app.core.resources.DrawableProvider +import io.mockk.every +import io.mockk.mockk + +class FakeDrawableProvider { + val instance = mockk() + + init { + every { instance.getDrawable(any()) } returns mockk() + every { instance.getDrawable(any(), any()) } returns mockk() + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeHomeLayoutPreferencesStore.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeLayoutPreferencesStore.kt new file mode 100644 index 0000000000..bd5dd20d37 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeLayoutPreferencesStore.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import im.vector.app.features.home.room.list.home.HomeLayoutPreferencesStore +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.MutableSharedFlow + +class FakeHomeLayoutPreferencesStore { + + private val _areRecentsEnabledFlow = MutableSharedFlow() + private val _areFiltersEnabledFlow = MutableSharedFlow() + private val _isAZOrderingEnabledFlow = MutableSharedFlow() + + val instance = mockk(relaxed = true) { + every { areRecentsEnabledFlow } returns _areRecentsEnabledFlow + every { areFiltersEnabledFlow } returns _areFiltersEnabledFlow + every { isAZOrderingEnabledFlow } returns _isAZOrderingEnabledFlow + } + + suspend fun givenRecentsEnabled(enabled: Boolean) { + _areRecentsEnabledFlow.emit(enabled) + } + + suspend fun givenFiltersEnabled(enabled: Boolean) { + _areFiltersEnabledFlow.emit(enabled) + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt index 506e96ba11..e957266383 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt @@ -30,4 +30,8 @@ class FakeRoomService( fun getRoomSummaryReturns(roomSummary: RoomSummary?) { every { getRoomSummary(any()) } returns roomSummary } + + fun set(roomSummary: RoomSummary?) { + every { getRoomSummary(any()) } returns roomSummary + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt index 4171307922..0360f45d85 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt @@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.flow.FlowSession import org.matrix.android.sdk.flow.flow @@ -41,6 +42,7 @@ class FakeSession( val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(), val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(), val fakeRoomService: FakeRoomService = FakeRoomService(), + val fakeUserService: FakeUserService = FakeUserService(), private val fakeEventService: FakeEventService = FakeEventService(), ) : Session by mockk(relaxed = true) { @@ -58,6 +60,7 @@ class FakeSession( override fun sharedSecretStorageService() = fakeSharedSecretStorageService override fun roomService() = fakeRoomService override fun eventService() = fakeEventService + override fun userService() = fakeUserService fun givenVectorStore(vectorSessionStore: VectorSessionStore) { coEvery { @@ -88,8 +91,10 @@ class FakeSession( /** * Do not forget to call mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt") in the setup method of the tests. */ + @SuppressWarnings("all") fun givenFlowSession(): FlowSession { val fakeFlowSession = mockk() + every { flow() } returns fakeFlowSession return fakeFlowSession } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt index 28d9f7c732..83f8607261 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt @@ -17,6 +17,7 @@ package im.vector.app.test.fakes import im.vector.app.core.resources.StringProvider +import io.mockk.InternalPlatformDsl.toStr import io.mockk.every import io.mockk.mockk @@ -27,6 +28,9 @@ class FakeStringProvider { every { instance.getString(any()) } answers { "test-${args[0]}" } + every { instance.getString(any(), any()) } answers { + "test-${args[0]}-${args[1].toStr()}" + } every { instance.getQuantityString(any(), any(), any()) } answers { "test-${args[0]}-${args[1]}" diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt new file mode 100644 index 0000000000..e9a29fcdf5 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import org.matrix.android.sdk.api.session.user.UserService +import org.matrix.android.sdk.api.session.user.model.User + +class FakeUserService: UserService by mockk() { + + private val userIdSlot = slot() + + init { + every { getUser(capture(userIdSlot)) } answers { User(userId = userIdSlot.captured) } + } +} From 9f8d37718182f803cc24dfffd94681e8c0df9055 Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Tue, 18 Oct 2022 10:47:44 +0200 Subject: [PATCH 002/237] lint --- .../java/im/vector/app/features/home/InvitesViewModelTest.kt | 1 - .../src/test/java/im/vector/app/test/fakes/FakeUserService.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt b/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt index 48bf232327..99f19bd99c 100644 --- a/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt @@ -100,7 +100,6 @@ class InvitesViewModelTest { session = fakeSession, stringProvider = fakeStringProvider.instance, drawableProvider = fakeDrawableProvider.instance, - ).also { viewModel = it initialState = state diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt index e9a29fcdf5..065796934c 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt @@ -22,7 +22,7 @@ import io.mockk.slot import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.user.model.User -class FakeUserService: UserService by mockk() { +class FakeUserService : UserService by mockk() { private val userIdSlot = slot() From 312d778c869d2690f571cd0a8aa70ab7e5006b03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Dec 2022 23:04:34 +0000 Subject: [PATCH 003/237] Bump flipper from 0.176.0 to 0.176.1 Bumps `flipper` from 0.176.0 to 0.176.1. Updates `flipper` from 0.176.0 to 0.176.1 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.176.0...v0.176.1) Updates `flipper-network-plugin` from 0.176.0 to 0.176.1 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.176.0...v0.176.1) --- updated-dependencies: - dependency-name: com.facebook.flipper:flipper dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: com.facebook.flipper:flipper-network-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index dbb5f5fe05..2efe8db49e 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -17,7 +17,7 @@ def markwon = "4.6.2" def moshi = "1.14.0" def lifecycle = "2.5.1" def flowBinding = "1.2.0" -def flipper = "0.176.0" +def flipper = "0.176.1" def epoxy = "5.0.0" def mavericks = "3.0.1" def glide = "4.14.2" From 256dfe1b58e76a4bf10c8b5255cae27aae1efc67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Dec 2022 23:01:54 +0000 Subject: [PATCH 004/237] Bump com.autonomousapps.dependency-analysis from 1.17.0 to 1.18.0 Bumps com.autonomousapps.dependency-analysis from 1.17.0 to 1.18.0. --- updated-dependencies: - dependency-name: com.autonomousapps.dependency-analysis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0f94fc418c..588ebf1537 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ plugins { id "com.google.devtools.ksp" version "1.7.22-1.0.8" // Dependency Analysis - id 'com.autonomousapps.dependency-analysis' version "1.17.0" + id 'com.autonomousapps.dependency-analysis' version "1.18.0" // Gradle doctor id "com.osacky.doctor" version "0.8.1" } From 32fcdad91696884f566ed7838415bba2d391fa4a Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Fri, 23 Dec 2022 13:19:35 +0100 Subject: [PATCH 005/237] fixed compilation error --- .../im/vector/app/features/home/RoomsListViewModelTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt b/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt index e2fef25aba..a601505d6c 100644 --- a/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt @@ -18,7 +18,6 @@ package im.vector.app.features.home import android.widget.ImageView import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import arrow.core.Option import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.R import im.vector.app.core.platform.StateView @@ -45,6 +44,7 @@ import org.junit.Test import org.matrix.android.sdk.api.query.SpaceFilter import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.FlowSession @@ -72,7 +72,7 @@ class RoomsListViewModelTest { mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt") fakeFLowSession = fakeSession.givenFlowSession() - every { fakeSpaceStateHandler.getSelectedSpaceFlow() } returns flowOf(Option.empty()) + every { fakeSpaceStateHandler.getSelectedSpaceFlow() } returns flowOf(Optional.empty()) every { fakeSpaceStateHandler.getCurrentSpace() } returns null every { fakeFLowSession.liveRoomSummaries(any(), any()) } returns flowOf(emptyList()) From f6cabfffd9832a4045bfd64651a2895cbd650451 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 23 Dec 2022 16:24:10 +0300 Subject: [PATCH 006/237] Set poll end event type as displayable. --- .../room/detail/timeline/helper/TimelineDisplayableEvents.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 51e961f247..2dcb6cc6d8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -55,6 +55,7 @@ object TimelineDisplayableEvents { VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, ) + EventType.POLL_START.values + + EventType.POLL_END.values + EventType.STATE_ROOM_BEACON_INFO.values + EventType.BEACON_LOCATION_DATA.values } From 486968fdc2bfb0abf7661ecc1cd45404684c55fd Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 26 Dec 2022 14:41:38 +0300 Subject: [PATCH 007/237] Render ended poll. --- .../model/message/MessageEndPollContent.kt | 12 +++++-- .../session/room/model/message/MessageType.kt | 1 + .../session/room/timeline/TimelineEvent.kt | 2 ++ .../timeline/factory/MessageItemFactory.kt | 29 ++++++++++++++++ .../timeline/factory/TimelineItemFactory.kt | 4 +-- .../helper/MessageInformationDataFactory.kt | 33 +++++++++++-------- 6 files changed, 63 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt index f0511903d0..6e31320b13 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent /** @@ -25,5 +26,12 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon */ @JsonClass(generateAdapter = true) data class MessageEndPollContent( - @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? = null -) + /** + * Local message type, not from server. + */ + @Transient + override val msgType: String = MessageType.MSGTYPE_POLL_END, + @Json(name = "body") override val body: String = "", + @Json(name = "m.new_content") override val newContent: Content? = null, + @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null +) : MessageContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt index e97a5be303..f6b7675d4f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt @@ -36,6 +36,7 @@ object MessageType { // Because poll events are not message events and they don't have msgtype field const val MSGTYPE_POLL_START = "org.matrix.android.sdk.poll.start" const val MSGTYPE_POLL_RESPONSE = "org.matrix.android.sdk.poll.response" + const val MSGTYPE_POLL_END = "org.matrix.android.sdk.poll.end" const val MSGTYPE_CONFETTI = "nic.custom.confetti" const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 9053425a39..6320ea964d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoCo import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody +import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -148,6 +149,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? { // so toModel won't parse them correctly // It's discriminated on event type instead. Maybe it shouldn't be MessageContent at all to avoid confusion? in EventType.POLL_START.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() + in EventType.POLL_END.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() in EventType.STATE_ROOM_BEACON_INFO.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() in EventType.BEACON_LOCATION_DATA.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() else -> (getLastEditNewContent() ?: root.getClearContent()).toModel() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 42e031a3c4..b4ba146176 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -91,11 +91,13 @@ import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageEmoteContent +import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent @@ -203,6 +205,7 @@ class MessageItemFactory @Inject constructor( is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) + is MessageEndPollContent -> buildEndedPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(event, highlight, attributes) is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, attributes) @@ -261,6 +264,32 @@ class MessageItemFactory @Inject constructor( .callback(callback) } + + + private fun buildEndedPollItem( + endedPollContent: MessageEndPollContent, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, + ): PollItem? { + val pollStartEventId = endedPollContent.relatesTo?.eventId ?: return null + val pollStartEvent = session.roomService().getRoom(roomId)?.getTimelineEvent(pollStartEventId) + val pollContent = pollStartEvent?.root?.getClearContent()?.toModel() ?: return null + + val aggregatedInformationData = informationData.copy( + pollResponseAggregatedSummary = messageInformationDataFactory.mapPollResponseSummary(pollStartEvent.annotations?.pollResponseSummary) + ) + + return buildPollItem( + pollContent, + aggregatedInformationData, + highlight, + callback, + attributes + ) + } + private fun createPollQuestion( informationData: MessageInformationData, question: String, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index ae3ea143a7..61b2385d1d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -102,6 +102,7 @@ class TimelineItemFactory @Inject constructor( // Message itemsX EventType.STICKER, in EventType.POLL_START.values, + in EventType.POLL_END.values, EventType.MESSAGE -> messageItemFactory.create(params) EventType.REDACTION, EventType.KEY_VERIFICATION_ACCEPT, @@ -114,8 +115,7 @@ class TimelineItemFactory @Inject constructor( EventType.CALL_SELECT_ANSWER, EventType.CALL_NEGOTIATE, EventType.REACTION, - in EventType.POLL_RESPONSE.values, - in EventType.POLL_END.values -> noticeItemFactory.create(params) + in EventType.POLL_RESPONSE.values -> noticeItemFactory.create(params) in EventType.BEACON_LOCATION_DATA.values -> { if (event.root.isRedacted()) { messageItemFactory.create(params) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 57a4388f74..d356c8a7d2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isSticker import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent +import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.message.MessageType @@ -99,20 +100,7 @@ class MessageInformationDataFactory @Inject constructor( memberName = event.senderInfo.disambiguatedDisplayName, messageLayout = messageLayout, reactionsSummary = reactionsSummaryFactory.create(event), - pollResponseAggregatedSummary = event.annotations?.pollResponseSummary?.let { - PollResponseData( - myVote = it.aggregatedContent?.myVote, - isClosed = it.closedTime != null, - votes = it.aggregatedContent?.votesSummary?.mapValues { votesSummary -> - PollVoteSummaryData( - total = votesSummary.value.total, - percentage = votesSummary.value.percentage - ) - }, - winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, - totalVotes = it.aggregatedContent?.totalVotes ?: 0 - ) - }, + pollResponseAggregatedSummary = mapPollResponseSummary(event.annotations?.pollResponseSummary), hasBeenEdited = event.hasBeenEdited(), hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false, referencesInfoData = event.annotations?.referencesAggregatedSummary?.let { referencesAggregatedSummary -> @@ -133,6 +121,23 @@ class MessageInformationDataFactory @Inject constructor( ) } + fun mapPollResponseSummary(pollResponseSummary: PollResponseAggregatedSummary?): PollResponseData? { + return pollResponseSummary?.let { + PollResponseData( + myVote = it.aggregatedContent?.myVote, + isClosed = it.closedTime != null, + votes = it.aggregatedContent?.votesSummary?.mapValues { votesSummary -> + PollVoteSummaryData( + total = votesSummary.value.total, + percentage = votesSummary.value.percentage + ) + }, + winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, + totalVotes = it.aggregatedContent?.totalVotes ?: 0 + ) + } + } + private fun getSenderId(event: TimelineEvent) = if (event.isEncrypted()) { event.root.toValidDecryptedEvent()?.let { session.cryptoService().deviceWithIdentityKey(it.cryptoSenderKey, it.algorithm)?.userId From 374445eed6001cc2ddc2a940b7c516e57f18766a Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 26 Dec 2022 17:57:04 +0300 Subject: [PATCH 008/237] Update poll layout. --- .../detail/timeline/item/PollOptionView.kt | 11 ++++---- .../src/main/res/layout/item_poll_option.xml | 27 ++++++------------- .../res/layout/item_timeline_event_poll.xml | 1 - 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt index 20aa6e3af2..38e6c5e6d7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt @@ -53,35 +53,36 @@ class PollOptionView @JvmOverloads constructor( private fun renderPollSending() { views.optionCheckImageView.isVisible = false - views.optionWinnerImageView.isVisible = false + views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) hideVotes() renderVoteSelection(false) } private fun renderPollEnded(state: PollOptionViewState.PollEnded) { views.optionCheckImageView.isVisible = false - views.optionWinnerImageView.isVisible = state.isWinner + val drawableStart = if (state.isWinner) R.drawable.ic_poll_winner else 0 + views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, 0, 0, 0) showVotes(state.voteCount, state.votePercentage) renderVoteSelection(state.isWinner) } private fun renderPollReady() { views.optionCheckImageView.isVisible = true - views.optionWinnerImageView.isVisible = false + views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) hideVotes() renderVoteSelection(false) } private fun renderPollVoted(state: PollOptionViewState.PollVoted) { views.optionCheckImageView.isVisible = true - views.optionWinnerImageView.isVisible = false + views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) showVotes(state.voteCount, state.votePercentage) renderVoteSelection(state.isSelected) } private fun renderPollUndisclosed(state: PollOptionViewState.PollUndisclosed) { views.optionCheckImageView.isVisible = true - views.optionWinnerImageView.isVisible = false + views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) hideVotes() renderVoteSelection(state.isSelected) } diff --git a/vector/src/main/res/layout/item_poll_option.xml b/vector/src/main/res/layout/item_poll_option.xml index 986bfeaa35..ff7d4498fb 100644 --- a/vector/src/main/res/layout/item_poll_option.xml +++ b/vector/src/main/res/layout/item_poll_option.xml @@ -36,34 +36,23 @@ android:layout_marginStart="12dp" android:layout_marginTop="16dp" android:layout_marginEnd="12dp" - app:layout_constraintEnd_toEndOf="@id/optionWinnerImageView" + app:layout_constraintEnd_toStartOf="@id/optionVoteCountTextView" app:layout_constraintStart_toEndOf="@id/optionCheckImageView" app:layout_constraintTop_toTopOf="parent" tools:text="@sample/poll.json/data/answer" /> - - @@ -78,9 +67,9 @@ android:layout_marginBottom="8dp" android:progressDrawable="@drawable/poll_option_progressbar_checked" app:layout_constraintBottom_toBottomOf="@id/optionBorderImageView" - app:layout_constraintEnd_toStartOf="@id/optionVoteCountTextView" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/optionNameTextView" tools:progress="60" /> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/item_timeline_event_poll.xml b/vector/src/main/res/layout/item_timeline_event_poll.xml index 393b736260..a3fa07ade2 100644 --- a/vector/src/main/res/layout/item_timeline_event_poll.xml +++ b/vector/src/main/res/layout/item_timeline_event_poll.xml @@ -13,7 +13,6 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:textColor="?vctr_content_primary" - android:textStyle="bold" app:layout_constraintHorizontal_bias="0" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" From b53615a8d7f827b026b481a7f4a2cdf2abb35837 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 2 Jan 2023 14:36:54 +0300 Subject: [PATCH 009/237] Add reply action for poll end events. --- library/ui-strings/src/main/res/values/strings.xml | 1 + .../im/vector/app/core/extensions/TimelineEvent.kt | 2 +- .../timeline/action/CheckIfCanReplyEventUseCase.kt | 11 +++++++++-- .../detail/timeline/action/MessageActionsViewModel.kt | 5 +++-- .../render/ProcessBodyOfReplyToEventUseCase.kt | 6 +++++- .../action/CheckIfCanReplyEventUseCaseTest.kt | 1 + 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 73cb60bb68..2b8e397521 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3501,4 +3501,5 @@ sent a video. sent a sticker. created a poll. + ended a poll. diff --git a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt index c94f9cd921..89bd28fc93 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt @@ -27,7 +27,7 @@ import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent fun TimelineEvent.canReact(): Boolean { // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment - return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values && + return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values + EventType.POLL_END.values && root.sendState == SendState.SYNCED && !root.isRedacted() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt index a9df059cc1..fdd94d1559 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt @@ -25,8 +25,14 @@ import javax.inject.Inject class CheckIfCanReplyEventUseCase @Inject constructor() { fun execute(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean { - // Only EventType.MESSAGE, EventType.POLL_START and EventType.STATE_ROOM_BEACON_INFO event types are supported for the moment - if (event.root.getClearType() !in EventType.STATE_ROOM_BEACON_INFO.values + EventType.POLL_START.values + EventType.MESSAGE) return false + // Only EventType.MESSAGE, EventType.POLL_START, EventType.POLL_END and EventType.STATE_ROOM_BEACON_INFO event types are supported for the moment + if (event.root.getClearType() !in + EventType.STATE_ROOM_BEACON_INFO.values + + EventType.POLL_START.values + + EventType.POLL_END.values + + EventType.MESSAGE + ) return false + if (!actionPermissions.canSendMessage) return false return when (messageContent?.msgType) { MessageType.MSGTYPE_TEXT, @@ -37,6 +43,7 @@ class CheckIfCanReplyEventUseCase @Inject constructor() { MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_FILE, MessageType.MSGTYPE_POLL_START, + MessageType.MSGTYPE_POLL_END, MessageType.MSGTYPE_BEACON_INFO, MessageType.MSGTYPE_LOCATION -> true else -> false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index a6d7e8386f..646cfa50d2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -498,6 +498,7 @@ class MessageActionsViewModel @AssistedInject constructor( MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_FILE, MessageType.MSGTYPE_POLL_START, + MessageType.MSGTYPE_POLL_END, MessageType.MSGTYPE_STICKER_LOCAL -> event.root.threadDetails?.isRootThread ?: false else -> false } @@ -529,8 +530,8 @@ class MessageActionsViewModel @AssistedInject constructor( } private fun canViewReactions(event: TimelineEvent): Boolean { - // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment - if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values) return false + // Only event of type EventType.MESSAGE, EventType.STICKER, EventType.POLL_START, EventType.POLL_END are supported for the moment + if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values + EventType.POLL_END.values) return false return event.annotations?.reactionsSummary?.isNotEmpty() ?: false } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt index 2197d89a2c..09fccdcbee 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.render import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.StringProvider +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.getPollQuestion import org.matrix.android.sdk.api.session.events.model.isAudioMessage import org.matrix.android.sdk.api.session.events.model.isFileMessage @@ -93,10 +94,13 @@ class ProcessBodyOfReplyToEventUseCase @Inject constructor( ) } repliedToEvent.isPoll() -> { + val fallbackText = if (repliedToEvent.type in EventType.POLL_START.values) + stringProvider.getString(R.string.message_reply_to_sender_created_poll) + else stringProvider.getString(R.string.message_reply_to_sender_ended_poll) matrixFormattedBody.replaceRange( afterBreakingLineIndex, endOfBlockQuoteIndex, - repliedToEvent.getPollQuestion() ?: stringProvider.getString(R.string.message_reply_to_sender_created_poll) + repliedToEvent.getPollQuestion() ?: fallbackText ) } repliedToEvent.isLiveLocation() -> { diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt index 51082e0e06..1244a0a108 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt @@ -78,6 +78,7 @@ class CheckIfCanReplyEventUseCaseTest { MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_FILE, MessageType.MSGTYPE_POLL_START, + MessageType.MSGTYPE_POLL_END, MessageType.MSGTYPE_BEACON_INFO, MessageType.MSGTYPE_LOCATION ) From 89f91a2ecd85fcd18115afba6296d236491a69fb Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 2 Jan 2023 14:37:20 +0300 Subject: [PATCH 010/237] Fix unit test. --- .../room/detail/timeline/style/TimelineMessageLayoutFactory.kt | 1 + .../timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index c207a5f67e..6e34aeeca2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -50,6 +50,7 @@ class TimelineMessageLayoutFactory @Inject constructor( EventType.STICKER, ) + EventType.POLL_START.values + + EventType.POLL_END.values + EventType.STATE_ROOM_BEACON_INFO.values // Can't be rendered in bubbles, so get back to default layout diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt index f612861511..ff10063d1a 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt @@ -29,6 +29,7 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.getPollQuestion import org.matrix.android.sdk.api.session.events.model.isAudioMessage import org.matrix.android.sdk.api.session.events.model.isFileMessage @@ -158,6 +159,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { // Given givenTypeOfRepliedEvent(isPollMessage = true) givenNewContentForId(R.string.message_reply_to_sender_created_poll) + every { fakeRepliedEvent.type } returns EventType.POLL_START.unstable every { fakeRepliedEvent.getPollQuestion() } returns null executeAndAssertResult() @@ -168,6 +170,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { // Given givenTypeOfRepliedEvent(isPollMessage = true) givenNewContentForId(R.string.message_reply_to_sender_created_poll) + every { fakeRepliedEvent.type } returns EventType.POLL_START.unstable every { fakeRepliedEvent.getPollQuestion() } returns A_NEW_CONTENT executeAndAssertResult() From 89a7d708497098084f6dc8190f9e173a9dddeb29 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 2 Jan 2023 16:16:23 +0300 Subject: [PATCH 011/237] Implement reply preview for poll.end events. --- library/ui-strings/src/main/res/values/strings.xml | 1 + .../room/detail/composer/PlainTextComposerLayout.kt | 2 ++ .../detail/timeline/format/EventDetailsFormatter.kt | 5 +++++ .../render/ProcessBodyOfReplyToEventUseCase.kt | 10 +++++++--- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 2b8e397521..100635cc27 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3502,4 +3502,5 @@ sent a sticker. created a poll. ended a poll. + Ended poll diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt index 8f4dd9b71d..cf127d834f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt @@ -44,6 +44,7 @@ import org.commonmark.parser.Parser import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -181,6 +182,7 @@ class PlainTextComposerLayout @JvmOverloads constructor( is MessageAudioContent -> getAudioContentBodyText(messageContent) is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() is MessageBeaconInfoContent -> resources.getString(R.string.live_location_description) + is MessageEndPollContent -> resources.getString(R.string.message_reply_to_ended_poll_preview) else -> messageContent?.body.orEmpty() } var formattedBody: CharSequence? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt index 2233a53eda..f936093a3b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt @@ -17,11 +17,13 @@ package im.vector.app.features.home.room.detail.timeline.format import android.content.Context +import im.vector.app.R import im.vector.app.core.utils.TextUtils import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.isAudioMessage import org.matrix.android.sdk.api.session.events.model.isFileMessage import org.matrix.android.sdk.api.session.events.model.isImageMessage +import org.matrix.android.sdk.api.session.events.model.isPollEnd import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent @@ -51,10 +53,13 @@ class EventDetailsFormatter @Inject constructor( event.isVideoMessage() -> formatForVideoMessage(event) event.isAudioMessage() -> formatForAudioMessage(event) event.isFileMessage() -> formatForFileMessage(event) + event.isPollEnd() -> formatPollEndMessage() else -> null } } + private fun formatPollEndMessage() = context.getString(R.string.message_reply_to_ended_poll_preview) + /** * Example: "1024 x 720 - 670 kB". */ diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt index 09fccdcbee..dded85e186 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt @@ -26,6 +26,8 @@ import org.matrix.android.sdk.api.session.events.model.isFileMessage import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isLiveLocation import org.matrix.android.sdk.api.session.events.model.isPoll +import org.matrix.android.sdk.api.session.events.model.isPollEnd +import org.matrix.android.sdk.api.session.events.model.isPollStart import org.matrix.android.sdk.api.session.events.model.isSticker import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.events.model.isVoiceMessage @@ -94,9 +96,11 @@ class ProcessBodyOfReplyToEventUseCase @Inject constructor( ) } repliedToEvent.isPoll() -> { - val fallbackText = if (repliedToEvent.type in EventType.POLL_START.values) - stringProvider.getString(R.string.message_reply_to_sender_created_poll) - else stringProvider.getString(R.string.message_reply_to_sender_ended_poll) + val fallbackText = when { + repliedToEvent.isPollStart() -> stringProvider.getString(R.string.message_reply_to_sender_created_poll) + repliedToEvent.isPollEnd() -> stringProvider.getString(R.string.message_reply_to_sender_ended_poll) + else -> "" + } matrixFormattedBody.replaceRange( afterBreakingLineIndex, endOfBlockQuoteIndex, From f2359ccac297ce221a57740cc96fdb0e7dae0699 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 5 Jan 2023 14:54:16 +0300 Subject: [PATCH 012/237] Implement ended poll indicator. --- .../src/main/res/values/strings.xml | 1 + .../sdk/api/session/events/model/Event.kt | 4 ++-- .../timeline/factory/MessageItemFactory.kt | 7 +++++-- .../room/detail/timeline/item/PollItem.kt | 7 +++++++ .../res/layout/item_timeline_event_poll.xml | 20 +++++++++++++++---- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 100635cc27..69ae724e49 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3190,6 +3190,7 @@ Voters see results as soon as they have voted Closed poll Results are only revealed when you end the poll + Ended the poll. Share location diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 9b5f4ac19f..e84d67fecd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -219,7 +219,7 @@ data class Event( if (isRedacted()) return "Message removed" val text = getDecryptedValue() ?: run { if (isPoll()) { - return getPollQuestion() ?: "created a poll." + return getPollQuestion() ?: if (isPollStart()) "created a poll." else if (isPollEnd()) "ended a poll." else null } return null } @@ -232,7 +232,7 @@ data class Event( isImageMessage() -> "sent an image." isVideoMessage() -> "sent a video." isSticker() -> "sent a sticker." - isPoll() -> getPollQuestion() ?: "created a poll." + isPoll() -> getPollQuestion() ?: if (isPollStart()) "created a poll." else if (isPollEnd()) "ended a poll." else null isLiveLocation() -> "Live location." isLocationMessage() -> "has shared their location." else -> text diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index b4ba146176..a0cfcc77e7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -204,7 +204,7 @@ class MessageItemFactory @Inject constructor( is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes) is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) + is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes, isEnded = false) is MessageEndPollContent -> buildEndedPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(event, highlight, attributes) @@ -248,6 +248,7 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, + isEnded: Boolean, ): PollItem { val pollViewState = pollItemViewStateFactory.create(pollContent, informationData) @@ -259,6 +260,7 @@ class MessageItemFactory @Inject constructor( .votesStatus(pollViewState.votesStatus) .optionViewStates(pollViewState.optionViewStates.orEmpty()) .edited(informationData.hasBeenEdited) + .ended(isEnded) .highlighted(highlight) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) @@ -286,7 +288,8 @@ class MessageItemFactory @Inject constructor( aggregatedInformationData, highlight, callback, - attributes + attributes, + isEnded = true ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt index 54be4092ed..6fe19e9762 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.item import android.widget.LinearLayout import android.widget.TextView import androidx.core.view.children +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -50,6 +51,9 @@ abstract class PollItem : AbsMessageItem() { @EpoxyAttribute lateinit var optionViewStates: List + @EpoxyAttribute + var ended: Boolean = false + override fun getViewStubId() = STUB_ID override fun bind(holder: Holder) { @@ -75,6 +79,8 @@ abstract class PollItem : AbsMessageItem() { it.setOnClickListener { onPollItemClick(optionViewState) } } } + + holder.endedPollTextView.isVisible = ended } private fun onPollItemClick(optionViewState: PollOptionViewState) { @@ -89,6 +95,7 @@ abstract class PollItem : AbsMessageItem() { val questionTextView by bind(R.id.questionTextView) val optionsContainer by bind(R.id.optionsContainer) val votesStatusTextView by bind(R.id.optionsVotesStatusTextView) + val endedPollTextView by bind(R.id.endedPollTextView) } companion object { diff --git a/vector/src/main/res/layout/item_timeline_event_poll.xml b/vector/src/main/res/layout/item_timeline_event_poll.xml index a3fa07ade2..9151fc68cf 100644 --- a/vector/src/main/res/layout/item_timeline_event_poll.xml +++ b/vector/src/main/res/layout/item_timeline_event_poll.xml @@ -2,9 +2,21 @@ + android:layout_height="wrap_content" + android:minWidth="@dimen/chat_bubble_fixed_size"> + + Date: Thu, 5 Jan 2023 15:42:32 +0300 Subject: [PATCH 013/237] Add changelog. --- changelog.d/7900.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7900.feature diff --git a/changelog.d/7900.feature b/changelog.d/7900.feature new file mode 100644 index 0000000000..c3cce1e0e6 --- /dev/null +++ b/changelog.d/7900.feature @@ -0,0 +1 @@ +Render ended polls From ad30ca867169dfffd28944735e928870f581e54f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 6 Jan 2023 14:52:41 +0300 Subject: [PATCH 014/237] Lint fixes. --- .../home/room/detail/timeline/factory/MessageItemFactory.kt | 2 -- .../detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt | 1 - 2 files changed, 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index a0cfcc77e7..0cbddffef9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -266,8 +266,6 @@ class MessageItemFactory @Inject constructor( .callback(callback) } - - private fun buildEndedPollItem( endedPollContent: MessageEndPollContent, informationData: MessageInformationData, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt index dded85e186..ff814d4cbc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt @@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.render import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.StringProvider -import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.getPollQuestion import org.matrix.android.sdk.api.session.events.model.isAudioMessage import org.matrix.android.sdk.api.session.events.model.isFileMessage From b73485e7b378c9e05c6abac0e08d8fcff1418876 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 6 Jan 2023 17:18:50 +0300 Subject: [PATCH 015/237] Fix unit tests. --- library/ui-strings/src/main/res/values/strings.xml | 1 + .../timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 4dbb63fb10..08bc7ced09 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3176,6 +3176,7 @@ Final result based on %1$d votes End poll + winner option End this poll? This will stop people from being able to vote and will display the final results of the poll. diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt index ff10063d1a..69bcb81d5f 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt @@ -159,7 +159,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { // Given givenTypeOfRepliedEvent(isPollMessage = true) givenNewContentForId(R.string.message_reply_to_sender_created_poll) - every { fakeRepliedEvent.type } returns EventType.POLL_START.unstable + every { fakeRepliedEvent.getClearType() } returns EventType.POLL_START.unstable every { fakeRepliedEvent.getPollQuestion() } returns null executeAndAssertResult() @@ -170,7 +170,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { // Given givenTypeOfRepliedEvent(isPollMessage = true) givenNewContentForId(R.string.message_reply_to_sender_created_poll) - every { fakeRepliedEvent.type } returns EventType.POLL_START.unstable + every { fakeRepliedEvent.getClearType() } returns EventType.POLL_START.unstable every { fakeRepliedEvent.getPollQuestion() } returns A_NEW_CONTENT executeAndAssertResult() From f33372411b6d2e16555bc1332fd13c1df9105a52 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 6 Jan 2023 18:23:41 +0300 Subject: [PATCH 016/237] Lint fix. --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 08bc7ced09..4731a6b5db 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3177,7 +3177,7 @@ End poll - winner option + winner option End this poll? This will stop people from being able to vote and will display the final results of the poll. End poll From 2b26f2b22161f305eca45e0f929a9ebeca3c538a Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 9 Jan 2023 16:00:24 +0300 Subject: [PATCH 017/237] Fix related event id is null issue. --- .../room/detail/timeline/factory/MessageItemFactory.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 0cbddffef9..824547b8d0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -111,8 +111,10 @@ import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent +import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.api.util.MimeTypes +import timber.log.Timber import javax.inject.Inject class MessageItemFactory @Inject constructor( @@ -205,7 +207,7 @@ class MessageItemFactory @Inject constructor( is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes, isEnded = false) - is MessageEndPollContent -> buildEndedPollItem(messageContent, informationData, highlight, callback, attributes) + is MessageEndPollContent -> buildEndedPollItem(event.getRelationContent()?.eventId, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(event, highlight, attributes) is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, attributes) @@ -267,13 +269,15 @@ class MessageItemFactory @Inject constructor( } private fun buildEndedPollItem( - endedPollContent: MessageEndPollContent, + pollStartEventId: String?, informationData: MessageInformationData, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, ): PollItem? { - val pollStartEventId = endedPollContent.relatesTo?.eventId ?: return null + pollStartEventId ?: return null.also { + Timber.e("### buildEndedPollItem. Cannot render poll end event because poll start event id is null") + } val pollStartEvent = session.roomService().getRoom(roomId)?.getTimelineEvent(pollStartEventId) val pollContent = pollStartEvent?.root?.getClearContent()?.toModel() ?: return null From ddc190ff3eda8cc8f6c71bb65f2f0c3d9c3f78ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 23:12:13 +0000 Subject: [PATCH 018/237] Bump danger/danger-js from 11.2.0 to 11.2.1 Bumps [danger/danger-js](https://github.com/danger/danger-js) from 11.2.0 to 11.2.1. - [Release notes](https://github.com/danger/danger-js/releases) - [Changelog](https://github.com/danger/danger-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/danger/danger-js/compare/11.2.0...11.2.1) --- updated-dependencies: - dependency-name: danger/danger-js dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/danger.yml | 2 +- .github/workflows/quality.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 8752f339bd..4901a84070 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -11,7 +11,7 @@ jobs: - run: | npm install --save-dev @babel/plugin-transform-flow-strip-types - name: Danger - uses: danger/danger-js@11.2.0 + uses: danger/danger-js@11.2.1 with: args: "--dangerfile ./tools/danger/dangerfile.js" env: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index fae8d97688..c32cb65c42 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -66,7 +66,7 @@ jobs: yarn add danger-plugin-lint-report --dev - name: Danger lint if: always() - uses: danger/danger-js@11.2.0 + uses: danger/danger-js@11.2.1 with: args: "--dangerfile ./tools/danger/dangerfile-lint.js" env: From a8b111dc8c8a525b23537c1138eae08e23b5f9e8 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 10 Jan 2023 15:04:13 +0300 Subject: [PATCH 019/237] Code review fixes. --- .../src/main/res/values/strings.xml | 1 + .../sdk/api/session/events/model/Event.kt | 14 +++- .../timeline/factory/MessageItemFactory.kt | 6 +- .../timeline/format/EventDetailsFormatter.kt | 4 ++ .../helper/MessageInformationDataFactory.kt | 25 +------ .../helper/PollResponseDataFactory.kt | 67 +++++++++++++++++++ .../action/CheckIfCanReplyEventUseCaseTest.kt | 2 +- .../ProcessBodyOfReplyToEventUseCaseTest.kt | 11 +++ 8 files changed, 100 insertions(+), 30 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 81f4f27127..0a12e859b5 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3509,6 +3509,7 @@ sent a sticker. created a poll. ended a poll. + Poll Ended poll Access Token diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 357c2b9608..40c69ceb66 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -248,7 +248,7 @@ data class Event( if (isRedacted()) return "Message removed" val text = getDecryptedValue() ?: run { if (isPoll()) { - return getPollQuestion() ?: if (isPollStart()) "created a poll." else if (isPollEnd()) "ended a poll." else null + return getTextSummaryForPoll() } return null } @@ -261,13 +261,23 @@ data class Event( isImageMessage() -> "sent an image." isVideoMessage() -> "sent a video." isSticker() -> "sent a sticker." - isPoll() -> getPollQuestion() ?: if (isPollStart()) "created a poll." else if (isPollEnd()) "ended a poll." else null + isPoll() -> getTextSummaryForPoll() isLiveLocation() -> "Live location." isLocationMessage() -> "has shared their location." else -> text } } + private fun getTextSummaryForPoll(): String? { + val pollQuestion = getPollQuestion() + return when { + pollQuestion != null -> pollQuestion + isPollStart() -> "created a poll." + isPollEnd() -> "ended a poll." + else -> null + } + } + private fun Event.isQuote(): Boolean { if (isReplyRenderedInThread()) return false return getDecryptedValue("formatted_body")?.contains("
") ?: false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 824547b8d0..219ccbe11c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -281,13 +281,9 @@ class MessageItemFactory @Inject constructor( val pollStartEvent = session.roomService().getRoom(roomId)?.getTimelineEvent(pollStartEventId) val pollContent = pollStartEvent?.root?.getClearContent()?.toModel() ?: return null - val aggregatedInformationData = informationData.copy( - pollResponseAggregatedSummary = messageInformationDataFactory.mapPollResponseSummary(pollStartEvent.annotations?.pollResponseSummary) - ) - return buildPollItem( pollContent, - aggregatedInformationData, + informationData, highlight, callback, attributes, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt index f936093a3b..1d3f016951 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.events.model.isAudioMessage import org.matrix.android.sdk.api.session.events.model.isFileMessage import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isPollEnd +import org.matrix.android.sdk.api.session.events.model.isPollStart import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent @@ -53,11 +54,14 @@ class EventDetailsFormatter @Inject constructor( event.isVideoMessage() -> formatForVideoMessage(event) event.isAudioMessage() -> formatForAudioMessage(event) event.isFileMessage() -> formatForFileMessage(event) + event.isPollStart() -> formatPollMessage() event.isPollEnd() -> formatPollEndMessage() else -> null } } + private fun formatPollMessage() = context.getString(R.string.message_reply_to_poll_preview) + private fun formatPollEndMessage() = context.getString(R.string.message_reply_to_ended_poll_preview) /** diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index d356c8a7d2..3ee309425a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -23,8 +23,6 @@ import im.vector.app.core.extensions.localDateTime import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData -import im.vector.app.features.home.room.detail.timeline.item.PollResponseData -import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory @@ -38,7 +36,6 @@ import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isSticker import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent -import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.message.MessageType @@ -55,7 +52,8 @@ class MessageInformationDataFactory @Inject constructor( private val session: Session, private val dateFormatter: VectorDateFormatter, private val messageLayoutFactory: TimelineMessageLayoutFactory, - private val reactionsSummaryFactory: ReactionsSummaryFactory + private val reactionsSummaryFactory: ReactionsSummaryFactory, + private val pollResponseDataFactory: PollResponseDataFactory, ) { fun create(params: TimelineItemFactoryParams): MessageInformationData { @@ -100,7 +98,7 @@ class MessageInformationDataFactory @Inject constructor( memberName = event.senderInfo.disambiguatedDisplayName, messageLayout = messageLayout, reactionsSummary = reactionsSummaryFactory.create(event), - pollResponseAggregatedSummary = mapPollResponseSummary(event.annotations?.pollResponseSummary), + pollResponseAggregatedSummary = pollResponseDataFactory.create(event), hasBeenEdited = event.hasBeenEdited(), hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false, referencesInfoData = event.annotations?.referencesAggregatedSummary?.let { referencesAggregatedSummary -> @@ -121,23 +119,6 @@ class MessageInformationDataFactory @Inject constructor( ) } - fun mapPollResponseSummary(pollResponseSummary: PollResponseAggregatedSummary?): PollResponseData? { - return pollResponseSummary?.let { - PollResponseData( - myVote = it.aggregatedContent?.myVote, - isClosed = it.closedTime != null, - votes = it.aggregatedContent?.votesSummary?.mapValues { votesSummary -> - PollVoteSummaryData( - total = votesSummary.value.total, - percentage = votesSummary.value.percentage - ) - }, - winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, - totalVotes = it.aggregatedContent?.totalVotes ?: 0 - ) - } - } - private fun getSenderId(event: TimelineEvent) = if (event.isEncrypted()) { event.root.toValidDecryptedEvent()?.let { session.cryptoService().deviceWithIdentityKey(it.cryptoSenderKey, it.algorithm)?.userId diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt new file mode 100644 index 0000000000..c71d7d493f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.helper + +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.home.room.detail.timeline.item.PollResponseData +import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData +import org.matrix.android.sdk.api.session.events.model.getRelationContent +import org.matrix.android.sdk.api.session.events.model.isPollEnd +import org.matrix.android.sdk.api.session.room.getTimelineEvent +import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import timber.log.Timber +import javax.inject.Inject + +class PollResponseDataFactory @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, +) { + + fun create(event: TimelineEvent): PollResponseData? { + val pollResponseSummary = getPollResponseSummary(event) + return pollResponseSummary?.let { + PollResponseData( + myVote = it.aggregatedContent?.myVote, + isClosed = it.closedTime != null, + votes = it.aggregatedContent?.votesSummary?.mapValues { votesSummary -> + PollVoteSummaryData( + total = votesSummary.value.total, + percentage = votesSummary.value.percentage + ) + }, + winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, + totalVotes = it.aggregatedContent?.totalVotes ?: 0 + ) + } + } + + private fun getPollResponseSummary(event: TimelineEvent): PollResponseAggregatedSummary? { + if (event.root.isPollEnd()) { + val pollStartEventId = event.root.getRelationContent()?.eventId ?: return null.also { + Timber.e("### Cannot render poll end event because poll start event id is null") + } + return activeSessionHolder + .getSafeActiveSession() + ?.roomService() + ?.getRoom(event.roomId) + ?.getTimelineEvent(pollStartEventId) + ?.annotations + ?.pollResponseSummary + } + return event.annotations?.pollResponseSummary + } +} diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt index 1244a0a108..e6e75b2e20 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt @@ -43,7 +43,7 @@ class CheckIfCanReplyEventUseCaseTest { @Test fun `given reply is allowed for the event type when use case is executed then result is true`() { - val eventTypes = EventType.STATE_ROOM_BEACON_INFO.values + EventType.POLL_START.values + EventType.MESSAGE + val eventTypes = EventType.STATE_ROOM_BEACON_INFO.values + EventType.POLL_START.values + EventType.POLL_END.values + EventType.MESSAGE eventTypes.forEach { eventType -> val event = givenAnEvent(eventType) diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt index 69bcb81d5f..c38afe20ec 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt @@ -176,6 +176,17 @@ class ProcessBodyOfReplyToEventUseCaseTest { executeAndAssertResult() } + @Test + fun `given a replied event of type poll end message when process the formatted body then content is replaced by correct string`() { + // Given + givenTypeOfRepliedEvent(isPollMessage = true) + givenNewContentForId(R.string.message_reply_to_sender_ended_poll) + every { fakeRepliedEvent.getClearType() } returns EventType.POLL_END.unstable + every { fakeRepliedEvent.getPollQuestion() } returns null + + executeAndAssertResult() + } + @Test fun `given a replied event of type live location message when process the formatted body then content is replaced by correct string`() { // Given From cc334bcc13872f1621d26b04f779ca46bc1337dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 13:15:51 +0000 Subject: [PATCH 020/237] Bump junit from 1.1.3 to 1.1.5 Bumps junit from 1.1.3 to 1.1.5. --- updated-dependencies: - dependency-name: androidx.test.ext:junit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index e970457e7c..b139d65303 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -60,7 +60,7 @@ ext.libs = [ 'work' : "androidx.work:work-runtime-ktx:2.7.1", 'autoFill' : "androidx.autofill:autofill:1.1.0", 'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0", - 'junit' : "androidx.test.ext:junit:1.1.3", + 'junit' : "androidx.test.ext:junit:1.1.5", 'lifecycleCommon' : "androidx.lifecycle:lifecycle-common:$lifecycle", 'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle", 'lifecycleProcess' : "androidx.lifecycle:lifecycle-process:$lifecycle", From 7ca2c9c0097ca5d0341a21b44c791e4c3c3c7179 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 13:21:58 +0000 Subject: [PATCH 021/237] Bump stem-plugin from 2.2.3 to 2.3.0 Bumps stem-plugin from 2.2.3 to 2.3.0. --- updated-dependencies: - dependency-name: com.likethesalad.android:stem-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1ebe910e80..8910d5a2f9 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ buildscript { classpath 'com.google.gms:google-services:4.3.14' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6' - classpath "com.likethesalad.android:stem-plugin:2.2.3" + classpath "com.likethesalad.android:stem-plugin:2.3.0" classpath 'org.owasp:dependency-check-gradle:7.4.4' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" From f8852856c6d60c6b9245d47b2393df5ed08dabc5 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 10 Jan 2023 10:34:45 +0100 Subject: [PATCH 022/237] Convert state enum to sealed interface --- .../MessageVoiceBroadcastListeningItem.kt | 20 +++--- .../listening/VoiceBroadcastPlayer.kt | 12 ++-- .../listening/VoiceBroadcastPlayerImpl.kt | 62 +++++++++---------- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index b788d79214..d21e6771d9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -63,10 +63,10 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem playPauseButton.setOnClickListener { if (player.currentVoiceBroadcast == voiceBroadcast) { when (player.playingState) { - VoiceBroadcastPlayer.State.PLAYING, - VoiceBroadcastPlayer.State.BUFFERING -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) - VoiceBroadcastPlayer.State.PAUSED, - VoiceBroadcastPlayer.State.IDLE -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) + VoiceBroadcastPlayer.State.Playing, + VoiceBroadcastPlayer.State.Buffering -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) + VoiceBroadcastPlayer.State.Paused, + VoiceBroadcastPlayer.State.Idle -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) } } else { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) @@ -100,17 +100,17 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem private fun renderPlayingState(holder: Holder, state: VoiceBroadcastPlayer.State) { with(holder) { - bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING - voiceBroadcastMetadata.isVisible = state != VoiceBroadcastPlayer.State.BUFFERING + bufferingView.isVisible = state == VoiceBroadcastPlayer.State.Buffering + voiceBroadcastMetadata.isVisible = state != VoiceBroadcastPlayer.State.Buffering when (state) { - VoiceBroadcastPlayer.State.PLAYING, - VoiceBroadcastPlayer.State.BUFFERING -> { + VoiceBroadcastPlayer.State.Playing, + VoiceBroadcastPlayer.State.Buffering -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) } - VoiceBroadcastPlayer.State.IDLE, - VoiceBroadcastPlayer.State.PAUSED -> { + VoiceBroadcastPlayer.State.Idle, + VoiceBroadcastPlayer.State.Paused -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_play) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt index 0de88e9992..5e5c320d43 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt @@ -26,7 +26,7 @@ interface VoiceBroadcastPlayer { val currentVoiceBroadcast: VoiceBroadcast? /** - * The current playing [State], [State.IDLE] by default. + * The current playing [State], [State.Idle] by default. */ val playingState: State @@ -68,11 +68,11 @@ interface VoiceBroadcastPlayer { /** * Player states. */ - enum class State { - PLAYING, - PAUSED, - BUFFERING, - IDLE + sealed interface State { + object Playing : State + object Paused : State + object Buffering : State + object Idle : State } /** diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 9cb894bb58..f00d657682 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -79,7 +79,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } - override var playingState = State.IDLE + override var playingState: State = State.Idle @MainThread set(value) { if (field != value) { @@ -96,7 +96,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val hasChanged = currentVoiceBroadcast != voiceBroadcast when { hasChanged -> startPlayback(voiceBroadcast) - playingState == State.PAUSED -> resumePlayback() + playingState == State.Paused -> resumePlayback() else -> Unit } } @@ -107,7 +107,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( override fun stop() { // Update state - playingState = State.IDLE + playingState = State.Idle // Stop and release media players stopPlayer() @@ -129,7 +129,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( listeners[voiceBroadcast.voiceBroadcastId]?.add(listener) ?: run { listeners[voiceBroadcast.voiceBroadcastId] = CopyOnWriteArrayList().apply { add(listener) } } - listener.onPlayingStateChanged(if (voiceBroadcast == currentVoiceBroadcast) playingState else State.IDLE) + listener.onPlayingStateChanged(if (voiceBroadcast == currentVoiceBroadcast) playingState else State.Idle) listener.onLiveModeChanged(voiceBroadcast == currentVoiceBroadcast) } @@ -139,11 +139,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun startPlayback(voiceBroadcast: VoiceBroadcast) { // Stop listening previous voice broadcast if any - if (playingState != State.IDLE) stop() + if (playingState != State.Idle) stop() currentVoiceBroadcast = voiceBroadcast - playingState = State.BUFFERING + playingState = State.Buffering observeVoiceBroadcastStateEvent(voiceBroadcast) } @@ -175,13 +175,13 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun onPlaylistUpdated() { when (playingState) { - State.PLAYING, - State.PAUSED -> { + State.Playing, + State.Paused -> { if (nextMediaPlayer == null && !isPreparingNextPlayer) { prepareNextMediaPlayer() } } - State.BUFFERING -> { + State.Buffering -> { val nextItem = if (isLiveListening && playlist.currentSequence == null) { // live listening, jump to the last item if playback has not started playlist.lastOrNull() @@ -193,7 +193,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( startPlayback(nextItem.startTime) } } - State.IDLE -> Unit // Should not happen + State.Idle -> Unit // Should not happen } } @@ -213,7 +213,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( if (sequencePosition > 0) { mp.seekTo(sequencePosition) } - playingState = State.PLAYING + playingState = State.Playing prepareNextMediaPlayer() } } catch (failure: Throwable) { @@ -224,7 +224,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } private fun pausePlayback() { - playingState = State.PAUSED // This will trigger a playing state update and save the current position + playingState = State.Paused // This will trigger a playing state update and save the current position if (currentMediaPlayer != null) { currentMediaPlayer?.pause() } else { @@ -234,7 +234,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun resumePlayback() { if (currentMediaPlayer != null) { - playingState = State.PLAYING + playingState = State.Playing currentMediaPlayer?.start() } else { val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } ?: 0 @@ -247,11 +247,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor( voiceBroadcast != currentVoiceBroadcast -> { playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) } - playingState == State.PLAYING || playingState == State.BUFFERING -> { + playingState == State.Playing || playingState == State.Buffering -> { updateLiveListeningMode(positionMillis) startPlayback(positionMillis) } - playingState == State.IDLE || playingState == State.PAUSED -> { + playingState == State.Idle || playingState == State.Paused -> { stopPlayer() playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) } @@ -267,15 +267,15 @@ class VoiceBroadcastPlayerImpl @Inject constructor( isPreparingNextPlayer = false nextMediaPlayer = mp when (playingState) { - State.PLAYING, - State.PAUSED -> { + State.Playing, + State.Paused -> { currentMediaPlayer?.setNextMediaPlayer(mp) } - State.BUFFERING -> { + State.Buffering -> { mp.start() onNextMediaPlayerStarted(mp) } - State.IDLE -> stopPlayer() + State.Idle -> stopPlayer() } } } @@ -327,10 +327,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor( currentVoiceBroadcast?.voiceBroadcastId?.let { voiceBroadcastId -> // Start or stop playback ticker when (playingState) { - State.PLAYING -> playbackTicker.startPlaybackTicker(voiceBroadcastId) - State.PAUSED, - State.BUFFERING, - State.IDLE -> playbackTicker.stopPlaybackTicker(voiceBroadcastId) + State.Playing -> playbackTicker.startPlaybackTicker(voiceBroadcastId) + State.Paused, + State.Buffering, + State.Idle -> playbackTicker.stopPlaybackTicker(voiceBroadcastId) } // Notify state change to all the listeners attached to the current voice broadcast id listeners[voiceBroadcastId]?.forEach { listener -> listener.onPlayingStateChanged(playingState) } @@ -348,7 +348,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( // the current voice broadcast is not live (ended) mostRecentVoiceBroadcastEvent?.isLive != true -> false // the player is stopped or paused - playingState == State.IDLE || playingState == State.PAUSED -> false + playingState == State.Idle || playingState == State.Paused -> false seekPosition != null -> { val seekDirection = seekPosition.compareTo(getCurrentPlaybackPosition() ?: 0) val newSequence = playlist.findByPosition(seekPosition)?.sequence @@ -374,13 +374,13 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun onLiveListeningChanged(isLiveListening: Boolean) { // Live has ended and last chunk has been reached, we can stop the playback - if (!isLiveListening && playingState == State.BUFFERING && playlist.currentSequence == mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence) { + if (!isLiveListening && playingState == State.Buffering && playlist.currentSequence == mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence) { stop() } } private fun onNextMediaPlayerStarted(mp: MediaPlayer) { - playingState = State.PLAYING + playingState = State.Playing playlist.currentSequence = playlist.currentSequence?.inc() currentMediaPlayer = mp nextMediaPlayer = null @@ -427,7 +427,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( stop() } else { // Enter in buffering mode and release current media player - playingState = State.BUFFERING + playingState = State.Buffering currentMediaPlayer?.release() currentMediaPlayer = null } @@ -462,18 +462,18 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val playbackTime = getCurrentPlaybackPosition() val percentage = getCurrentPlaybackPercentage() when (playingState) { - State.PLAYING -> { + State.Playing -> { if (playbackTime != null && percentage != null) { playbackTracker.updatePlayingAtPlaybackTime(id, playbackTime, percentage) } } - State.PAUSED, - State.BUFFERING -> { + State.Paused, + State.Buffering -> { if (playbackTime != null && percentage != null) { playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) } } - State.IDLE -> { + State.Idle -> { if (playbackTime == null || percentage == null || (playlist.duration - playbackTime) < 50) { playbackTracker.stopPlayback(id) } else { From 2d24eb1273d0aa72e9ea2e5f758484763628af34 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 10 Jan 2023 14:00:46 +0100 Subject: [PATCH 023/237] Handle playback error --- changelog.d/7829.bugfix | 1 + .../src/main/res/values/strings.xml | 1 + .../vector/app/core/error/ErrorFormatter.kt | 2 + .../voice/VoiceMessageRecorderView.kt | 1 + .../factory/VoiceBroadcastItemFactory.kt | 3 + .../helper/AudioMessagePlaybackTracker.kt | 30 ++++++--- .../item/AbsMessageVoiceBroadcastItem.kt | 3 + .../detail/timeline/item/MessageAudioItem.kt | 1 + .../MessageVoiceBroadcastListeningItem.kt | 35 ++++++++--- .../detail/timeline/item/MessageVoiceItem.kt | 1 + .../voicebroadcast/VoiceBroadcastFailure.kt | 11 ++++ .../listening/VoiceBroadcastPlayer.kt | 2 + .../listening/VoiceBroadcastPlayerImpl.kt | 63 +++++++++++-------- .../res/drawable/ic_voice_broadcast_error.xml | 10 +++ ...e_event_voice_broadcast_listening_stub.xml | 23 +++++++ 15 files changed, 144 insertions(+), 43 deletions(-) create mode 100644 changelog.d/7829.bugfix create mode 100644 vector/src/main/res/drawable/ic_voice_broadcast_error.xml diff --git a/changelog.d/7829.bugfix b/changelog.d/7829.bugfix new file mode 100644 index 0000000000..705f7310f0 --- /dev/null +++ b/changelog.d/7829.bugfix @@ -0,0 +1 @@ +Handle exceptions when listening a voice broadcast diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index d9f94ba27b..ce84ef61ad 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3121,6 +3121,7 @@ You don’t have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions. Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one. You are already recording a voice broadcast. Please end your current voice broadcast to start a new one. + Unable to play this voice broadcast. %1$s left Stop live broadcasting? diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index 380c80775b..78aaa058e9 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -157,6 +157,8 @@ class DefaultErrorFormatter @Inject constructor( RecordingError.BlockedBySomeoneElse -> stringProvider.getString(R.string.error_voice_broadcast_blocked_by_someone_else_message) RecordingError.NoPermission -> stringProvider.getString(R.string.error_voice_broadcast_permission_denied_message) RecordingError.UserAlreadyBroadcasting -> stringProvider.getString(R.string.error_voice_broadcast_already_in_progress_message) + is VoiceBroadcastFailure.ListeningError.UnableToPlay, + is VoiceBroadcastFailure.ListeningError.DownloadError -> stringProvider.getString(R.string.error_voice_broadcast_unable_to_play) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt index a7b926f29a..b5c4b4a537 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt @@ -229,6 +229,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( voiceMessageViews.renderPlaying(state) } is AudioMessagePlaybackTracker.Listener.State.Paused, + is AudioMessagePlaybackTracker.Listener.State.Error, is AudioMessagePlaybackTracker.Listener.State.Idle -> { voiceMessageViews.renderIdle() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index cc3a015120..ad8e163633 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -15,6 +15,7 @@ */ package im.vector.app.features.home.room.detail.timeline.factory +import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider import im.vector.app.features.displayname.getBestName @@ -45,6 +46,7 @@ class VoiceBroadcastItemFactory @Inject constructor( private val avatarSizeProvider: AvatarSizeProvider, private val colorProvider: ColorProvider, private val drawableProvider: DrawableProvider, + private val errorFormatter: ErrorFormatter, private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, private val voiceBroadcastPlayer: VoiceBroadcastPlayer, private val playbackTracker: AudioMessagePlaybackTracker, @@ -82,6 +84,7 @@ class VoiceBroadcastItemFactory @Inject constructor( roomItem = session.getRoom(params.event.roomId)?.roomSummary()?.toMatrixItem(), colorProvider = colorProvider, drawableProvider = drawableProvider, + errorFormatter = errorFormatter, ) return if (isRecording) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt index c34cbbc74a..c598a99af7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt @@ -50,8 +50,11 @@ class AudioMessagePlaybackTracker @Inject constructor() { listeners.remove(id) } - fun pauseAllPlaybacks() { - listeners.keys.forEach(::pausePlayback) + fun unregisterListeners() { + listeners.forEach { + it.value.onUpdate(Listener.State.Idle) + } + listeners.clear() } /** @@ -84,6 +87,10 @@ class AudioMessagePlaybackTracker @Inject constructor() { } } + fun pauseAllPlaybacks() { + listeners.keys.forEach(::pausePlayback) + } + fun pausePlayback(id: String) { val state = getPlaybackState(id) if (state is Listener.State.Playing) { @@ -94,7 +101,14 @@ class AudioMessagePlaybackTracker @Inject constructor() { } fun stopPlayback(id: String) { - setState(id, Listener.State.Idle) + val state = getPlaybackState(id) + if (state !is Listener.State.Error) { + setState(id, Listener.State.Idle) + } + } + + fun onError(id: String, error: Throwable) { + setState(id, Listener.State.Error(error)) } fun updatePlayingAtPlaybackTime(id: String, time: Int, percentage: Float) { @@ -116,6 +130,7 @@ class AudioMessagePlaybackTracker @Inject constructor() { is Listener.State.Playing -> state.playbackTime is Listener.State.Paused -> state.playbackTime is Listener.State.Recording, + is Listener.State.Error, Listener.State.Idle, null -> null } @@ -126,18 +141,12 @@ class AudioMessagePlaybackTracker @Inject constructor() { is Listener.State.Playing -> state.percentage is Listener.State.Paused -> state.percentage is Listener.State.Recording, + is Listener.State.Error, Listener.State.Idle, null -> null } } - fun unregisterListeners() { - listeners.forEach { - it.value.onUpdate(Listener.State.Idle) - } - listeners.clear() - } - companion object { const val RECORDING_ID = "RECORDING_ID" } @@ -148,6 +157,7 @@ class AudioMessagePlaybackTracker @Inject constructor() { sealed class State { object Idle : State() + data class Error(val failure: Throwable) : State() data class Playing(val playbackTime: Int, val percentage: Float) : State() data class Paused(val playbackTime: Int, val percentage: Float) : State() data class Recording(val amplitudeList: List) : State() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt index c6b90cdabe..7cde978e42 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt @@ -22,6 +22,7 @@ import androidx.annotation.IdRes import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import im.vector.app.R +import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.tintBackground import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider @@ -48,6 +49,7 @@ abstract class AbsMessageVoiceBroadcastItem() { private fun renderStateBasedOnAudioPlayback(holder: Holder) { audioMessagePlaybackTracker.track(attributes.informationData.eventId) { state -> when (state) { + is AudioMessagePlaybackTracker.Listener.State.Error, is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder) is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state) is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index d21e6771d9..0aa2aaad3b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -20,11 +20,13 @@ import android.text.format.DateUtils import android.widget.ImageButton import android.widget.SeekBar import android.widget.TextView +import androidx.constraintlayout.widget.Group import androidx.core.view.isInvisible import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.onClick +import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer @@ -54,6 +56,16 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } } player.addListener(voiceBroadcast, playerListener) + + playbackTracker.track(voiceBroadcast.voiceBroadcastId) { playbackState -> + renderBackwardForwardButtons(holder, playbackState) + renderPlaybackError(holder, playbackState) + renderLiveIndicator(holder) + if (!isUserSeeking) { + holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) ?: 0 + } + } + bindSeekBar(holder) bindButtons(holder) } @@ -66,6 +78,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem VoiceBroadcastPlayer.State.Playing, VoiceBroadcastPlayer.State.Buffering -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) VoiceBroadcastPlayer.State.Paused, + is VoiceBroadcastPlayer.State.Error, VoiceBroadcastPlayer.State.Idle -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast)) } } else { @@ -109,6 +122,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) } + is VoiceBroadcastPlayer.State.Error, VoiceBroadcastPlayer.State.Idle, VoiceBroadcastPlayer.State.Paused -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_play) @@ -120,6 +134,18 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } } + private fun renderPlaybackError(holder: Holder, playbackState: State) { + with(holder) { + if (playbackState is State.Error) { + controlsGroup.isVisible = false + errorView.setTextOrHide(errorFormatter.toHumanReadable(playbackState.failure)) + } else { + errorView.isVisible = false + controlsGroup.isVisible = true + } + } + } + private fun bindSeekBar(holder: Holder) { with(holder) { remainingTimeView.text = formatRemainingTime(duration) @@ -141,13 +167,6 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem } }) } - playbackTracker.track(voiceBroadcast.voiceBroadcastId) { playbackState -> - renderBackwardForwardButtons(holder, playbackState) - renderLiveIndicator(holder) - if (!isUserSeeking) { - holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) ?: 0 - } - } } private fun renderBackwardForwardButtons(holder: Holder, playbackState: State) { @@ -187,6 +206,8 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem val broadcasterNameMetadata by bind(R.id.broadcasterNameMetadata) val voiceBroadcastMetadata by bind(R.id.voiceBroadcastMetadata) val listenersCountMetadata by bind(R.id.listenersCountMetadata) + val errorView by bind(R.id.errorView) + val controlsGroup by bind(R.id.controlsGroup) } companion object { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index d3f320db7d..a8e215b4a9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -124,6 +124,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { audioMessagePlaybackTracker.track(attributes.informationData.eventId) { state -> when (state) { + is AudioMessagePlaybackTracker.Listener.State.Error, is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder, waveformColorIdle, waveformColorPlayed) is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state, waveformColorIdle, waveformColorPlayed) is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state, waveformColorIdle, waveformColorPlayed) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt index 76b50c78ab..75863dc042 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt @@ -16,10 +16,21 @@ package im.vector.app.features.voicebroadcast +import android.media.MediaPlayer + sealed class VoiceBroadcastFailure : Throwable() { sealed class RecordingError : VoiceBroadcastFailure() { object NoPermission : RecordingError() object BlockedBySomeoneElse : RecordingError() object UserAlreadyBroadcasting : RecordingError() } + + sealed class ListeningError : VoiceBroadcastFailure() { + /** + * @property what the type of error that has occurred, see [MediaPlayer.OnErrorListener.onError]. + * @property extra an extra code, specific to the error, see [MediaPlayer.OnErrorListener.onError]. + */ + data class UnableToPlay(val what: Int, val extra: Int) : ListeningError() + data class DownloadError(override val cause: Throwable?) : ListeningError() + } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt index 5e5c320d43..ad0ecf69b4 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast.listening +import im.vector.app.features.voicebroadcast.VoiceBroadcastFailure import im.vector.app.features.voicebroadcast.model.VoiceBroadcast interface VoiceBroadcastPlayer { @@ -72,6 +73,7 @@ interface VoiceBroadcastPlayer { object Playing : State object Paused : State object Buffering : State + data class Error(val failure: VoiceBroadcastFailure.ListeningError) : State object Idle : State } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index f00d657682..538b2f8da4 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -24,7 +24,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.onFirst import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.session.coroutineScope -import im.vector.app.features.voice.VoiceFailure +import im.vector.app.features.voicebroadcast.VoiceBroadcastFailure import im.vector.app.features.voicebroadcast.isLive import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Listener import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.State @@ -193,6 +193,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( startPlayback(nextItem.startTime) } } + is State.Error -> Unit State.Idle -> Unit // Should not happen } } @@ -205,20 +206,15 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val sequence = playlistItem.sequence ?: run { Timber.w("## Voice Broadcast | Playlist item has no sequence"); return } val sequencePosition = position - playlistItem.startTime sessionScope.launch { - try { - prepareMediaPlayer(content) { mp -> - currentMediaPlayer = mp - playlist.currentSequence = sequence - mp.start() - if (sequencePosition > 0) { - mp.seekTo(sequencePosition) - } - playingState = State.Playing - prepareNextMediaPlayer() + prepareMediaPlayer(content) { mp -> + currentMediaPlayer = mp + playlist.currentSequence = sequence + mp.start() + if (sequencePosition > 0) { + mp.seekTo(sequencePosition) } - } catch (failure: Throwable) { - Timber.e(failure, "## Voice Broadcast | Unable to start playback: $failure") - throw VoiceFailure.UnableToPlay(failure) + playingState = State.Playing + prepareNextMediaPlayer() } } } @@ -275,6 +271,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( mp.start() onNextMediaPlayerStarted(mp) } + is State.Error, State.Idle -> stopPlayer() } } @@ -288,11 +285,12 @@ class VoiceBroadcastPlayerImpl @Inject constructor( session.fileService().downloadFile(messageAudioContent) } catch (failure: Throwable) { Timber.e(failure, "Voice Broadcast | Download has failed: $failure") - throw VoiceFailure.UnableToPlay(failure) + throw VoiceBroadcastFailure.ListeningError.DownloadError(failure) } return audioFile.inputStream().use { fis -> MediaPlayer().apply { + setOnErrorListener(mediaPlayerListener) setAudioAttributes( AudioAttributes.Builder() // Do not use CONTENT_TYPE_SPEECH / USAGE_VOICE_COMMUNICATION because we want to play loud here @@ -302,10 +300,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor( ) setDataSource(fis.fd) setOnInfoListener(mediaPlayerListener) - setOnErrorListener(mediaPlayerListener) setOnPreparedListener(onPreparedListener) setOnCompletionListener(mediaPlayerListener) - prepare() + prepareAsync() } } } @@ -330,8 +327,15 @@ class VoiceBroadcastPlayerImpl @Inject constructor( State.Playing -> playbackTicker.startPlaybackTicker(voiceBroadcastId) State.Paused, State.Buffering, + is State.Error, State.Idle -> playbackTicker.stopPlaybackTicker(voiceBroadcastId) } + + // Notify playback tracker about error + if (playingState is State.Error) { + playbackTracker.onError(voiceBroadcastId, playingState.failure) + } + // Notify state change to all the listeners attached to the current voice broadcast id listeners[voiceBroadcastId]?.forEach { listener -> listener.onPlayingStateChanged(playingState) } } @@ -374,7 +378,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun onLiveListeningChanged(isLiveListening: Boolean) { // Live has ended and last chunk has been reached, we can stop the playback - if (!isLiveListening && playingState == State.Buffering && playlist.currentSequence == mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence) { + val hasReachedLastChunk = playlist.currentSequence == mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence + if (!isLiveListening && playingState == State.Buffering && hasReachedLastChunk) { stop() } } @@ -389,16 +394,16 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun getCurrentPlaybackPosition(): Int? { val voiceBroadcastId = currentVoiceBroadcast?.voiceBroadcastId ?: return null - val computedPosition = currentMediaPlayer?.currentPosition?.let { playlist.currentItem?.startTime?.plus(it) } + val computedPosition = tryOrNull { currentMediaPlayer?.currentPosition }?.let { playlist.currentItem?.startTime?.plus(it) } val savedPosition = playbackTracker.getPlaybackTime(voiceBroadcastId) return computedPosition ?: savedPosition } private fun getCurrentPlaybackPercentage(): Float? { val playlistPosition = playlist.currentItem?.startTime - val computedPosition = currentMediaPlayer?.currentPosition?.let { playlistPosition?.plus(it) } ?: playlistPosition - val duration = playlist.duration.takeIf { it > 0 } - val computedPercentage = if (computedPosition != null && duration != null) computedPosition.toFloat() / duration else null + val computedPosition = tryOrNull { currentMediaPlayer?.currentPosition }?.let { playlistPosition?.plus(it) } ?: playlistPosition + val duration = playlist.duration + val computedPercentage = if (computedPosition != null && duration > 0) computedPosition.toFloat() / duration else null val savedPercentage = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPercentage(it) } return computedPercentage ?: savedPercentage } @@ -416,6 +421,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } override fun onCompletion(mp: MediaPlayer) { + // Release media player as soon as it completed + mp.release() + currentMediaPlayer = null + // Next media player is already attached to this player and will start playing automatically if (nextMediaPlayer != null) return @@ -426,15 +435,16 @@ class VoiceBroadcastPlayerImpl @Inject constructor( // We'll not receive new chunks anymore so we can stop the live listening stop() } else { - // Enter in buffering mode and release current media player playingState = State.Buffering - currentMediaPlayer?.release() - currentMediaPlayer = null + prepareNextMediaPlayer() } } override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean { - stop() + Timber.d("## Voice Broadcast | onError: what=$what, extra=$extra") + if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) { + playingState = State.Error(VoiceBroadcastFailure.ListeningError.UnableToPlay(what, extra)) + } return true } } @@ -480,6 +490,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) } } + is State.Error -> Unit } } } diff --git a/vector/src/main/res/drawable/ic_voice_broadcast_error.xml b/vector/src/main/res/drawable/ic_voice_broadcast_error.xml new file mode 100644 index 0000000000..6cbd4592cb --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_broadcast_error.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml index 760293ee64..deec85e2ed 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml @@ -176,4 +176,27 @@ tools:ignore="NegativeMargin" tools:text="-0:12" /> + + + + From 3663f225905aabdfaa73550224cf210d6616fa96 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 10 Jan 2023 15:16:25 +0100 Subject: [PATCH 024/237] Handle download error during playback --- .../listening/VoiceBroadcastPlayerImpl.kt | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 538b2f8da4..ab4b6c2269 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -206,15 +206,19 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val sequence = playlistItem.sequence ?: run { Timber.w("## Voice Broadcast | Playlist item has no sequence"); return } val sequencePosition = position - playlistItem.startTime sessionScope.launch { - prepareMediaPlayer(content) { mp -> - currentMediaPlayer = mp - playlist.currentSequence = sequence - mp.start() - if (sequencePosition > 0) { - mp.seekTo(sequencePosition) + try { + prepareMediaPlayer(content) { mp -> + currentMediaPlayer = mp + playlist.currentSequence = sequence + mp.start() + if (sequencePosition > 0) { + mp.seekTo(sequencePosition) + } + playingState = State.Playing + prepareNextMediaPlayer() } - playingState = State.Playing - prepareNextMediaPlayer() + } catch (failure: VoiceBroadcastFailure.ListeningError.DownloadError) { + playingState = State.Error(failure) } } } @@ -259,20 +263,27 @@ class VoiceBroadcastPlayerImpl @Inject constructor( if (nextItem != null) { isPreparingNextPlayer = true sessionScope.launch { - prepareMediaPlayer(nextItem.audioEvent.content) { mp -> + try { + prepareMediaPlayer(nextItem.audioEvent.content) { mp -> + isPreparingNextPlayer = false + nextMediaPlayer = mp + when (playingState) { + State.Playing, + State.Paused -> { + currentMediaPlayer?.setNextMediaPlayer(mp) + } + State.Buffering -> { + mp.start() + onNextMediaPlayerStarted(mp) + } + is State.Error, + State.Idle -> stopPlayer() + } + } + } catch (failure: VoiceBroadcastFailure.ListeningError.DownloadError) { isPreparingNextPlayer = false - nextMediaPlayer = mp - when (playingState) { - State.Playing, - State.Paused -> { - currentMediaPlayer?.setNextMediaPlayer(mp) - } - State.Buffering -> { - mp.start() - onNextMediaPlayerStarted(mp) - } - is State.Error, - State.Idle -> stopPlayer() + if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) { + playingState = State.Error(failure) } } } From 9d3b5c5bbb7c0d2a457a11fa6a20214b3e788293 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 10 Jan 2023 16:00:19 +0100 Subject: [PATCH 025/237] Fix no display name for some voice broadcast recorder name --- .../room/detail/timeline/factory/VoiceBroadcastItemFactory.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index ad8e163633..3439fb1f57 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -18,7 +18,6 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider -import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup @@ -37,7 +36,6 @@ import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom -import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -77,7 +75,7 @@ class VoiceBroadcastItemFactory @Inject constructor( voiceBroadcast = voiceBroadcast, voiceBroadcastState = voiceBroadcastContent.voiceBroadcastState, duration = voiceBroadcastEventsGroup.getDuration(), - recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName().orEmpty(), + recorderName = params.event.senderInfo.disambiguatedDisplayName, recorder = voiceBroadcastRecorder, player = voiceBroadcastPlayer, playbackTracker = playbackTracker, From b4e6656c42f36924bf4e504f3a1c460d86a23095 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Jan 2023 16:24:24 +0100 Subject: [PATCH 026/237] version++ --- matrix-sdk-android/build.gradle | 2 +- vector-app/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index d132158615..ef6cb8cddb 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -62,7 +62,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.5.20\"" + buildConfigField "String", "SDK_VERSION", "\"1.5.22\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/vector-app/build.gradle b/vector-app/build.gradle index e157f0704a..11119a75cc 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -37,7 +37,7 @@ ext.versionMinor = 5 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 20 +ext.versionPatch = 22 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From c85161a4bba9768b3c0eb4f4da666ded576bdd16 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Jan 2023 17:07:47 +0100 Subject: [PATCH 027/237] Fix release script --- tools/release/releaseScript.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/release/releaseScript.sh b/tools/release/releaseScript.sh index 553c02101c..f9f5303546 100755 --- a/tools/release/releaseScript.sh +++ b/tools/release/releaseScript.sh @@ -359,9 +359,9 @@ adb -d install ${apkPath} read -p "Please run the APK on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done." printf "\n================================================================================\n" -githubCreateReleaseLink="https://github.com/vector-im/element-android/releases/new?tag=v${version}&title=Element%%20Android%%20v${version}&body=${changelogUrlEncoded}" +githubCreateReleaseLink="https://github.com/vector-im/element-android/releases/new?tag=v${version}&title=Element%20Android%20v${version}&body=${changelogUrlEncoded}" printf "Creating the release on gitHub.\n" -printf "Open this link: ${githubCreateReleaseLink}\n" +printf -- "Open this link: %s\n" ${githubCreateReleaseLink} printf "Then\n" printf " - click on the 'Generate releases notes' button\n" printf " - Add the 4 signed APKs to the GitHub release. They are located at ${targetPath}\n" @@ -369,7 +369,7 @@ read -p ". Press enter when it's done. " printf "\n================================================================================\n" printf "Message for the Android internal room:\n\n" -message="@room Element Android ${version} is ready to be tested. You can get if from https://github.com/vector-im/element-android/releases/tag/v${version}. Please report any feedback here. Thanks!" +message="@room Element Android ${version} is ready to be tested. You can get it from https://github.com/vector-im/element-android/releases/tag/v${version}. Please report any feedback here. Thanks!" printf "${message}\n\n" if [[ -z "${elementBotToken}" ]]; then From ec27c67940b2ef3d6b28e512b6cd4692a5d089c1 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 10 Jan 2023 19:14:30 +0300 Subject: [PATCH 028/237] Fix color of winning vote count. --- .../home/room/detail/timeline/item/PollOptionView.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt index 38e6c5e6d7..e8d636e20b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt @@ -25,6 +25,7 @@ import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.extensions.setAttributeTintedImageResource import im.vector.app.databinding.ItemPollOptionBinding +import im.vector.app.features.themes.ThemeUtils class PollOptionView @JvmOverloads constructor( context: Context, @@ -62,6 +63,10 @@ class PollOptionView @JvmOverloads constructor( views.optionCheckImageView.isVisible = false val drawableStart = if (state.isWinner) R.drawable.ic_poll_winner else 0 views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, 0, 0, 0) + views.optionVoteCountTextView.setTextColor( + if (state.isWinner) ThemeUtils.getColor(context, R.attr.colorPrimary) + else ThemeUtils.getColor(context, R.attr.vctr_content_secondary) + ) showVotes(state.voteCount, state.votePercentage) renderVoteSelection(state.isWinner) } From 8495536fd327c672757e4b0ac967793725ebff74 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 10 Jan 2023 19:22:56 +0300 Subject: [PATCH 029/237] Code review fix. --- .../helper/PollResponseDataFactory.kt | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt index c71d7d493f..533397b4d8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt @@ -50,18 +50,22 @@ class PollResponseDataFactory @Inject constructor( } private fun getPollResponseSummary(event: TimelineEvent): PollResponseAggregatedSummary? { - if (event.root.isPollEnd()) { - val pollStartEventId = event.root.getRelationContent()?.eventId ?: return null.also { + return if (event.root.isPollEnd()) { + val pollStartEventId = event.root.getRelationContent()?.eventId + if (pollStartEventId.isNullOrEmpty()) { Timber.e("### Cannot render poll end event because poll start event id is null") + null + } else { + activeSessionHolder + .getSafeActiveSession() + ?.roomService() + ?.getRoom(event.roomId) + ?.getTimelineEvent(pollStartEventId) + ?.annotations + ?.pollResponseSummary } - return activeSessionHolder - .getSafeActiveSession() - ?.roomService() - ?.getRoom(event.roomId) - ?.getTimelineEvent(pollStartEventId) - ?.annotations - ?.pollResponseSummary + } else { + event.annotations?.pollResponseSummary } - return event.annotations?.pollResponseSummary } } From b7073cb104ae500d0e772e23cb9ac880e04063a9 Mon Sep 17 00:00:00 2001 From: jonnyandrew Date: Tue, 10 Jan 2023 17:04:39 +0000 Subject: [PATCH 030/237] [Rich text editor] Update list item bullet appearance (#7930) --- changelog.d/7930.feature | 1 + dependencies.gradle | 2 +- library/ui-styles/src/main/res/values/styles_edit_text.xml | 1 + vector/src/main/res/layout/composer_rich_text_layout.xml | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelog.d/7930.feature diff --git a/changelog.d/7930.feature b/changelog.d/7930.feature new file mode 100644 index 0000000000..7eb779e6ec --- /dev/null +++ b/changelog.d/7930.feature @@ -0,0 +1 @@ +"[Rich text editor] Update list item bullet appearance" \ No newline at end of file diff --git a/dependencies.gradle b/dependencies.gradle index ee056c1e25..dd26240b6e 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -101,7 +101,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:0.14.0" + 'wysiwyg' : "io.element.android:wysiwyg:0.15.0" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", diff --git a/library/ui-styles/src/main/res/values/styles_edit_text.xml b/library/ui-styles/src/main/res/values/styles_edit_text.xml index 94f4d86160..6b282a7674 100644 --- a/library/ui-styles/src/main/res/values/styles_edit_text.xml +++ b/library/ui-styles/src/main/res/values/styles_edit_text.xml @@ -22,6 +22,7 @@ false 15sp ?vctr_message_text_color + 20sp diff --git a/vector/src/main/res/layout/composer_rich_text_layout.xml b/vector/src/main/res/layout/composer_rich_text_layout.xml index 7cc2d48cda..8992b632c0 100644 --- a/vector/src/main/res/layout/composer_rich_text_layout.xml +++ b/vector/src/main/res/layout/composer_rich_text_layout.xml @@ -124,6 +124,8 @@ app:layout_constraintEnd_toStartOf="@id/composerFullScreenButton" app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder" app:layout_constraintTop_toBottomOf="@id/composerModeBarrier" + app:bulletRadius="4sp" + app:bulletGap="8sp" tools:text="@tools:sample/lorem/random" /> Date: Wed, 11 Jan 2023 09:14:53 +0000 Subject: [PATCH 031/237] Bump sentry-android from 6.9.2 to 6.11.0 Bumps [sentry-android](https://github.com/getsentry/sentry-java) from 6.9.2 to 6.11.0. - [Release notes](https://github.com/getsentry/sentry-java/releases) - [Changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-java/compare/6.9.2...6.11.0) --- updated-dependencies: - dependency-name: io.sentry:sentry-android dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 47d716b4fe..e0d1454afd 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -27,7 +27,7 @@ def jjwt = "0.11.5" // Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert // the whole commit which set version 0.16.0-SNAPSHOT def vanniktechEmoji = "0.16.0-SNAPSHOT" -def sentry = "6.9.2" +def sentry = "6.11.0" def fragment = "1.5.5" // Testing def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 From 273d2e6a63e011d14670b344ccc047a85c2d99a8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Jan 2023 11:38:23 +0100 Subject: [PATCH 032/237] Remove package declaration from AndroidManifest.xml --- matrix-sdk-android/src/androidTest/AndroidManifest.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/AndroidManifest.xml b/matrix-sdk-android/src/androidTest/AndroidManifest.xml index 40360fcd19..859ebbd238 100644 --- a/matrix-sdk-android/src/androidTest/AndroidManifest.xml +++ b/matrix-sdk-android/src/androidTest/AndroidManifest.xml @@ -1,6 +1,5 @@ + xmlns:tools="http://schemas.android.com/tools"> From 48b54b402b55e9783b696f647815e47a99685d41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Nov 2022 15:27:20 +0000 Subject: [PATCH 033/237] Bump androidxTest from 1.4.0 to 1.5.0 Bumps `androidxTest` from 1.4.0 to 1.5.0. Updates `core` from 1.4.0 to 1.5.0 Updates `runner` from 1.4.0 to 1.5.0 Updates `rules` from 1.4.0 to 1.5.0 --- updated-dependencies: - dependency-name: androidx.test:core dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: androidx.test:runner dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: androidx.test:rules dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 25785e984e..060391781a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -32,7 +32,7 @@ def fragment = "1.5.5" // Testing def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 def espresso = "3.4.0" -def androidxTest = "1.4.0" +def androidxTest = "1.5.0" def androidxOrchestrator = "1.4.2" def paparazzi = "1.1.0" From 2614911631bd25fbc913d0031a6cdde286ff1a26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Dec 2022 23:03:51 +0000 Subject: [PATCH 034/237] Bump kotlin-reflect from 1.7.22 to 1.8.0 Bumps [kotlin-reflect](https://github.com/JetBrains/kotlin) from 1.7.22 to 1.8.0. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.7.22...v1.8.0) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-reflect dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- vector-app/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector-app/build.gradle b/vector-app/build.gradle index 11119a75cc..a4787e90db 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -403,7 +403,7 @@ dependencies { androidTestImplementation libs.mockk.mockkAndroid androidTestUtil libs.androidx.orchestrator androidTestImplementation libs.androidx.fragmentTesting - androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.22" + androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0" debugImplementation libs.androidx.fragmentTesting debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' } diff --git a/vector/build.gradle b/vector/build.gradle index 2224634194..e9497c81a0 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -331,5 +331,5 @@ dependencies { androidTestImplementation libs.mockk.mockkAndroid androidTestUtil libs.androidx.orchestrator debugImplementation libs.androidx.fragmentTesting - androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.22" + androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0" } From 136282d5b1eaca90fe9538c799aec443693dd500 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Dec 2022 23:08:38 +0000 Subject: [PATCH 035/237] Bump kotlin-gradle-plugin from 1.7.22 to 1.8.0 Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.7.22 to 1.8.0. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.7.22...v1.8.0) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 060391781a..c5dc908098 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -8,7 +8,7 @@ ext.versions = [ def gradle = "7.3.1" // Ref: https://kotlinlang.org/releases.html -def kotlin = "1.7.22" +def kotlin = "1.8.0" def kotlinCoroutines = "1.6.4" def dagger = "2.44.2" def firebaseBom = "31.1.1" From 6c50c75220823b5457639b54422c4726c72278fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 09:57:59 +0000 Subject: [PATCH 036/237] Bump espresso from 3.4.0 to 3.5.1 Bumps `espresso` from 3.4.0 to 3.5.1. Updates `espresso-core` from 3.4.0 to 3.5.1 Updates `espresso-contrib` from 3.4.0 to 3.5.1 Updates `espresso-intents` from 3.4.0 to 3.5.1 --- updated-dependencies: - dependency-name: androidx.test.espresso:espresso-core dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: androidx.test.espresso:espresso-contrib dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: androidx.test.espresso:espresso-intents dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index c5dc908098..a9a1b24cc5 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -31,7 +31,7 @@ def sentry = "6.11.0" def fragment = "1.5.5" // Testing def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 -def espresso = "3.4.0" +def espresso = "3.5.1" def androidxTest = "1.5.0" def androidxOrchestrator = "1.4.2" def paparazzi = "1.1.0" From e502d5d9a6a0f126d26c962805927496651d9e22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Dec 2022 23:04:09 +0000 Subject: [PATCH 037/237] Bump com.google.devtools.ksp from 1.7.22-1.0.8 to 1.8.0-1.0.8 Bumps [com.google.devtools.ksp](https://github.com/google/ksp) from 1.7.22-1.0.8 to 1.8.0-1.0.8. - [Release notes](https://github.com/google/ksp/releases) - [Commits](https://github.com/google/ksp/compare/1.7.22-1.0.8...1.8.0-1.0.8) --- updated-dependencies: - dependency-name: com.google.devtools.ksp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 53b7a983ec..850a4143c9 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ plugins { // Detekt id "io.gitlab.arturbosch.detekt" version "1.22.0" // Ksp - id "com.google.devtools.ksp" version "1.7.22-1.0.8" + id "com.google.devtools.ksp" version "1.8.0-1.0.8" // Dependency Analysis id 'com.autonomousapps.dependency-analysis' version "1.18.0" From d686d7aab24fd9a879b082962d6429c49546f89a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Jan 2023 11:22:46 +0100 Subject: [PATCH 038/237] Project property value can only be strings. Fix > Failed to notify project evaluation listener. > Could not create task ':element-android:matrix-sdk-android:compileDebugAndroidTestKotlin'. > Could not create task of type 'KotlinCompile'. > class java.util.LinkedHashMap cannot be cast to class java.lang.String (java.util.LinkedHashMap and java.lang.String are in module java.base of loader 'bootstrap') --- coverage.gradle | 4 ++-- matrix-sdk-android/build.gradle | 2 +- vector-app/build.gradle | 2 +- vector/build.gradle | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/coverage.gradle b/coverage.gradle index 2c0af25368..421c500728 100644 --- a/coverage.gradle +++ b/coverage.gradle @@ -80,12 +80,12 @@ task generateCoverageReport(type: JacocoReport) { task unitTestsWithCoverage(type: GradleBuild) { // the 7.1.3 android gradle plugin has a bug where enableTestCoverage generates invalid coverage - startParameter.projectProperties.coverage = [enableTestCoverage: false] + startParameter.projectProperties.coverage = "false" tasks = ['testDebugUnitTest'] } task instrumentationTestsWithCoverage(type: GradleBuild) { - startParameter.projectProperties.coverage = [enableTestCoverage: true] + startParameter.projectProperties.coverage = "true" startParameter.projectProperties['android.testInstrumentationRunnerArguments.notPackage'] = 'im.vector.app.ui' tasks = [':vector-app:connectedGplayDebugAndroidTest', ':vector:connectedDebugAndroidTest', 'matrix-sdk-android:connectedDebugAndroidTest'] } diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index ef6cb8cddb..9c9d2dd0dc 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -81,7 +81,7 @@ android { buildTypes { debug { if (project.hasProperty("coverage")) { - testCoverageEnabled = coverage.enableTestCoverage + testCoverageEnabled = coverage == "true" } // Set to true to log privacy or sensible data, such as token buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData") diff --git a/vector-app/build.gradle b/vector-app/build.gradle index a4787e90db..b2cfb7b426 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -232,7 +232,7 @@ android { resValue "color", "launcher_background", "#0DBD8B" if (project.hasProperty("coverage")) { - testCoverageEnabled = coverage.enableTestCoverage + testCoverageEnabled = coverage == "true" } } diff --git a/vector/build.gradle b/vector/build.gradle index e9497c81a0..c5bb55ae9a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -69,7 +69,7 @@ android { buildTypes { debug { if (project.hasProperty("coverage")) { - testCoverageEnabled = coverage.enableTestCoverage + testCoverageEnabled = coverage == "true" } } } From 242596744ac1f620cde5d48dffaeea006b4f5906 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Jan 2023 12:03:41 +0100 Subject: [PATCH 039/237] Use Fragment 1.6.0 alpha to fix issue with test https://issuetracker.google.com/issues/128612536 --- dependencies.gradle | 4 +++- vector-app/build.gradle | 2 +- vector/build.gradle | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index a9a1b24cc5..8b0933b943 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -28,7 +28,8 @@ def jjwt = "0.11.5" // the whole commit which set version 0.16.0-SNAPSHOT def vanniktechEmoji = "0.16.0-SNAPSHOT" def sentry = "6.11.0" -def fragment = "1.5.5" +// Use 1.6.0 alpha to fix issue with test +def fragment = "1.6.0-alpha04" // Testing def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 def espresso = "3.5.1" @@ -56,6 +57,7 @@ ext.libs = [ 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.5", 'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment", 'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment", + 'fragmentTestingManifest' : "androidx.fragment:fragment-testing-manifest:$fragment", 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4", 'work' : "androidx.work:work-runtime-ktx:2.7.1", 'autoFill' : "androidx.autofill:autofill:1.1.0", diff --git a/vector-app/build.gradle b/vector-app/build.gradle index b2cfb7b426..824f651b4d 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -404,7 +404,7 @@ dependencies { androidTestUtil libs.androidx.orchestrator androidTestImplementation libs.androidx.fragmentTesting androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0" - debugImplementation libs.androidx.fragmentTesting + debugImplementation libs.androidx.fragmentTestingManifest debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' } diff --git a/vector/build.gradle b/vector/build.gradle index c5bb55ae9a..efea312bed 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -330,6 +330,7 @@ dependencies { } androidTestImplementation libs.mockk.mockkAndroid androidTestUtil libs.androidx.orchestrator - debugImplementation libs.androidx.fragmentTesting + debugImplementation libs.androidx.fragmentTestingManifest + androidTestImplementation libs.androidx.fragmentTesting androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0" } From 0c045f3b111659bcb39214d1be151b88001ace41 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Jan 2023 12:20:23 +0100 Subject: [PATCH 040/237] Changelog file --- changelog.d/7936.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7936.misc diff --git a/changelog.d/7936.misc b/changelog.d/7936.misc new file mode 100644 index 0000000000..8480d9a6bf --- /dev/null +++ b/changelog.d/7936.misc @@ -0,0 +1 @@ +Upgrade to Kotlin 1.8 From 912d3e5055d301b5d4bd0c143b6ce9e21d5f9954 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 11 Jan 2023 15:57:39 +0300 Subject: [PATCH 041/237] Fix edited poll preview in room list. --- .../room/detail/timeline/format/DisplayableEventFormatter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index 5fa9576dd4..ef4846d822 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -130,7 +130,7 @@ class DisplayableEventFormatter @Inject constructor( span { } } in EventType.POLL_START.values -> { - timelineEvent.root.getClearContent().toModel(catchError = true)?.getBestPollCreationInfo()?.question?.getBestQuestion() + (timelineEvent.getVectorLastMessageContent() as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: stringProvider.getString(R.string.sent_a_poll) } in EventType.POLL_RESPONSE.values -> { From 61f7f12d7f40e4e33c937b02ee0a4a7910b3c06f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 11 Jan 2023 15:58:10 +0300 Subject: [PATCH 042/237] Fix edited rendering poll question in action preview. --- .../room/detail/timeline/action/MessageActionsViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 646cfa50d2..d442c1f1ba 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -216,8 +216,8 @@ class MessageActionsViewModel @AssistedInject constructor( noticeEventFormatter.format(timelineEvent, room?.roomSummary()?.isDirect.orFalse()) } in EventType.POLL_START.values -> { - timelineEvent.root.getClearContent().toModel(catchError = true) - ?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "" + (timelineEvent.getVectorLastMessageContent() as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() + ?: stringProvider.getString(R.string.message_reply_to_poll_preview) } else -> null } From 62e0c80a0633bfefde04d58b9c76c3ee2ea1aabf Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 11 Jan 2023 15:59:15 +0300 Subject: [PATCH 043/237] Fix rendering edited poll in timeline. --- .../sdk/api/session/room/timeline/TimelineEvent.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 6320ea964d..3aa480094c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -148,8 +148,8 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? { // Polls/Beacon are not message contents like others as there is no msgtype subtype to discriminate moshi parsing // so toModel won't parse them correctly // It's discriminated on event type instead. Maybe it shouldn't be MessageContent at all to avoid confusion? - in EventType.POLL_START.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() - in EventType.POLL_END.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() + in EventType.POLL_START.values -> (getLastPollEditNewContent() ?: root.getClearContent()).toModel() + in EventType.POLL_END.values -> (getLastPollEditNewContent() ?: root.getClearContent()).toModel() in EventType.STATE_ROOM_BEACON_INFO.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() in EventType.BEACON_LOCATION_DATA.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() else -> (getLastEditNewContent() ?: root.getClearContent()).toModel() @@ -160,6 +160,10 @@ fun TimelineEvent.getLastEditNewContent(): Content? { return annotations?.editSummary?.latestEdit?.getClearContent()?.toModel()?.newContent } +private fun TimelineEvent.getLastPollEditNewContent(): Content? { + return annotations?.editSummary?.latestEdit?.getClearContent()?.toModel()?.newContent +} + /** * Returns true if it's a reply. */ From ca99dc8a33c31a912d5e120721f6cc8d9f48e9d3 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 11 Jan 2023 16:10:06 +0300 Subject: [PATCH 044/237] Add changelog. --- changelog.d/7938.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7938.bugfix diff --git a/changelog.d/7938.bugfix b/changelog.d/7938.bugfix new file mode 100644 index 0000000000..70218edf8a --- /dev/null +++ b/changelog.d/7938.bugfix @@ -0,0 +1 @@ +Fix rendering of edited polls From f2d183520d1c43f259e6dbf40dce9aa90173c5ea Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Jan 2023 15:18:28 +0100 Subject: [PATCH 045/237] Add SECURITY.md (copied from https://github.com/vector-im/.github/blob/main/SECURITY.md) --- SECURITY.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..3126b47a07 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Reporting a Vulnerability + +**If you've found a security vulnerability, please report it to security@matrix.org** + +For more information on our security disclosure policy, visit https://www.matrix.org/security-disclosure-policy/ From c63d6fa1fbf8b88ef3cf026072be21cf14c43abe Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 4 Jan 2023 17:25:38 +0100 Subject: [PATCH 046/237] Fix unexpected live voice broadcast in the room list --- .../GetRoomLiveVoiceBroadcastsUseCase.kt | 9 +++- .../GetVoiceBroadcastStateEventLiveUseCase.kt | 13 +---- .../GetVoiceBroadcastStateEventUseCase.kt | 54 +++++++++++++++++++ 3 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetRoomLiveVoiceBroadcastsUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetRoomLiveVoiceBroadcastsUseCase.kt index fa5f06bfe6..fb48328305 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetRoomLiveVoiceBroadcastsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetRoomLiveVoiceBroadcastsUseCase.kt @@ -19,14 +19,20 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.isLive +import im.vector.app.features.voicebroadcast.model.VoiceBroadcast import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.voiceBroadcastId import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.getRoom import javax.inject.Inject +/** + * Get the list of live (not ended) voice broadcast events in the given room. + */ class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, + private val getVoiceBroadcastStateEventUseCase: GetVoiceBroadcastStateEventUseCase, ) { fun execute(roomId: String): List { @@ -37,7 +43,8 @@ class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor( setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO), QueryStringValue.IsNotEmpty ) - .mapNotNull { it.asVoiceBroadcastEvent() } + .mapNotNull { stateEvent -> stateEvent.asVoiceBroadcastEvent()?.voiceBroadcastId } + .mapNotNull { voiceBroadcastId -> getVoiceBroadcastStateEventUseCase.execute(VoiceBroadcast(voiceBroadcastId, roomId)) } .filter { it.isLive } } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventLiveUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventLiveUseCase.kt index b3bbdad635..22fb0df6f9 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventLiveUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventLiveUseCase.kt @@ -32,7 +32,6 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.transformWhile import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.util.Optional @@ -44,6 +43,7 @@ import javax.inject.Inject class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor( private val session: Session, + private val getVoiceBroadcastStateEventUseCase: GetVoiceBroadcastStateEventUseCase, ) { fun execute(voiceBroadcast: VoiceBroadcast): Flow> { @@ -93,7 +93,7 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor( * Get a flow of the most recent related event. */ private fun getMostRecentRelatedEventFlow(room: Room, voiceBroadcast: VoiceBroadcast): Flow> { - val mostRecentEvent = getMostRecentRelatedEvent(room, voiceBroadcast).toOptional() + val mostRecentEvent = getVoiceBroadcastStateEventUseCase.execute(voiceBroadcast).toOptional() return if (mostRecentEvent.hasValue()) { val stateKey = mostRecentEvent.get().root.stateKey.orEmpty() // observe incoming voice broadcast state events @@ -141,15 +141,6 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor( } } - /** - * Get the most recent event related to the given voice broadcast. - */ - private fun getMostRecentRelatedEvent(room: Room, voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? { - return room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) - .mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent()?.takeUnless { it.root.isRedacted() } } - .maxByOrNull { it.root.originServerTs ?: 0 } - } - /** * Get a flow of the given voice broadcast event changes. */ diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt new file mode 100644 index 0000000000..9c3d1cced3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.model.VoiceBroadcast +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.voiceBroadcastId +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.Room +import timber.log.Timber +import javax.inject.Inject + +class GetVoiceBroadcastStateEventUseCase @Inject constructor( + private val session: Session, +) { + + fun execute(voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? { + val room = session.getRoom(voiceBroadcast.roomId) ?: error("Unknown roomId: ${voiceBroadcast.roomId}") + return getMostRecentRelatedEvent(room, voiceBroadcast) + .also { event -> + Timber.d( + "## VoiceBroadcast | " + + "voiceBroadcastId=${event?.voiceBroadcastId}, " + + "state=${event?.content?.voiceBroadcastState}" + ) + } + } + + /** + * Get the most recent event related to the given voice broadcast. + */ + private fun getMostRecentRelatedEvent(room: Room, voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? { + return room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) + .mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent()?.takeUnless { it.root.isRedacted() } } + .maxByOrNull { it.root.originServerTs ?: 0 } + } +} From 63dccb4f3bce1f711c0ac107d17a17f8fcc08f18 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 5 Jan 2023 14:32:25 +0100 Subject: [PATCH 047/237] Add changelog file --- changelog.d/7832.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7832.bugfix diff --git a/changelog.d/7832.bugfix b/changelog.d/7832.bugfix new file mode 100644 index 0000000000..871f9aabb9 --- /dev/null +++ b/changelog.d/7832.bugfix @@ -0,0 +1 @@ +[Voice Broadcast] Fix unexpected "live broadcast" in the room list From 39c0cb201504cfc59b9ac45fa4ffed6534dbd64d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 6 Jan 2023 14:58:51 +0100 Subject: [PATCH 048/237] Add unit test --- .../GetVoiceBroadcastStateEventUseCaseTest.kt | 114 ++++++++++++++++++ .../java/im/vector/app/test/fakes/FakeRoom.kt | 2 +- .../vector/app/test/fakes/FakeRoomService.kt | 2 +- 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt new file mode 100644 index 0000000000..779ac39273 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.model.VoiceBroadcast +import im.vector.app.test.fakes.FakeSession +import io.mockk.every +import io.mockk.mockk +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeNull +import org.amshove.kluent.shouldNotBeNull +import org.junit.Test +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +private const val A_ROOM_ID = "A_ROOM_ID" +private const val A_VOICE_BROADCAST_ID = "A_VOICE_BROADCAST_ID" + +internal class GetVoiceBroadcastStateEventUseCaseTest { + + private val fakeSession = FakeSession() + private val getVoiceBroadcastStateEventUseCase = GetVoiceBroadcastStateEventUseCase(fakeSession) + + private val fakeRoom get() = fakeSession.fakeRoomService.fakeRoom + + @Test + fun `given there is no event related to the given vb, when execute, then return null`() { + // Given + val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) + every { fakeRoom.fakeTimelineService.getTimelineEventsRelatedTo(any(), any()) } returns emptyList() + + // When + val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast) + + // Then + result.shouldBeNull() + } + + @Test + fun `given there are several related events related to the given vb, when execute, then return the most recent one`() { + // Given + val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) + val aListOfTimelineEvents = listOf( + mockk(relaxed = true) { + every { root.eventId } returns "event_id_1" + every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { root.isRedacted() } returns false + every { root.originServerTs } returns 1L + }, + mockk(relaxed = true) { + every { root.eventId } returns "event_id_3" + every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { root.isRedacted() } returns false + every { root.originServerTs } returns 3L + }, + mockk(relaxed = true) { + every { root.eventId } returns "event_id_2" + every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { root.isRedacted() } returns false + every { root.originServerTs } returns 2L + }, + ) + every { fakeRoom.fakeTimelineService.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents + + // When + val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast) + + // Then + result.shouldNotBeNull() + result.root.eventId shouldBeEqualTo "event_id_3" + } + + @Test + fun `given there are several related events related to the given vb, when execute, then return the most recent one which is not redacted`() { + // Given + val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) + val aListOfTimelineEvents = listOf( + mockk(relaxed = true) { + every { root.eventId } returns "event_id_1" + every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { root.isRedacted() } returns false + every { root.originServerTs } returns 1L + }, + mockk(relaxed = true) { + every { root.eventId } returns "event_id_2" + every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { root.isRedacted() } returns true + every { root.originServerTs } returns 2L + }, + ) + every { fakeRoom.fakeTimelineService.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents + + // When + val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast) + + // Then + result.shouldNotBeNull() + result.root.eventId shouldBeEqualTo "event_id_1" + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt index 7835c314ef..8397095e35 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.Room class FakeRoom( private val fakeLocationSharingService: FakeLocationSharingService = FakeLocationSharingService(), private val fakeSendService: FakeSendService = FakeSendService(), - private val fakeTimelineService: FakeTimelineService = FakeTimelineService(), + val fakeTimelineService: FakeTimelineService = FakeTimelineService(), private val fakeRelationService: FakeRelationService = FakeRelationService(), private val fakeStateService: FakeStateService = FakeStateService(), ) : Room by mockk() { diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt index e957266383..7ba4b8e336 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.room.model.RoomSummary class FakeRoomService( - private val fakeRoom: FakeRoom = FakeRoom() + val fakeRoom: FakeRoom = FakeRoom() ) : RoomService by mockk() { override fun getRoom(roomId: String) = fakeRoom From 2df94807e02a6e522a96f4f550c78e3bf5e17883 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 9 Jan 2023 18:13:05 +0100 Subject: [PATCH 049/237] Restore private visibility of fake component fields --- .../usecase/GetVoiceBroadcastStateEventUseCaseTest.kt | 9 ++++----- .../src/test/java/im/vector/app/test/fakes/FakeRoom.kt | 2 +- .../java/im/vector/app/test/fakes/FakeRoomService.kt | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt index 779ac39273..8da864c22c 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt @@ -25,6 +25,7 @@ import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeNull import org.amshove.kluent.shouldNotBeNull import org.junit.Test +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent private const val A_ROOM_ID = "A_ROOM_ID" @@ -35,13 +36,11 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { private val fakeSession = FakeSession() private val getVoiceBroadcastStateEventUseCase = GetVoiceBroadcastStateEventUseCase(fakeSession) - private val fakeRoom get() = fakeSession.fakeRoomService.fakeRoom - @Test fun `given there is no event related to the given vb, when execute, then return null`() { // Given val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) - every { fakeRoom.fakeTimelineService.getTimelineEventsRelatedTo(any(), any()) } returns emptyList() + every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns emptyList() // When val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast) @@ -74,7 +73,7 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { every { root.originServerTs } returns 2L }, ) - every { fakeRoom.fakeTimelineService.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents + every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents // When val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast) @@ -102,7 +101,7 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { every { root.originServerTs } returns 2L }, ) - every { fakeRoom.fakeTimelineService.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents + every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents // When val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast) diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt index 8397095e35..7835c314ef 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.Room class FakeRoom( private val fakeLocationSharingService: FakeLocationSharingService = FakeLocationSharingService(), private val fakeSendService: FakeSendService = FakeSendService(), - val fakeTimelineService: FakeTimelineService = FakeTimelineService(), + private val fakeTimelineService: FakeTimelineService = FakeTimelineService(), private val fakeRelationService: FakeRelationService = FakeRelationService(), private val fakeStateService: FakeStateService = FakeStateService(), ) : Room by mockk() { diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt index 7ba4b8e336..e957266383 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.room.model.RoomSummary class FakeRoomService( - val fakeRoom: FakeRoom = FakeRoom() + private val fakeRoom: FakeRoom = FakeRoom() ) : RoomService by mockk() { override fun getRoom(roomId: String) = fakeRoom From 493fa7a0eba80ffc340a241b757609331a7b0544 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 9 Jan 2023 18:17:29 +0100 Subject: [PATCH 050/237] Use private method to mockk voice broadcast event --- .../GetVoiceBroadcastStateEventUseCaseTest.kt | 50 +++++++------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt index 8da864c22c..ea4777cb13 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt @@ -53,25 +53,10 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { fun `given there are several related events related to the given vb, when execute, then return the most recent one`() { // Given val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) - val aListOfTimelineEvents = listOf( - mockk(relaxed = true) { - every { root.eventId } returns "event_id_1" - every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO - every { root.isRedacted() } returns false - every { root.originServerTs } returns 1L - }, - mockk(relaxed = true) { - every { root.eventId } returns "event_id_3" - every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO - every { root.isRedacted() } returns false - every { root.originServerTs } returns 3L - }, - mockk(relaxed = true) { - every { root.eventId } returns "event_id_2" - every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO - every { root.isRedacted() } returns false - every { root.originServerTs } returns 2L - }, + val aListOfTimelineEvents = listOf( + givenAVoiceBroadcastEvent(eventId = "event_id_1", isRedacted = false, timestamp = 1L), + givenAVoiceBroadcastEvent(eventId = "event_id_3", isRedacted = false, timestamp = 3L), + givenAVoiceBroadcastEvent(eventId = "event_id_2", isRedacted = false, timestamp = 2L), ) every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents @@ -87,19 +72,9 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { fun `given there are several related events related to the given vb, when execute, then return the most recent one which is not redacted`() { // Given val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) - val aListOfTimelineEvents = listOf( - mockk(relaxed = true) { - every { root.eventId } returns "event_id_1" - every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO - every { root.isRedacted() } returns false - every { root.originServerTs } returns 1L - }, - mockk(relaxed = true) { - every { root.eventId } returns "event_id_2" - every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO - every { root.isRedacted() } returns true - every { root.originServerTs } returns 2L - }, + val aListOfTimelineEvents = listOf( + givenAVoiceBroadcastEvent(eventId = "event_id_1", isRedacted = false, timestamp = 1L), + givenAVoiceBroadcastEvent(eventId = "event_id_2", isRedacted = true, timestamp = 2L), ) every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents @@ -110,4 +85,15 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { result.shouldNotBeNull() result.root.eventId shouldBeEqualTo "event_id_1" } + + private fun givenAVoiceBroadcastEvent( + eventId: String, + isRedacted: Boolean, + timestamp: Long, + ) = mockk(relaxed = true) { + every { root.eventId } returns eventId + every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { root.isRedacted() } returns isRedacted + every { root.originServerTs } returns timestamp + } } From f62f661d2bed9700d4a0a4b00b19bfe9c41433d9 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 11 Jan 2023 17:21:34 +0100 Subject: [PATCH 051/237] Room list - Do not show live broadcast if the started event is redacted --- .../home/room/list/RoomSummaryItemFactory.kt | 27 +-- .../GetLatestPreviewableEventUseCase.kt | 72 +++++++ .../GetVoiceBroadcastStateEventUseCase.kt | 14 +- .../GetLatestPreviewableEventUseCaseTest.kt | 196 ++++++++++++++++++ .../GetVoiceBroadcastStateEventUseCaseTest.kt | 48 ++++- 5 files changed, 321 insertions(+), 36 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCase.kt create mode 100644 vector/src/test/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCaseTest.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index a55900a5c4..18c8ea3bde 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -22,41 +22,33 @@ import com.airbnb.mvrx.Loading import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter +import im.vector.app.features.home.room.list.usecase.GetLatestPreviewableEventUseCase import im.vector.app.features.home.room.typing.TypingHelper import im.vector.app.features.voicebroadcast.isLive -import im.vector.app.features.voicebroadcast.isVoiceBroadcast import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent -import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.getRoom -import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo -import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject class RoomSummaryItemFactory @Inject constructor( - private val sessionHolder: ActiveSessionHolder, private val displayableEventFormatter: DisplayableEventFormatter, private val dateFormatter: VectorDateFormatter, private val stringProvider: StringProvider, private val typingHelper: TypingHelper, private val avatarRenderer: AvatarRenderer, private val errorFormatter: ErrorFormatter, - private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase, + private val getLatestPreviewableEventUseCase: GetLatestPreviewableEventUseCase, ) { fun create( @@ -142,7 +134,7 @@ class RoomSummaryItemFactory @Inject constructor( val showSelected = selectedRoomIds.contains(roomSummary.roomId) var latestFormattedEvent: CharSequence = "" var latestEventTime = "" - val latestEvent = roomSummary.getVectorLatestPreviewableEvent() + val latestEvent = getLatestPreviewableEventUseCase.execute(roomSummary.roomId) if (latestEvent != null) { latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not()) latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST) @@ -150,7 +142,8 @@ class RoomSummaryItemFactory @Inject constructor( val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers) // Skip typing while there is a live voice broadcast - .takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() }.orEmpty() + .takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() } + .orEmpty() return if (subtitle.isBlank() && displayMode == RoomListDisplayMode.FILTERED) { createCenteredRoomSummaryItem(roomSummary, displayMode, showSelected, unreadCount, onClick, onLongClick) @@ -240,14 +233,4 @@ class RoomSummaryItemFactory @Inject constructor( else -> stringProvider.getQuantityString(R.plurals.search_space_multiple_parents, size - 1, directParentNames[0], size - 1) } } - - private fun RoomSummary.getVectorLatestPreviewableEvent(): TimelineEvent? { - val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return latestPreviewableEvent - val liveVoiceBroadcastTimelineEvent = getRoomLiveVoiceBroadcastsUseCase.execute(roomId).lastOrNull() - ?.root?.eventId?.let { room.getTimelineEvent(it) } - return latestPreviewableEvent?.takeIf { it.root.getClearType() == EventType.CALL_INVITE } - ?: liveVoiceBroadcastTimelineEvent - ?: latestPreviewableEvent - ?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast - } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCase.kt new file mode 100644 index 0000000000..6a50e87562 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCase.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.usecase + +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.voicebroadcast.isLive +import im.vector.app.features.voicebroadcast.isVoiceBroadcast +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase +import im.vector.app.features.voicebroadcast.voiceBroadcastId +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.getTimelineEvent +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import javax.inject.Inject + +class GetLatestPreviewableEventUseCase @Inject constructor( + private val sessionHolder: ActiveSessionHolder, + private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase, +) { + + fun execute(roomId: String): TimelineEvent? { + val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return null + val roomSummary = room.roomSummary() ?: return null + return getCallEvent(roomSummary) + ?: getLiveVoiceBroadcastEvent(room) + ?: getDefaultLatestEvent(room, roomSummary) + } + + private fun getCallEvent(roomSummary: RoomSummary): TimelineEvent? { + return roomSummary.latestPreviewableEvent + ?.takeIf { it.root.getClearType() == EventType.CALL_INVITE } + } + + private fun getLiveVoiceBroadcastEvent(room: Room): TimelineEvent? { + return getRoomLiveVoiceBroadcastsUseCase.execute(room.roomId) + .lastOrNull() + ?.voiceBroadcastId + ?.let { room.getTimelineEvent(it) } + } + + private fun getDefaultLatestEvent(room: Room, roomSummary: RoomSummary): TimelineEvent? { + val latestPreviewableEvent = roomSummary.latestPreviewableEvent + + // If the default latest event is a live voice broadcast (paused or resumed), rely to the started event + val liveVoiceBroadcastEventId = latestPreviewableEvent?.root?.asVoiceBroadcastEvent()?.takeIf { it.isLive }?.voiceBroadcastId + if (liveVoiceBroadcastEventId != null) { + return room.getTimelineEvent(liveVoiceBroadcastEventId) + } + + return latestPreviewableEvent + ?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt index 9c3d1cced3..e821e09119 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt @@ -20,10 +20,12 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcast import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.voiceBroadcastId +import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.getTimelineEvent import timber.log.Timber import javax.inject.Inject @@ -47,8 +49,14 @@ class GetVoiceBroadcastStateEventUseCase @Inject constructor( * Get the most recent event related to the given voice broadcast. */ private fun getMostRecentRelatedEvent(room: Room, voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? { - return room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) - .mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent()?.takeUnless { it.root.isRedacted() } } - .maxByOrNull { it.root.originServerTs ?: 0 } + val startedEvent = room.getTimelineEvent(voiceBroadcast.voiceBroadcastId) + return if (startedEvent?.root?.isRedacted().orTrue()) { + null + } else { + room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) + .mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent() } + .filterNot { it.root.isRedacted() } + .maxByOrNull { it.root.originServerTs ?: 0 } + } } } diff --git a/vector/src/test/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCaseTest.kt new file mode 100644 index 0000000000..5d526c783b --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCaseTest.kt @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.usecase + +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase +import im.vector.app.test.fakes.FakeActiveSessionHolder +import im.vector.app.test.fakes.FakeRoom +import io.mockk.every +import io.mockk.mockk +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeNull +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +private const val A_ROOM_ID = "a-room-id" + +internal class GetLatestPreviewableEventUseCaseTest { + + private val fakeRoom = FakeRoom() + private val fakeSessionHolder = FakeActiveSessionHolder() + private val fakeRoomSummary = mockk() + private val fakeGetRoomLiveVoiceBroadcastsUseCase = mockk() + + private val getLatestPreviewableEventUseCase = GetLatestPreviewableEventUseCase( + fakeSessionHolder.instance, + fakeGetRoomLiveVoiceBroadcastsUseCase, + ) + + @Before + fun setup() { + every { fakeSessionHolder.instance.getSafeActiveSession()?.getRoom(A_ROOM_ID) } returns fakeRoom + every { fakeRoom.roomSummary() } returns fakeRoomSummary + every { fakeRoom.roomId } returns A_ROOM_ID + every { fakeRoom.timelineService().getTimelineEvent(any()) } answers { + mockk(relaxed = true) { + every { eventId } returns firstArg() + } + } + } + + @Test + fun `given the latest event is a call invite and there is a live broadcast, when execute, returns the call event`() { + // Given + val aLatestPreviewableEvent = mockk { + every { root.type } returns EventType.MESSAGE + every { root.getClearType() } returns EventType.CALL_INVITE + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns listOf( + givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STARTED, "id1"), + givenAVoiceBroadcastEvent("id2", VoiceBroadcastState.RESUMED, "id1"), + ).mapNotNull { it.asVoiceBroadcastEvent() } + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result shouldBe aLatestPreviewableEvent + } + + @Test + fun `given the latest event is not a call invite and there is a live broadcast, when execute, returns the latest broadcast event`() { + // Given + val aLatestPreviewableEvent = mockk { + every { root.type } returns EventType.MESSAGE + every { root.getClearType() } returns EventType.MESSAGE + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns listOf( + givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STARTED, "vb_id1"), + givenAVoiceBroadcastEvent("id2", VoiceBroadcastState.RESUMED, "vb_id2"), + ).mapNotNull { it.asVoiceBroadcastEvent() } + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result?.eventId shouldBeEqualTo "vb_id2" + } + + @Test + fun `given there is no live broadcast, when execute, returns the latest event`() { + // Given + val aLatestPreviewableEvent = mockk { + every { root.type } returns EventType.MESSAGE + every { root.getClearType() } returns EventType.MESSAGE + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList() + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result shouldBe aLatestPreviewableEvent + } + + @Test + fun `given there is no live broadcast and the latest event is a vb message, when execute, returns null`() { + // Given + val aLatestPreviewableEvent = mockk { + every { root.type } returns EventType.MESSAGE + every { root.getClearType() } returns EventType.MESSAGE + every { root.getClearContent() } returns mapOf( + MessageContent.MSG_TYPE_JSON_KEY to "m.audio", + VOICE_BROADCAST_CHUNK_KEY to "1", + "body" to "", + ) + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList() + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result.shouldBeNull() + } + + @Test + fun `given the latest event is an ended vb, when execute, returns the stopped event`() { + // Given + val aLatestPreviewableEvent = mockk { + every { eventId } returns "id1" + every { root } returns givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STOPPED, "vb_id1") + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList() + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result?.eventId shouldBeEqualTo "id1" + } + + @Test + fun `given the latest event is a resumed vb, when execute, returns the started event`() { + // Given + val aLatestPreviewableEvent = mockk { + every { eventId } returns "id1" + every { root } returns givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.RESUMED, "vb_id1") + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList() + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result?.eventId shouldBeEqualTo "vb_id1" + } + + private fun givenAVoiceBroadcastEvent( + eventId: String, + state: VoiceBroadcastState, + voiceBroadcastId: String, + ): Event = mockk { + every { this@mockk.eventId } returns eventId + every { getClearType() } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { content } returns mapOf( + "state" to state.value, + "m.relates_to" to mapOf( + "rel_type" to RelationType.REFERENCE, + "event_id" to voiceBroadcastId + ) + ) + } +} diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt index ea4777cb13..00b04aea81 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt @@ -18,6 +18,7 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.VoiceBroadcast +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeSession import io.mockk.every import io.mockk.mockk @@ -40,6 +41,7 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { fun `given there is no event related to the given vb, when execute, then return null`() { // Given val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) + every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEvent(A_VOICE_BROADCAST_ID) } returns null every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns emptyList() // When @@ -54,9 +56,9 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { // Given val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) val aListOfTimelineEvents = listOf( - givenAVoiceBroadcastEvent(eventId = "event_id_1", isRedacted = false, timestamp = 1L), - givenAVoiceBroadcastEvent(eventId = "event_id_3", isRedacted = false, timestamp = 3L), - givenAVoiceBroadcastEvent(eventId = "event_id_2", isRedacted = false, timestamp = 2L), + givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = false, timestamp = 1L), + givenAVoiceBroadcastEvent(eventId = "event_id_3", state = VoiceBroadcastState.STOPPED, isRedacted = false, timestamp = 3L), + givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.PAUSED, isRedacted = false, timestamp = 2L), ) every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents @@ -73,8 +75,8 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { // Given val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) val aListOfTimelineEvents = listOf( - givenAVoiceBroadcastEvent(eventId = "event_id_1", isRedacted = false, timestamp = 1L), - givenAVoiceBroadcastEvent(eventId = "event_id_2", isRedacted = true, timestamp = 2L), + givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = false, timestamp = 1L), + givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.STOPPED, isRedacted = true, timestamp = 2L), ) every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents @@ -83,17 +85,41 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { // Then result.shouldNotBeNull() - result.root.eventId shouldBeEqualTo "event_id_1" + result.root.eventId shouldBeEqualTo A_VOICE_BROADCAST_ID + } + + @Test + fun `given a not ended voice broadcast with a redacted start event, when execute, then return null`() { + // Given + val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) + val aListOfTimelineEvents = listOf( + givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = true, timestamp = 1L), + givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.PAUSED, isRedacted = false, timestamp = 2L), + givenAVoiceBroadcastEvent(eventId = "event_id_3", state = VoiceBroadcastState.RESUMED, isRedacted = false, timestamp = 3L), + ) + every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents + + // When + val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast) + + // Then + result.shouldBeNull() } private fun givenAVoiceBroadcastEvent( eventId: String, + state: VoiceBroadcastState, isRedacted: Boolean, timestamp: Long, - ) = mockk(relaxed = true) { - every { root.eventId } returns eventId - every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO - every { root.isRedacted() } returns isRedacted - every { root.originServerTs } returns timestamp + ): TimelineEvent { + val timelineEvent = mockk { + every { root.eventId } returns eventId + every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { root.content } returns mapOf("state" to state.value) + every { root.isRedacted() } returns isRedacted + every { root.originServerTs } returns timestamp + } + every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEvent(eventId) } returns timelineEvent + return timelineEvent } } From a4a7fa69e81377ee6051983a3067f6ca87ff9591 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jan 2023 23:02:02 +0000 Subject: [PATCH 052/237] Bump appcompat from 1.5.1 to 1.6.0 Bumps appcompat from 1.5.1 to 1.6.0. --- updated-dependencies: - dependency-name: androidx.appcompat:appcompat dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 8b0933b943..23f0be97eb 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -50,7 +50,7 @@ ext.libs = [ ], androidx : [ 'activity' : "androidx.activity:activity-ktx:1.6.1", - 'appCompat' : "androidx.appcompat:appcompat:1.5.1", + 'appCompat' : "androidx.appcompat:appcompat:1.6.0", 'biometric' : "androidx.biometric:biometric:1.1.0", 'core' : "androidx.core:core-ktx:1.9.0", 'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1", From 12ad6496c200aec33e3e8cc55b4aafefaf7d35f7 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 20 Dec 2022 09:38:32 +0100 Subject: [PATCH 053/237] Adding changelog entry --- changelog.d/7824.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7824.feature diff --git a/changelog.d/7824.feature b/changelog.d/7824.feature new file mode 100644 index 0000000000..3c8b416571 --- /dev/null +++ b/changelog.d/7824.feature @@ -0,0 +1 @@ +[Poll] Warning message on decryption failure of some events From bd7b1f94960336f1fd176b8bb3481bd80d99f3d4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 20 Dec 2022 17:17:42 +0100 Subject: [PATCH 054/237] (WIP) Introducing new UnableToDecryptEventEntity --- .../database/EventInsertLiveObserver.kt | 3 + .../UnableToDecryptEventLiveObserver.kt | 83 ++++++++++++++++++ .../PollResponseAggregatedSummaryEntity.kt | 1 + .../model/UnableToDecryptEventEntity.kt | 27 ++++++ .../database/query/EventEntityQueries.kt | 9 +- .../sdk/internal/session/SessionModule.kt | 10 +++ .../UnableToDecryptEventLiveProcessor.kt | 33 +++++++ ...yptedEventRelationsAggregationProcessor.kt | 87 +++++++++++++++++++ .../EventRelationsAggregationProcessor.kt | 6 +- .../poll/DefaultPollAggregationProcessor.kt | 4 + .../EncryptedReferenceAggregationProcessor.kt | 48 ++++++++++ .../factory/PollItemViewStateFactory.kt | 1 + 12 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index d1ca4f48a6..c276e571fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -40,6 +40,9 @@ internal class EventInsertLiveObserver @Inject constructor( private val lock = Mutex() + // TODO should we create a dedicated UnableToDecryptEntity or EncryptedEventEntity? + // and process them into a dedicated observer? + // Create also a new LiveProcessor interface for the new entity? override val query = Monarchy.Query { it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt new file mode 100644 index 0000000000..7835a16ad1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database + +import com.zhuinden.monarchy.Monarchy +import io.realm.RealmConfiguration +import io.realm.RealmResults +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.matrix.android.sdk.internal.database.mapper.asDomain +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity +import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntityFields +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.UnableToDecryptEventLiveProcessor +import timber.log.Timber +import javax.inject.Inject + +internal class UnableToDecryptEventLiveObserver @Inject constructor( + @SessionDatabase realmConfiguration: RealmConfiguration, + private val processors: Set<@JvmSuppressWildcards UnableToDecryptEventLiveProcessor> +) : + RealmLiveEntityObserver(realmConfiguration) { + + private val lock = Mutex() + + override val query = Monarchy.Query { + it.where(UnableToDecryptEventEntity::class.java) + } + + override fun onChange(results: RealmResults) { + observerScope.launch { + lock.withLock { + if (!results.isLoaded || results.isEmpty()) { + return@withLock + } + val copiedEvents = ArrayList(results.size) + Timber.v("UnableToDecryptEventEntity updated with ${results.size} results in db") + results.forEach { + // don't use copy from realm over there + val copiedEvent = UnableToDecryptEventEntity(eventId = it.eventId) + copiedEvents.add(copiedEvent) + } + awaitTransaction(realmConfiguration) { realm -> + Timber.v("##Transaction: There are ${copiedEvents.size} events to process ") + copiedEvents.forEach { utdEvent -> + val eventId = utdEvent.eventId + val event = EventEntity.where(realm, eventId).findFirst() + if (event == null) { + Timber.v("Event $eventId not found") + return@forEach + } + val domainEvent = event.asDomain() + processors.forEach { + it.process(realm, domainEvent) + } + } + realm.where(UnableToDecryptEventEntity::class.java) + .`in`(UnableToDecryptEventEntityFields.EVENT_ID, copiedEvents.map { it.eventId }.toTypedArray()) + .findAll() + .deleteAllFromRealm() + } + processors.forEach { it.onPostProcess() } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt index d759bd3cd9..11d2ed64ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt @@ -36,5 +36,6 @@ internal open class PollResponseAggregatedSummaryEntity( var sourceLocalEchoEvents: RealmList = RealmList() ) : RealmObject() { + // TODO add a list of related eventIds which could not be decrypted companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt new file mode 100644 index 0000000000..6393877d38 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.model + +import io.realm.RealmObject + +/** + * This class is used to get notification on new UTD events. Since these events cannot be processed + * in EventInsertEntity, we should introduce a dedicated entity for that. + */ +internal open class UnableToDecryptEventEntity( + var eventId: String = "", +) : RealmObject() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt index 0f1c226044..e18495c924 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertEntity import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInsertType): EventEntity { val eventEntity = realm.where() @@ -32,11 +33,17 @@ internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInse .equalTo(EventEntityFields.ROOM_ID, roomId) .findFirst() return if (eventEntity == null) { - val canBeProcessed = type != EventType.ENCRYPTED || decryptionResultJson != null + val isEncrypted = type == EventType.ENCRYPTED && decryptionResultJson == null + val canBeProcessed = isEncrypted.not() val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = canBeProcessed).apply { this.insertType = insertType } realm.insert(insertEntity) + // TODO check with others if it is the right spot to detect UTD events + if (isEncrypted) { + val utdEventEntity = UnableToDecryptEventEntity(eventId = eventId) + realm.insert(utdEventEntity) + } // copy this event entity and return it realm.copyToRealm(this) } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index b9f56cbc9f..d33112a922 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -50,6 +50,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask import org.matrix.android.sdk.internal.database.EventInsertLiveObserver import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory +import org.matrix.android.sdk.internal.database.UnableToDecryptEventLiveObserver import org.matrix.android.sdk.internal.di.Authenticated import org.matrix.android.sdk.internal.di.CacheDirectory import org.matrix.android.sdk.internal.di.DeviceId @@ -84,6 +85,7 @@ import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService +import org.matrix.android.sdk.internal.session.room.EncryptedEventRelationsAggregationProcessor import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor @@ -346,6 +348,10 @@ internal abstract class SessionModule { @IntoSet abstract fun bindEventInsertObserver(observer: EventInsertLiveObserver): SessionLifecycleObserver + @Binds + @IntoSet + abstract fun bindUnableToDecryptEventObserver(observer: UnableToDecryptEventLiveObserver): SessionLifecycleObserver + @Binds @IntoSet abstract fun bindIntegrationManager(manager: IntegrationManager): SessionLifecycleObserver @@ -405,4 +411,8 @@ internal abstract class SessionModule { @Binds abstract fun bindPollAggregationProcessor(processor: DefaultPollAggregationProcessor): PollAggregationProcessor + + @Binds + @IntoSet + abstract fun bindEncryptedEventRelationsAggregationProcessor(processor: EncryptedEventRelationsAggregationProcessor): UnableToDecryptEventLiveProcessor } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt new file mode 100644 index 0000000000..c5fa6dc88e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session + +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.Event + +internal interface UnableToDecryptEventLiveProcessor { + + fun process(realm: Realm, event: Event) + + /** + * Called after transaction. + * Maybe you prefer to process the events outside of the realm transaction. + */ + suspend fun onPostProcess() { + // Noop by default + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt new file mode 100644 index 0000000000..2cd05a8e6f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room + +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.LocalEcho +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.internal.session.UnableToDecryptEventLiveProcessor +import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor +import timber.log.Timber +import javax.inject.Inject + +internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( + private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, +) : UnableToDecryptEventLiveProcessor { + + // TODO add unit tests + override fun process(realm: Realm, event: Event) { + val roomId = event.roomId + if (roomId == null) { + Timber.w("Event has no room id ${event.eventId}") + return + } + + val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") + + when (event.getClearType()) { + EventType.ENCRYPTED -> { + val encryptedEventContent = event.content.toModel() + processEncryptedContent( + encryptedEventContent = encryptedEventContent, + realm = realm, + event = event, + roomId = roomId, + isLocalEcho = isLocalEcho, + ) + } + else -> Unit + } + } + + private fun processEncryptedContent( + encryptedEventContent: EncryptedEventContent?, + realm: Realm, + event: Event, + roomId: String, + isLocalEcho: Boolean, + ) { + when (encryptedEventContent?.relatesTo?.type) { + RelationType.REPLACE -> { + Timber.w("## UTD replace in room $roomId for event ${event.eventId}") + } + RelationType.RESPONSE -> { + // can we / should we do we something for UTD response?? + Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + } + RelationType.REFERENCE -> { + // can we / should we do we something for UTD reference?? + Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + encryptedReferenceAggregationProcessor.handle(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) + } + RelationType.ANNOTATION -> { + // can we / should we do we something for UTD annotation?? + Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + } + else -> Unit + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index be73309837..d03f98158b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -61,6 +61,7 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor +import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -73,6 +74,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private val sessionManager: SessionManager, private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor, private val pollAggregationProcessor: PollAggregationProcessor, + private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, private val editValidator: EventEditValidator, private val clock: Clock, ) : EventInsertLiveProcessor { @@ -170,11 +172,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } } + // TODO remove this once other implementation is validated // As for now Live event processors are not receiving UTD events. // They will get an update if the event is decrypted later EventType.ENCRYPTED -> { // Relation type is in clear, it might be possible to do some things? - // Notice that if the event is decrypted later, process be called again + // Notice that if the event is decrypted later, process will be called again val encryptedEventContent = event.content.toModel() when (encryptedEventContent?.relatesTo?.type) { RelationType.REPLACE -> { @@ -189,6 +192,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( RelationType.REFERENCE -> { // can we / should we do we something for UTD reference?? Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + encryptedReferenceAggregationProcessor.handle(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) } RelationType.ANNOTATION -> { // can we / should we do we something for UTD annotation?? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt index a424becbd6..301996ebab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -155,6 +155,8 @@ internal class DefaultPollAggregationProcessor @Inject constructor( ) aggregatedPollSummaryEntity.aggregatedContent = ContentMapper.map(newSumModel.toContent()) + // TODO check also if eventId is part of UTD list of eventIds, if so remove it + return true } @@ -184,6 +186,8 @@ internal class DefaultPollAggregationProcessor @Inject constructor( ensurePollIsFullyAggregated(roomId, pollEventId) } + // TODO check also if eventId is part of UTD list of eventIds, if so remove it + return true } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt new file mode 100644 index 0000000000..442ce24dbb --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.aggregation.utd + +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.Event +import javax.inject.Inject + +class EncryptedReferenceAggregationProcessor @Inject constructor() { + + // TODO add unit tests + fun handle( + realm: Realm, + event: Event, + roomId: String, + isLocalEcho: Boolean, + relatedEventId: String? + ) { + if(isLocalEcho || relatedEventId.isNullOrEmpty()) return + + handlePollReference(realm = realm, event = event, roomId = roomId, relatedEventId = relatedEventId) + } + + // TODO how to check this is working? + private fun handlePollReference( + realm: Realm, + event: Event, + roomId: String, + relatedEventId: String + ) { + // TODO check if relatedEventId is referencing any existing poll event in DB + // TODO if related to a poll, then add the event id into the list of encryptedRelatedEvents in the summary + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index 13f63e86c4..c885f6f3ff 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -35,6 +35,7 @@ class PollItemViewStateFactory @Inject constructor( pollContent: MessagePollContent, informationData: MessageInformationData, ): PollViewState { + // TODO check for decryption failure error in informationData val pollCreationInfo = pollContent.getBestPollCreationInfo() val question = pollCreationInfo?.question?.getBestQuestion().orEmpty() From ae2639aeb069d8e802c019b2d406f9178956c7c0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 21 Dec 2022 14:10:13 +0100 Subject: [PATCH 055/237] Keep track of related events to a poll which had failed to be decrypted --- .../model/PollResponseAggregatedSummary.kt | 4 +++- .../database/EventInsertLiveObserver.kt | 3 --- ...llResponseAggregatedSummaryEntityMapper.kt | 6 ++++-- .../PollResponseAggregatedSummaryEntity.kt | 5 +++-- .../database/model/SessionRealmModule.kt | 3 ++- ...yptedEventRelationsAggregationProcessor.kt | 2 +- .../EncryptedReferenceAggregationProcessor.kt | 21 ++++++++++++------- .../factory/PollItemViewStateFactory.kt | 2 +- 8 files changed, 28 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt index b16852e47d..e8b4ef6ed6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt @@ -23,5 +23,7 @@ data class PollResponseAggregatedSummary( val nbOptions: Int = 0, // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk) val sourceEvents: List, - val localEchos: List + val localEchos: List, + // list of related event ids which are encrypted due to decryption failure + val encryptedRelatedEventIds: List, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index c276e571fe..d1ca4f48a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -40,9 +40,6 @@ internal class EventInsertLiveObserver @Inject constructor( private val lock = Mutex() - // TODO should we create a dedicated UnableToDecryptEntity or EncryptedEventEntity? - // and process them into a dedicated observer? - // Create also a new LiveProcessor interface for the new entity? override val query = Monarchy.Query { it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PollResponseAggregatedSummaryEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PollResponseAggregatedSummaryEntityMapper.kt index 00998af9bb..808a49b958 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PollResponseAggregatedSummaryEntityMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PollResponseAggregatedSummaryEntityMapper.kt @@ -30,7 +30,8 @@ internal object PollResponseAggregatedSummaryEntityMapper { closedTime = entity.closedTime, localEchos = entity.sourceLocalEchoEvents.toList(), sourceEvents = entity.sourceEvents.toList(), - nbOptions = entity.nbOptions + nbOptions = entity.nbOptions, + encryptedRelatedEventIds = entity.encryptedRelatedEventIds.toList(), ) } @@ -40,7 +41,8 @@ internal object PollResponseAggregatedSummaryEntityMapper { nbOptions = model.nbOptions, closedTime = model.closedTime, sourceEvents = RealmList().apply { addAll(model.sourceEvents) }, - sourceLocalEchoEvents = RealmList().apply { addAll(model.localEchos) } + sourceLocalEchoEvents = RealmList().apply { addAll(model.localEchos) }, + encryptedRelatedEventIds = RealmList().apply { addAll(model.encryptedRelatedEventIds) }, ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt index 11d2ed64ba..906e329f6f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt @@ -33,9 +33,10 @@ internal open class PollResponseAggregatedSummaryEntity( // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk) var sourceEvents: RealmList = RealmList(), - var sourceLocalEchoEvents: RealmList = RealmList() + var sourceLocalEchoEvents: RealmList = RealmList(), + // list of related event ids which are encrypted due to decryption failure + var encryptedRelatedEventIds: RealmList = RealmList(), ) : RealmObject() { - // TODO add a list of related eventIds which could not be decrypted companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 0d998e8fe1..79b0dd699c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -72,7 +72,8 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit SpaceParentSummaryEntity::class, UserPresenceEntity::class, ThreadSummaryEntity::class, - ThreadListPageEntity::class + ThreadListPageEntity::class, + UnableToDecryptEventEntity::class, ] ) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt index 2cd05a8e6f..7e9a0a9499 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt @@ -75,7 +75,7 @@ internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( RelationType.REFERENCE -> { // can we / should we do we something for UTD reference?? Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - encryptedReferenceAggregationProcessor.handle(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) + encryptedReferenceAggregationProcessor.handle(realm, event, isLocalEcho, encryptedEventContent.relatesTo.eventId) } RelationType.ANNOTATION -> { // can we / should we do we something for UTD annotation?? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt index 442ce24dbb..fdd3bc80e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.room.aggregation.utd import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields import javax.inject.Inject class EncryptedReferenceAggregationProcessor @Inject constructor() { @@ -26,23 +28,28 @@ class EncryptedReferenceAggregationProcessor @Inject constructor() { fun handle( realm: Realm, event: Event, - roomId: String, isLocalEcho: Boolean, relatedEventId: String? ) { - if(isLocalEcho || relatedEventId.isNullOrEmpty()) return + if (isLocalEcho || relatedEventId.isNullOrEmpty()) return - handlePollReference(realm = realm, event = event, roomId = roomId, relatedEventId = relatedEventId) + handlePollReference(realm = realm, event = event, relatedEventId = relatedEventId) } - // TODO how to check this is working? private fun handlePollReference( realm: Realm, event: Event, - roomId: String, relatedEventId: String ) { - // TODO check if relatedEventId is referencing any existing poll event in DB - // TODO if related to a poll, then add the event id into the list of encryptedRelatedEvents in the summary + event.eventId?.let { eventId -> + val existingRelatedPoll = getPollSummaryWithEventId(realm, relatedEventId) + existingRelatedPoll?.encryptedRelatedEventIds?.add(eventId) + } + } + + private fun getPollSummaryWithEventId(realm: Realm, eventId: String): PollResponseAggregatedSummaryEntity? { + return realm.where(PollResponseAggregatedSummaryEntity::class.java) + .containsValue(PollResponseAggregatedSummaryEntityFields.SOURCE_EVENTS.`$`, eventId) + .findFirst() } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index c885f6f3ff..db11dff007 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -35,7 +35,7 @@ class PollItemViewStateFactory @Inject constructor( pollContent: MessagePollContent, informationData: MessageInformationData, ): PollViewState { - // TODO check for decryption failure error in informationData + // TODO add new field in ViewState to reflect decryption error of related events val pollCreationInfo = pollContent.getBestPollCreationInfo() val question = pollCreationInfo?.question?.getBestQuestion().orEmpty() From c0c5e208bde46610e615862ca23331d248b86096 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 21 Dec 2022 15:16:47 +0100 Subject: [PATCH 056/237] Remove processing of encrypted events from EventRelationsAggregationProcessor --- .../EventRelationsAggregationProcessor.kt | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index d03f98158b..0734b286b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel @@ -61,7 +60,6 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor -import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -74,7 +72,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private val sessionManager: SessionManager, private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor, private val pollAggregationProcessor: PollAggregationProcessor, - private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, private val editValidator: EventEditValidator, private val clock: Clock, ) : EventInsertLiveProcessor { @@ -172,34 +169,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } } - // TODO remove this once other implementation is validated - // As for now Live event processors are not receiving UTD events. - // They will get an update if the event is decrypted later - EventType.ENCRYPTED -> { - // Relation type is in clear, it might be possible to do some things? - // Notice that if the event is decrypted later, process will be called again - val encryptedEventContent = event.content.toModel() - when (encryptedEventContent?.relatesTo?.type) { - RelationType.REPLACE -> { - Timber.v("###REPLACE in room $roomId for event ${event.eventId}") - // A replace! - handleReplace(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) - } - RelationType.RESPONSE -> { - // can we / should we do we something for UTD response?? - Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - } - RelationType.REFERENCE -> { - // can we / should we do we something for UTD reference?? - Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - encryptedReferenceAggregationProcessor.handle(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) - } - RelationType.ANNOTATION -> { - // can we / should we do we something for UTD annotation?? - Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - } - } - } EventType.REDACTION -> { val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } ?: return From 3b9faa5f31f983a8204c4e0ba53e3f7aee63c22a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 21 Dec 2022 15:17:35 +0100 Subject: [PATCH 057/237] Render specific message on decryption error --- .../src/main/res/values/strings.xml | 1 + ...yptedEventRelationsAggregationProcessor.kt | 7 +++++- .../factory/PollItemViewStateFactory.kt | 22 +++++++++++++++---- .../timeline/item/MessageInformationData.kt | 3 ++- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index f1c2eb2c6d..d884938170 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3194,6 +3194,7 @@ Closed poll Results are only revealed when you end the poll Ended the poll. + Due to decryption errors, some votes may not be counted Active polls There are no active polls in this room Past polls diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt index 7e9a0a9499..bae95f1c15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt @@ -75,7 +75,12 @@ internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( RelationType.REFERENCE -> { // can we / should we do we something for UTD reference?? Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - encryptedReferenceAggregationProcessor.handle(realm, event, isLocalEcho, encryptedEventContent.relatesTo.eventId) + encryptedReferenceAggregationProcessor.handle( + realm = realm, + event = event, + isLocalEcho = isLocalEcho, + relatedEventId = encryptedEventContent.relatesTo.eventId + ) } RelationType.ANNOTATION -> { // can we / should we do we something for UTD annotation?? diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index db11dff007..64f529fb59 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -31,11 +31,11 @@ class PollItemViewStateFactory @Inject constructor( private val stringProvider: StringProvider, ) { + // TODO update unit tests fun create( pollContent: MessagePollContent, informationData: MessageInformationData, ): PollViewState { - // TODO add new field in ViewState to reflect decryption error of related events val pollCreationInfo = pollContent.getBestPollCreationInfo() val question = pollCreationInfo?.question?.getBestQuestion().orEmpty() @@ -84,9 +84,14 @@ class PollItemViewStateFactory @Inject constructor( totalVotes: Int, winnerVoteCount: Int?, ): PollViewState { + val totalVotesText = if (pollResponseSummary?.hasDecryptionError.orFalse()) { + stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) + } else { + stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes) + } return PollViewState( question = question, - votesStatus = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes), + votesStatus = totalVotesText, canVote = false, optionViewStates = pollCreationInfo?.answers?.map { answer -> val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "") @@ -127,9 +132,14 @@ class PollItemViewStateFactory @Inject constructor( pollResponseSummary: PollResponseData?, totalVotes: Int ): PollViewState { + val totalVotesText = if (pollResponseSummary?.hasDecryptionError.orFalse()) { + stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) + } else { + stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes) + } return PollViewState( question = question, - votesStatus = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes), + votesStatus = totalVotesText, canVote = true, optionViewStates = pollCreationInfo?.answers?.map { answer -> val isMyVote = pollResponseSummary?.myVote == answer.id @@ -145,7 +155,11 @@ class PollItemViewStateFactory @Inject constructor( ) } - private fun createReadyPollViewState(question: String, pollCreationInfo: PollCreationInfo?, totalVotes: Int): PollViewState { + private fun createReadyPollViewState( + question: String, + pollCreationInfo: PollCreationInfo?, + totalVotes: Int + ): PollViewState { val totalVotesText = if (totalVotes == 0) { stringProvider.getString(R.string.poll_no_votes_cast) } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 757246d4e4..4bdfb948ce 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -90,7 +90,8 @@ data class PollResponseData( val votes: Map?, val totalVotes: Int = 0, val winnerVoteCount: Int = 0, - val isClosed: Boolean = false + val isClosed: Boolean = false, + val hasDecryptionError: Boolean = false, ) : Parcelable { fun getVoteSummaryOfAnOption(optionId: String) = votes?.get(optionId) From a29d4399a5b0945cff50c7a53b8ad1210b39a3bd Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 21 Dec 2022 16:34:54 +0100 Subject: [PATCH 058/237] Removing encrypted related id when receiving decrypted event --- .../poll/DefaultPollAggregationProcessor.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt index 301996ebab..5b25238677 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -45,6 +45,7 @@ import org.matrix.android.sdk.internal.session.room.relation.poll.FetchPollRespo import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject +// TODO update unit tests internal class DefaultPollAggregationProcessor @Inject constructor( private val taskExecutor: TaskExecutor, private val fetchPollResponseEventsTask: FetchPollResponseEventsTask, @@ -155,7 +156,7 @@ internal class DefaultPollAggregationProcessor @Inject constructor( ) aggregatedPollSummaryEntity.aggregatedContent = ContentMapper.map(newSumModel.toContent()) - // TODO check also if eventId is part of UTD list of eventIds, if so remove it + event.eventId?.let { removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity, it) } return true } @@ -182,12 +183,12 @@ internal class DefaultPollAggregationProcessor @Inject constructor( aggregatedPollSummaryEntity.sourceEvents.add(event.eventId) } + event.eventId?.let { removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity, it) } + if (!isLocalEcho) { ensurePollIsFullyAggregated(roomId, pollEventId) } - // TODO check also if eventId is part of UTD list of eventIds, if so remove it - return true } @@ -230,4 +231,10 @@ internal class DefaultPollAggregationProcessor @Inject constructor( fetchPollResponseEventsTask.execute(params) } } + + private fun removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity: PollResponseAggregatedSummaryEntity, eventId: String) { + if(aggregatedPollSummaryEntity.encryptedRelatedEventIds.contains(eventId)) { + aggregatedPollSummaryEntity.encryptedRelatedEventIds.remove(eventId) + } + } } From 7e1016da7e42f5dff6c4dca955ecc945a66d3d6d Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 21 Dec 2022 16:35:27 +0100 Subject: [PATCH 059/237] Changing where we insert UnableToDecryptEventEntity in DB --- .../sdk/internal/crypto/CryptoModule.kt | 5 ++ .../sdk/internal/crypto/EventDecryptor.kt | 14 +++++- .../CreateUnableToDecryptEventEntityTask.kt | 46 +++++++++++++++++++ .../database/query/EventEntityQueries.kt | 9 +--- 4 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index c69a859016..d78f4a3107 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -62,7 +62,9 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.CreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice +import org.matrix.android.sdk.internal.crypto.tasks.DefaultCreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask @@ -253,4 +255,7 @@ internal abstract class CryptoModule { @Binds abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask + + @Binds + abstract fun bindCreateUnableToDecryptEventEntityTask(task: DefaultCreateUnableToDecryptEventEntityTask): CreateUnableToDecryptEventEntityTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index c9eabeab48..524db32670 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.tasks.CreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.session.SessionScope @@ -59,7 +60,8 @@ internal class EventDecryptor @Inject constructor( private val sendToDeviceTask: SendToDeviceTask, private val deviceListManager: DeviceListManager, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, - private val cryptoStore: IMXCryptoStore + private val cryptoStore: IMXCryptoStore, + private val createUnableToDecryptEventEntityTask: CreateUnableToDecryptEventEntityTask, ) { /** @@ -136,6 +138,7 @@ internal class EventDecryptor @Inject constructor( val eventContent = event.content if (eventContent == null) { Timber.tag(loggerTag.value).e("decryptEvent : empty event content") + createUnableToDecryptEventEntity(event.eventId) throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON) } else if (event.isRedacted()) { // we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm @@ -153,6 +156,7 @@ internal class EventDecryptor @Inject constructor( if (alg == null) { val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) Timber.tag(loggerTag.value).e("decryptEvent() : $reason") + createUnableToDecryptEventEntity(event.eventId) throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason) } else { try { @@ -171,12 +175,20 @@ internal class EventDecryptor @Inject constructor( } } } + createUnableToDecryptEventEntity(event.eventId) throw mxCryptoError } } } } + private suspend fun createUnableToDecryptEventEntity(eventId: String?) { + eventId?.let { + val params = CreateUnableToDecryptEventEntityTask.Params(eventId = it) + createUnableToDecryptEventEntityTask.execute(params) + } + } + private suspend fun markOlmSessionForUnwedging(senderId: String, senderKey: String) { wedgedMutex.withLock { val info = WedgedDeviceInfo(senderId, senderKey) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt new file mode 100644 index 0000000000..17c7ae5ccd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.tasks + +import io.realm.RealmConfiguration +import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransactionAsync +import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +/** + * This task create a dedicated entity for UTD events so that it can be processed later. + */ +internal interface CreateUnableToDecryptEventEntityTask : Task { + data class Params( + val eventId: String, + ) +} + +// TODO add unit tests +internal class DefaultCreateUnableToDecryptEventEntityTask @Inject constructor( + @SessionDatabase val realmConfiguration: RealmConfiguration, +) : CreateUnableToDecryptEventEntityTask { + + override suspend fun execute(params: CreateUnableToDecryptEventEntityTask.Params) { + val utdEventEntity = UnableToDecryptEventEntity(eventId = params.eventId) + doRealmTransactionAsync(realmConfiguration) { realm -> + realm.insert(utdEventEntity) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt index e18495c924..0f1c226044 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertEntity import org.matrix.android.sdk.internal.database.model.EventInsertType -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInsertType): EventEntity { val eventEntity = realm.where() @@ -33,17 +32,11 @@ internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInse .equalTo(EventEntityFields.ROOM_ID, roomId) .findFirst() return if (eventEntity == null) { - val isEncrypted = type == EventType.ENCRYPTED && decryptionResultJson == null - val canBeProcessed = isEncrypted.not() + val canBeProcessed = type != EventType.ENCRYPTED || decryptionResultJson != null val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = canBeProcessed).apply { this.insertType = insertType } realm.insert(insertEntity) - // TODO check with others if it is the right spot to detect UTD events - if (isEncrypted) { - val utdEventEntity = UnableToDecryptEventEntity(eventId = eventId) - realm.insert(utdEventEntity) - } // copy this event entity and return it realm.copyToRealm(this) } else { From da6b41c34d94052d8975f6debfb987b4bc27ec46 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 22 Dec 2022 13:31:43 +0100 Subject: [PATCH 060/237] Updating unit tests for PollItemViewStateFactory --- .../factory/PollItemViewStateFactory.kt | 1 - .../factory/PollItemViewStateFactoryTest.kt | 46 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index 64f529fb59..28c560e161 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -31,7 +31,6 @@ class PollItemViewStateFactory @Inject constructor( private val stringProvider: StringProvider, ) { - // TODO update unit tests fun create( pollContent: MessagePollContent, informationData: MessageInformationData, diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt index 78e544f79d..4d14458e7f 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt @@ -131,6 +131,24 @@ class PollItemViewStateFactoryTest { ) } + @Test + fun `given a sent poll state with some decryption error when poll is closed then warning message is displayed`() { + // Given + val stringProvider = FakeStringProvider() + val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) + val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasDecryptionError = true) + val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary) + + // When + val pollViewState = pollItemViewStateFactory.create( + pollContent = A_POLL_CONTENT, + informationData = closedPollInformationData, + ) + + // Then + pollViewState.votesStatus shouldBeEqualTo stringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll) + } + @Test fun `given a sent poll when undisclosed poll type is selected then poll is votable and option states are PollUndisclosed`() { val stringProvider = FakeStringProvider() @@ -193,6 +211,34 @@ class PollItemViewStateFactoryTest { ) } + @Test + fun `given a sent poll with decryption failure when my vote exists then a warning message is displayed`() { + // Given + val stringProvider = FakeStringProvider() + val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) + val votedPollData = A_POLL_RESPONSE_DATA.copy( + totalVotes = 1, + myVote = A_POLL_OPTION_IDS[0], + votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0)), + hasDecryptionError = true, + ) + val disclosedPollContent = A_POLL_CONTENT.copy( + unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy( + kind = PollType.DISCLOSED_UNSTABLE + ), + ) + val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData) + + // When + val pollViewState = pollItemViewStateFactory.create( + pollContent = disclosedPollContent, + informationData = votedInformationData, + ) + + // Then + pollViewState.votesStatus shouldBeEqualTo stringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll) + } + @Test fun `given a sent poll when poll type is disclosed then poll is votable and option view states are PollReady`() { val stringProvider = FakeStringProvider() From fdc28c0383fd61c91770e6fb2623d662ad16e439 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 09:40:58 +0100 Subject: [PATCH 061/237] Add encrypted event id only if not already in the list --- .../aggregation/utd/EncryptedReferenceAggregationProcessor.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt index fdd3bc80e3..8987e837bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt @@ -43,7 +43,9 @@ class EncryptedReferenceAggregationProcessor @Inject constructor() { ) { event.eventId?.let { eventId -> val existingRelatedPoll = getPollSummaryWithEventId(realm, relatedEventId) - existingRelatedPoll?.encryptedRelatedEventIds?.add(eventId) + if (eventId !in existingRelatedPoll?.encryptedRelatedEventIds.orEmpty()) { + existingRelatedPoll?.encryptedRelatedEventIds?.add(eventId) + } } } From eb4de37603e345b6a9d403163a216cfde7455c62 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 09:55:32 +0100 Subject: [PATCH 062/237] Updating unit tests for poll aggregation processor --- .../poll/DefaultPollAggregationProcessor.kt | 1 - .../DefaultPollAggregationProcessorTest.kt | 54 ++++++++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt index 5b25238677..e6815eb0ca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -45,7 +45,6 @@ import org.matrix.android.sdk.internal.session.room.relation.poll.FetchPollRespo import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject -// TODO update unit tests internal class DefaultPollAggregationProcessor @Inject constructor( private val taskExecutor: TaskExecutor, private val fetchPollResponseEventsTask: FetchPollResponseEventsTask, diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt index 0888d82907..766e51a8e5 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt @@ -25,6 +25,8 @@ import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue +import org.amshove.kluent.shouldContain +import org.amshove.kluent.shouldNotContain import org.junit.Before import org.junit.Test import org.matrix.android.sdk.api.session.Session @@ -105,6 +107,24 @@ class DefaultPollAggregationProcessorTest { pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT).shouldBeTrue() } + @Test + fun `given a poll response event with a reference, when processing, then event id is removed from encrypted events list`() { + // Given + val anotherEventId = "other-event-id" + val pollResponseAggregatedSummaryEntity = PollResponseAggregatedSummaryEntity( + encryptedRelatedEventIds = RealmList(AN_EVENT_ID, anotherEventId) + ) + every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns pollResponseAggregatedSummaryEntity + + // When + val result = pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT) + + // Then + result.shouldBeTrue() + pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldNotContain(AN_EVENT_ID) + pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldContain(anotherEventId) + } + @Test fun `given a poll response event after poll is closed, when processing, then is ignored and returns false`() { every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity().apply { @@ -132,12 +152,33 @@ class DefaultPollAggregationProcessorTest { // Given every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity() every { fakeTaskExecutor.instance.executorScope } returns this - - // When val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, true) + // When + val result = pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT) + // Then - pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue() + result.shouldBeTrue() + } + + @Test + fun `given a poll end event, when processing, then event id is removed from encrypted events list`() = runTest { + // Given + val anotherEventId = "other-event-id" + val pollResponseAggregatedSummaryEntity = PollResponseAggregatedSummaryEntity( + encryptedRelatedEventIds = RealmList(AN_EVENT_ID, anotherEventId) + ) + every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns pollResponseAggregatedSummaryEntity + every { fakeTaskExecutor.instance.executorScope } returns this + val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, true) + + // When + val result = pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT) + + // Then + result.shouldBeTrue() + pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldNotContain(AN_EVENT_ID) + pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldContain(anotherEventId) } @Test @@ -145,12 +186,13 @@ class DefaultPollAggregationProcessorTest { // Given every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity() every { fakeTaskExecutor.instance.executorScope } returns this - - // When val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, false) + // When + val result = pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT) + // Then - pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue() + result.shouldBeTrue() } @Test From 1bd11775e92f454bf6d8e8cc2ac45fb4b9954202 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 10:33:25 +0100 Subject: [PATCH 063/237] Adding unit tests for EncryptedReferenceAggregationProcessor --- .../poll/PollAggregationProcessor.kt | 2 +- .../EncryptedReferenceAggregationProcessor.kt | 16 +- ...ryptedReferenceAggregationProcessorTest.kt | 138 ++++++++++++++++++ .../android/sdk/test/fakes/FakeRealm.kt | 8 + 4 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessorTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt index 848643b435..33a69b720a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper -interface PollAggregationProcessor { +internal interface PollAggregationProcessor { /** * Poll start events don't need to be processed by the aggregator. * This function will only handle if the poll is edited and will update the poll summary entity. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt index 8987e837bc..43631fcc3e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,18 +22,20 @@ import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSumm import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields import javax.inject.Inject -class EncryptedReferenceAggregationProcessor @Inject constructor() { +internal class EncryptedReferenceAggregationProcessor @Inject constructor() { - // TODO add unit tests fun handle( realm: Realm, event: Event, isLocalEcho: Boolean, relatedEventId: String? - ) { - if (isLocalEcho || relatedEventId.isNullOrEmpty()) return - - handlePollReference(realm = realm, event = event, relatedEventId = relatedEventId) + ): Boolean { + return if (isLocalEcho || relatedEventId.isNullOrEmpty()) { + false + } else { + handlePollReference(realm = realm, event = event, relatedEventId = relatedEventId) + true + } } private fun handlePollReference( diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessorTest.kt new file mode 100644 index 0000000000..2998b9bff0 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessorTest.kt @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.aggregation.utd + +import io.mockk.every +import io.mockk.mockk +import io.realm.RealmList +import org.amshove.kluent.shouldBeFalse +import org.amshove.kluent.shouldBeTrue +import org.amshove.kluent.shouldContain +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.givenContainsValue +import org.matrix.android.sdk.test.fakes.givenFindFirst + +internal class EncryptedReferenceAggregationProcessorTest { + + private val fakeRealm = FakeRealm() + + private val encryptedReferenceAggregationProcessor = EncryptedReferenceAggregationProcessor() + + @Test + fun `given local echo when process then result is false`() { + // Given + val anEvent = mockk() + val isLocalEcho = true + val relatedEventId = "event-id" + + // When + val result = encryptedReferenceAggregationProcessor.handle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = isLocalEcho, + relatedEventId = relatedEventId, + ) + + // Then + result.shouldBeFalse() + } + + @Test + fun `given invalid event id when process then result is false`() { + // Given + val anEvent = mockk() + val isLocalEcho = false + + // When + val result1 = encryptedReferenceAggregationProcessor.handle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = isLocalEcho, + relatedEventId = null, + ) + val result2 = encryptedReferenceAggregationProcessor.handle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = isLocalEcho, + relatedEventId = "", + ) + + // Then + result1.shouldBeFalse() + result2.shouldBeFalse() + } + + @Test + fun `given related event id of an existing poll when process then result is true and event id is stored in poll summary`() { + // Given + val anEventId = "event-id" + val anEvent = givenAnEvent(anEventId) + val isLocalEcho = false + val relatedEventId = "related-event-id" + val pollResponseAggregatedSummaryEntity = PollResponseAggregatedSummaryEntity( + encryptedRelatedEventIds = RealmList(), + ) + fakeRealm.givenWhere() + .givenContainsValue(PollResponseAggregatedSummaryEntityFields.SOURCE_EVENTS.`$`, relatedEventId) + .givenFindFirst(pollResponseAggregatedSummaryEntity) + + // When + val result = encryptedReferenceAggregationProcessor.handle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = isLocalEcho, + relatedEventId = relatedEventId, + ) + + // Then + result.shouldBeTrue() + pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldContain(anEventId) + } + + @Test + fun `given related event id but no existing related poll when process then result is true and event id is not stored`() { + // Given + val anEventId = "event-id" + val anEvent = givenAnEvent(anEventId) + val isLocalEcho = false + val relatedEventId = "related-event-id" + fakeRealm.givenWhere() + .givenContainsValue(PollResponseAggregatedSummaryEntityFields.SOURCE_EVENTS.`$`, relatedEventId) + .givenFindFirst(null) + + // When + val result = encryptedReferenceAggregationProcessor.handle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = isLocalEcho, + relatedEventId = relatedEventId, + ) + + // Then + result.shouldBeTrue() + } + + private fun givenAnEvent(eventId: String): Event { + return mockk().also { + every { it.eventId } returns eventId + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt index ba124a86aa..49d64c1835 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt @@ -117,6 +117,14 @@ inline fun RealmQuery.givenIn( return this } +inline fun RealmQuery.givenContainsValue( + fieldName: String, + value: String, +): RealmQuery { + every { containsValue(fieldName, value) } returns this + return this +} + /** * Should be called on a mocked RealmObject and not on a real RealmObject so that the underlying final method is mocked. */ From a04c60a85bee8c52e6dc1c94ce5be13283863db0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 11:57:50 +0100 Subject: [PATCH 064/237] Adding unit tests for EncryptedEventRelationsAggregationProcessor --- .../UnableToDecryptEventLiveProcessor.kt | 8 +- ...yptedEventRelationsAggregationProcessor.kt | 49 ++-- ...dEventRelationsAggregationProcessorTest.kt | 209 ++++++++++++++++++ ...eEncryptedReferenceAggregationProcessor.kt | 42 ++++ 4 files changed, 284 insertions(+), 24 deletions(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/room/aggregation/utd/FakeEncryptedReferenceAggregationProcessor.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt index c5fa6dc88e..8ff8cec6ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt @@ -21,7 +21,13 @@ import org.matrix.android.sdk.api.session.events.model.Event internal interface UnableToDecryptEventLiveProcessor { - fun process(realm: Realm, event: Event) + /** + * Process the given event. + * @param realm a realm instance + * @param event the event to be processed + * @return true if it has been processed, false if it was ignored. + */ + fun process(realm: Realm, event: Event): Boolean /** * Called after transaction. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt index bae95f1c15..1d0270d8e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt @@ -32,28 +32,27 @@ internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, ) : UnableToDecryptEventLiveProcessor { - // TODO add unit tests - override fun process(realm: Realm, event: Event) { + override fun process(realm: Realm, event: Event): Boolean { val roomId = event.roomId - if (roomId == null) { + return if (roomId == null) { Timber.w("Event has no room id ${event.eventId}") - return - } + false + } else { + val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") - val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") - - when (event.getClearType()) { - EventType.ENCRYPTED -> { - val encryptedEventContent = event.content.toModel() - processEncryptedContent( - encryptedEventContent = encryptedEventContent, - realm = realm, - event = event, - roomId = roomId, - isLocalEcho = isLocalEcho, - ) + return when (event.getClearType()) { + EventType.ENCRYPTED -> { + val encryptedEventContent = event.content.toModel() + processEncryptedContent( + encryptedEventContent = encryptedEventContent, + realm = realm, + event = event, + roomId = roomId, + isLocalEcho = isLocalEcho, + ) + } + else -> false } - else -> Unit } } @@ -63,30 +62,34 @@ internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( event: Event, roomId: String, isLocalEcho: Boolean, - ) { - when (encryptedEventContent?.relatesTo?.type) { + ): Boolean { + return when (encryptedEventContent?.relatesTo?.type) { RelationType.REPLACE -> { Timber.w("## UTD replace in room $roomId for event ${event.eventId}") + false } RelationType.RESPONSE -> { // can we / should we do we something for UTD response?? Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + false } RelationType.REFERENCE -> { // can we / should we do we something for UTD reference?? Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - encryptedReferenceAggregationProcessor.handle( + val result = encryptedReferenceAggregationProcessor.handle( realm = realm, event = event, isLocalEcho = isLocalEcho, - relatedEventId = encryptedEventContent.relatesTo.eventId + relatedEventId = encryptedEventContent.relatesTo.eventId, ) + result } RelationType.ANNOTATION -> { // can we / should we do we something for UTD annotation?? Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + false } - else -> Unit + else -> false } } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt new file mode 100644 index 0000000000..9d68ed1a77 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room + +import io.mockk.every +import io.mockk.mockk +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeFalse +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.internal.session.room.aggregation.utd.FakeEncryptedReferenceAggregationProcessor + +class EncryptedEventRelationsAggregationProcessorTest { + + private val fakeEncryptedReferenceAggregationProcessor = FakeEncryptedReferenceAggregationProcessor() + private val fakeRealm = FakeRealm() + + private val encryptedEventRelationsAggregationProcessor = EncryptedEventRelationsAggregationProcessor( + encryptedReferenceAggregationProcessor = fakeEncryptedReferenceAggregationProcessor.instance, + ) + + @Test + fun `given no room Id when process then result is false`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = null, + eventType = EventType.ENCRYPTED, + ) + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result.shouldBeFalse() + } + + @Test + fun `given an encrypted reference event when process then reference is processed`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = "room-id", + eventType = EventType.ENCRYPTED, + ) + val relatedEventId = "related-event-id" + val encryptedEventContent = givenEncryptedEventContent( + relationType = RelationType.REFERENCE, + relatedEventId = relatedEventId, + ) + every { anEvent.content } returns encryptedEventContent.toContent() + val resultOfReferenceProcess = false + fakeEncryptedReferenceAggregationProcessor.givenHandleReturns(resultOfReferenceProcess) + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result shouldBeEqualTo resultOfReferenceProcess + fakeEncryptedReferenceAggregationProcessor.verifyHandle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = false, + relatedEventId = relatedEventId, + ) + } + + @Test + fun `given an encrypted replace event when process then result is false`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = "room-id", + eventType = EventType.ENCRYPTED, + ) + val relatedEventId = "related-event-id" + val encryptedEventContent = givenEncryptedEventContent( + relationType = RelationType.REPLACE, + relatedEventId = relatedEventId, + ) + every { anEvent.content } returns encryptedEventContent.toContent() + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result.shouldBeFalse() + } + + @Test + fun `given an encrypted response event when process then result is false`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = "room-id", + eventType = EventType.ENCRYPTED, + ) + val relatedEventId = "related-event-id" + val encryptedEventContent = givenEncryptedEventContent( + relationType = RelationType.RESPONSE, + relatedEventId = relatedEventId, + ) + every { anEvent.content } returns encryptedEventContent.toContent() + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result.shouldBeFalse() + } + + @Test + fun `given an encrypted annotation event when process then result is false`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = "room-id", + eventType = EventType.ENCRYPTED, + ) + val relatedEventId = "related-event-id" + val encryptedEventContent = givenEncryptedEventContent( + relationType = RelationType.ANNOTATION, + relatedEventId = relatedEventId, + ) + every { anEvent.content } returns encryptedEventContent.toContent() + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result.shouldBeFalse() + } + + @Test + fun `given a non encrypted event when process then result is false`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = "room-id", + eventType = EventType.MESSAGE, + ) + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result.shouldBeFalse() + } + + private fun givenAnEvent( + eventId: String, + roomId: String?, + eventType: String, + ): Event { + return mockk().also { + every { it.eventId } returns eventId + every { it.roomId } returns roomId + every { it.getClearType() } returns eventType + } + } + + private fun givenEncryptedEventContent(relationType: String, relatedEventId: String): EncryptedEventContent { + val relationContent = RelationDefaultContent( + eventId = relatedEventId, + type = relationType, + ) + return EncryptedEventContent( + relatesTo = relationContent, + ) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/room/aggregation/utd/FakeEncryptedReferenceAggregationProcessor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/room/aggregation/utd/FakeEncryptedReferenceAggregationProcessor.kt new file mode 100644 index 0000000000..7661095fe3 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/room/aggregation/utd/FakeEncryptedReferenceAggregationProcessor.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes.internal.session.room.aggregation.utd + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor + +internal class FakeEncryptedReferenceAggregationProcessor { + + val instance: EncryptedReferenceAggregationProcessor = mockk() + + fun givenHandleReturns(result: Boolean) { + every { instance.handle(any(), any(), any(), any()) } returns result + } + + fun verifyHandle( + realm: Realm, + event: Event, + isLocalEcho: Boolean, + relatedEventId: String?, + ) { + verify { instance.handle(realm, event, isLocalEcho, relatedEventId) } + } +} From e9f59d85b46a2fc8b84362859b16bab18c0ca756 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 14:38:21 +0100 Subject: [PATCH 065/237] Adding unit tests for DefaultCreateUnableToDecryptEventEntityTask --- .../CreateUnableToDecryptEventEntityTask.kt | 1 - ...reateUnableToDecryptEventEntityTaskTest.kt | 65 +++++++++++++++++++ .../android/sdk/test/fakes/FakeRealm.kt | 8 +++ .../sdk/test/fakes/FakeRealmConfiguration.kt | 6 ++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/tasks/DefaultCreateUnableToDecryptEventEntityTaskTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt index 17c7ae5ccd..c502e19e35 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt @@ -32,7 +32,6 @@ internal interface CreateUnableToDecryptEventEntityTask : Task { + it is UnableToDecryptEventEntity && it.eventId == anEventId + }) + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt index 49d64c1835..1f9bc2a976 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt @@ -23,6 +23,7 @@ import io.mockk.mockk import io.mockk.runs import io.mockk.verify import io.realm.Realm +import io.realm.Realm.Transaction import io.realm.RealmModel import io.realm.RealmObject import io.realm.RealmQuery @@ -42,6 +43,13 @@ internal class FakeRealm { inline fun verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { verify { instance.insertOrUpdate(verification()) } } + + fun givenExecuteTransactionAsync() { + every { instance.executeTransactionAsync(any()) } answers { + firstArg().execute(instance) + mockk() + } + } } inline fun RealmQuery.givenFindFirst( diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt index 9ad7032262..3a69515140 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.test.fakes import io.mockk.coEvery +import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.realm.Realm @@ -36,4 +37,9 @@ internal class FakeRealmConfiguration { secondArg<(Realm) -> T>().invoke(realm) } } + + fun givenGetRealmInstance(realm: Realm) { + mockkStatic(Realm::class) + every { Realm.getInstance(instance) } returns realm + } } From b001bc382f5d8dd96aadeec191170c6e14eea3e6 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 15:29:42 +0100 Subject: [PATCH 066/237] Fixing code style issue --- .../room/aggregation/poll/DefaultPollAggregationProcessor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt index e6815eb0ca..2ff43d6812 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -232,7 +232,7 @@ internal class DefaultPollAggregationProcessor @Inject constructor( } private fun removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity: PollResponseAggregatedSummaryEntity, eventId: String) { - if(aggregatedPollSummaryEntity.encryptedRelatedEventIds.contains(eventId)) { + if (aggregatedPollSummaryEntity.encryptedRelatedEventIds.contains(eventId)) { aggregatedPollSummaryEntity.encryptedRelatedEventIds.remove(eventId) } } From fd58875c31c0c42f4d5c4136c243456ed58b46f1 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 3 Jan 2023 10:43:34 +0100 Subject: [PATCH 067/237] Database migration --- .../database/RealmSessionStoreMigration.kt | 4 +- .../database/migration/MigrateSessionTo048.kt | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index ba102a7a48..2b7e9a04a1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -64,6 +64,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -72,7 +73,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 47L, + schemaVersion = 48L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -129,5 +130,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 45) MigrateSessionTo045(realm).perform() if (oldVersion < 46) MigrateSessionTo046(realm).perform() if (oldVersion < 47) MigrateSessionTo047(realm).perform() + if (oldVersion < 48) MigrateSessionTo048(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt new file mode 100644 index 0000000000..f9bd8ae7aa --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +/** + * Adding a new field in poll summary to keep track of non decrypted related events. + * Adding a new entity UnableToDecryptEventEntity. + */ +internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 47) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("PollResponseAggregatedSummaryEntity") + ?.addRealmListField(PollResponseAggregatedSummaryEntityFields.ENCRYPTED_RELATED_EVENT_IDS.`$`, String::class.java) + + realm.schema.create("UnableToDecryptEventEntity") + ?.addField(UnableToDecryptEventEntityFields.EVENT_ID, String::class.java) + ?.setRequired(UnableToDecryptEventEntityFields.EVENT_ID, true) + } +} From 8b051c5b86cf6dfd12b41dd95b8665af8b2e5eb9 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 6 Jan 2023 15:19:33 +0100 Subject: [PATCH 068/237] Replace usage of new UnableToDecryptEventEntity by usage of existing EventInsertEntity --- .../sdk/internal/crypto/CryptoModule.kt | 5 - .../sdk/internal/crypto/EventDecryptor.kt | 12 - .../CreateUnableToDecryptEventEntityTask.kt | 45 ---- .../database/EventInsertLiveObserver.kt | 16 +- .../UnableToDecryptEventLiveObserver.kt | 83 ------- .../database/migration/MigrateSessionTo048.kt | 8 +- .../database/model/EventInsertEntity.kt | 2 +- .../database/model/SessionRealmModule.kt | 1 - .../database/query/EventEntityQueries.kt | 8 +- .../sdk/internal/session/SessionModule.kt | 10 - .../UnableToDecryptEventLiveProcessor.kt | 39 ---- ...yptedEventRelationsAggregationProcessor.kt | 95 -------- .../EventRelationsAggregationProcessor.kt | 43 ++++ ...reateUnableToDecryptEventEntityTaskTest.kt | 65 ------ ...dEventRelationsAggregationProcessorTest.kt | 209 ------------------ .../EventRelationsAggregationProcessorTest.kt | 132 +++++++++++ .../android/sdk/test/fakes/FakeRealm.kt | 8 - .../sdk/test/fakes/FakeRealmConfiguration.kt | 6 - .../fakes/internal/FakeEventEditValidator.kt} | 18 +- .../FakeLiveLocationAggregationProcessor.kt | 25 +++ .../internal/FakePollAggregationProcessor.kt | 25 +++ 21 files changed, 252 insertions(+), 603 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt delete mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/tasks/DefaultCreateUnableToDecryptEventEntityTaskTest.kt delete mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessorTest.kt rename matrix-sdk-android/src/{main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt => test/java/org/matrix/android/sdk/test/fakes/internal/FakeEventEditValidator.kt} (57%) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeLiveLocationAggregationProcessor.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePollAggregationProcessor.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index d78f4a3107..c69a859016 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -62,9 +62,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask -import org.matrix.android.sdk.internal.crypto.tasks.CreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice -import org.matrix.android.sdk.internal.crypto.tasks.DefaultCreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask @@ -255,7 +253,4 @@ internal abstract class CryptoModule { @Binds abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask - - @Binds - abstract fun bindCreateUnableToDecryptEventEntityTask(task: DefaultCreateUnableToDecryptEventEntityTask): CreateUnableToDecryptEventEntityTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index 524db32670..03672ae81c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -38,7 +38,6 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.tasks.CreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.session.SessionScope @@ -61,7 +60,6 @@ internal class EventDecryptor @Inject constructor( private val deviceListManager: DeviceListManager, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val cryptoStore: IMXCryptoStore, - private val createUnableToDecryptEventEntityTask: CreateUnableToDecryptEventEntityTask, ) { /** @@ -138,7 +136,6 @@ internal class EventDecryptor @Inject constructor( val eventContent = event.content if (eventContent == null) { Timber.tag(loggerTag.value).e("decryptEvent : empty event content") - createUnableToDecryptEventEntity(event.eventId) throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON) } else if (event.isRedacted()) { // we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm @@ -156,7 +153,6 @@ internal class EventDecryptor @Inject constructor( if (alg == null) { val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) Timber.tag(loggerTag.value).e("decryptEvent() : $reason") - createUnableToDecryptEventEntity(event.eventId) throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason) } else { try { @@ -175,20 +171,12 @@ internal class EventDecryptor @Inject constructor( } } } - createUnableToDecryptEventEntity(event.eventId) throw mxCryptoError } } } } - private suspend fun createUnableToDecryptEventEntity(eventId: String?) { - eventId?.let { - val params = CreateUnableToDecryptEventEntityTask.Params(eventId = it) - createUnableToDecryptEventEntityTask.execute(params) - } - } - private suspend fun markOlmSessionForUnwedging(senderId: String, senderKey: String) { wedgedMutex.withLock { val info = WedgedDeviceInfo(senderId, senderKey) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt deleted file mode 100644 index c502e19e35..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.tasks - -import io.realm.RealmConfiguration -import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransactionAsync -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.task.Task -import javax.inject.Inject - -/** - * This task create a dedicated entity for UTD events so that it can be processed later. - */ -internal interface CreateUnableToDecryptEventEntityTask : Task { - data class Params( - val eventId: String, - ) -} - -internal class DefaultCreateUnableToDecryptEventEntityTask @Inject constructor( - @SessionDatabase val realmConfiguration: RealmConfiguration, -) : CreateUnableToDecryptEventEntityTask { - - override suspend fun execute(params: CreateUnableToDecryptEventEntityTask.Params) { - val utdEventEntity = UnableToDecryptEventEntity(eventId = params.eventId) - doRealmTransactionAsync(realmConfiguration) { realm -> - realm.insert(utdEventEntity) - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index d1ca4f48a6..84fb14c914 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -22,6 +22,7 @@ import io.realm.RealmResults import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertEntity @@ -34,7 +35,7 @@ import javax.inject.Inject internal class EventInsertLiveObserver @Inject constructor( @SessionDatabase realmConfiguration: RealmConfiguration, - private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor> + private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>, ) : RealmLiveEntityObserver(realmConfiguration) { @@ -51,6 +52,7 @@ internal class EventInsertLiveObserver @Inject constructor( return@withLock } val idsToDeleteAfterProcess = ArrayList() + val idsOfEncryptedEvents = ArrayList() val filteredEvents = ArrayList(results.size) Timber.v("EventInsertEntity updated with ${results.size} results in db") results.forEach { @@ -64,7 +66,11 @@ internal class EventInsertLiveObserver @Inject constructor( } filteredEvents.add(copiedEvent) } - idsToDeleteAfterProcess.add(it.eventId) + if (it.eventType == EventType.ENCRYPTED) { + idsOfEncryptedEvents.add(it.eventId) + } else { + idsToDeleteAfterProcess.add(it.eventId) + } } awaitTransaction(realmConfiguration) { realm -> Timber.v("##Transaction: There are ${filteredEvents.size} events to process ") @@ -86,6 +92,12 @@ internal class EventInsertLiveObserver @Inject constructor( .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray()) .findAll() .deleteAllFromRealm() + + // make the encrypted events not processable: they will be processed again after decryption + realm.where(EventInsertEntity::class.java) + .`in`(EventInsertEntityFields.EVENT_ID, idsOfEncryptedEvents.toTypedArray()) + .findAll() + .forEach { it.canBeProcessed = false } } processors.forEach { it.onPostProcess() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt deleted file mode 100644 index 7835a16ad1..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.database - -import com.zhuinden.monarchy.Monarchy -import io.realm.RealmConfiguration -import io.realm.RealmResults -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import org.matrix.android.sdk.internal.database.mapper.asDomain -import org.matrix.android.sdk.internal.database.model.EventEntity -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntityFields -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.UnableToDecryptEventLiveProcessor -import timber.log.Timber -import javax.inject.Inject - -internal class UnableToDecryptEventLiveObserver @Inject constructor( - @SessionDatabase realmConfiguration: RealmConfiguration, - private val processors: Set<@JvmSuppressWildcards UnableToDecryptEventLiveProcessor> -) : - RealmLiveEntityObserver(realmConfiguration) { - - private val lock = Mutex() - - override val query = Monarchy.Query { - it.where(UnableToDecryptEventEntity::class.java) - } - - override fun onChange(results: RealmResults) { - observerScope.launch { - lock.withLock { - if (!results.isLoaded || results.isEmpty()) { - return@withLock - } - val copiedEvents = ArrayList(results.size) - Timber.v("UnableToDecryptEventEntity updated with ${results.size} results in db") - results.forEach { - // don't use copy from realm over there - val copiedEvent = UnableToDecryptEventEntity(eventId = it.eventId) - copiedEvents.add(copiedEvent) - } - awaitTransaction(realmConfiguration) { realm -> - Timber.v("##Transaction: There are ${copiedEvents.size} events to process ") - copiedEvents.forEach { utdEvent -> - val eventId = utdEvent.eventId - val event = EventEntity.where(realm, eventId).findFirst() - if (event == null) { - Timber.v("Event $eventId not found") - return@forEach - } - val domainEvent = event.asDomain() - processors.forEach { - it.process(realm, domainEvent) - } - } - realm.where(UnableToDecryptEventEntity::class.java) - .`in`(UnableToDecryptEventEntityFields.EVENT_ID, copiedEvents.map { it.eventId }.toTypedArray()) - .findAll() - .deleteAllFromRealm() - } - processors.forEach { it.onPostProcess() } - } - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt index f9bd8ae7aa..4299054c56 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt @@ -18,21 +18,15 @@ package org.matrix.android.sdk.internal.database.migration import io.realm.DynamicRealm import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator /** * Adding a new field in poll summary to keep track of non decrypted related events. - * Adding a new entity UnableToDecryptEventEntity. */ -internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 47) { +internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 48) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("PollResponseAggregatedSummaryEntity") ?.addRealmListField(PollResponseAggregatedSummaryEntityFields.ENCRYPTED_RELATED_EVENT_IDS.`$`, String::class.java) - - realm.schema.create("UnableToDecryptEventEntity") - ?.addField(UnableToDecryptEventEntityFields.EVENT_ID, String::class.java) - ?.setRequired(UnableToDecryptEventEntityFields.EVENT_ID, true) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt index eff332dc3a..054094c398 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt @@ -27,7 +27,7 @@ internal open class EventInsertEntity( var eventType: String = "", /** * This flag will be used to filter EventInsertEntity in EventInsertLiveObserver. - * Currently it's set to false when the event content is encrypted. + * Currently it's set to false after an event with encrypted content has been processed. */ var canBeProcessed: Boolean = true ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 79b0dd699c..93fe1bd1d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -73,7 +73,6 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit UserPresenceEntity::class, ThreadSummaryEntity::class, ThreadListPageEntity::class, - UnableToDecryptEventEntity::class, ] ) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt index 0f1c226044..4805c36f8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt @@ -20,7 +20,6 @@ import io.realm.Realm import io.realm.RealmList import io.realm.RealmQuery import io.realm.kotlin.where -import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertEntity @@ -32,10 +31,9 @@ internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInse .equalTo(EventEntityFields.ROOM_ID, roomId) .findFirst() return if (eventEntity == null) { - val canBeProcessed = type != EventType.ENCRYPTED || decryptionResultJson != null - val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = canBeProcessed).apply { - this.insertType = insertType - } + val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = true) + insertEntity.insertType = insertType + realm.insert(insertEntity) // copy this event entity and return it realm.copyToRealm(this) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index d33112a922..b9f56cbc9f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -50,7 +50,6 @@ import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask import org.matrix.android.sdk.internal.database.EventInsertLiveObserver import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory -import org.matrix.android.sdk.internal.database.UnableToDecryptEventLiveObserver import org.matrix.android.sdk.internal.di.Authenticated import org.matrix.android.sdk.internal.di.CacheDirectory import org.matrix.android.sdk.internal.di.DeviceId @@ -85,7 +84,6 @@ import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService -import org.matrix.android.sdk.internal.session.room.EncryptedEventRelationsAggregationProcessor import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor @@ -348,10 +346,6 @@ internal abstract class SessionModule { @IntoSet abstract fun bindEventInsertObserver(observer: EventInsertLiveObserver): SessionLifecycleObserver - @Binds - @IntoSet - abstract fun bindUnableToDecryptEventObserver(observer: UnableToDecryptEventLiveObserver): SessionLifecycleObserver - @Binds @IntoSet abstract fun bindIntegrationManager(manager: IntegrationManager): SessionLifecycleObserver @@ -411,8 +405,4 @@ internal abstract class SessionModule { @Binds abstract fun bindPollAggregationProcessor(processor: DefaultPollAggregationProcessor): PollAggregationProcessor - - @Binds - @IntoSet - abstract fun bindEncryptedEventRelationsAggregationProcessor(processor: EncryptedEventRelationsAggregationProcessor): UnableToDecryptEventLiveProcessor } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt deleted file mode 100644 index 8ff8cec6ce..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.session - -import io.realm.Realm -import org.matrix.android.sdk.api.session.events.model.Event - -internal interface UnableToDecryptEventLiveProcessor { - - /** - * Process the given event. - * @param realm a realm instance - * @param event the event to be processed - * @return true if it has been processed, false if it was ignored. - */ - fun process(realm: Realm, event: Event): Boolean - - /** - * Called after transaction. - * Maybe you prefer to process the events outside of the realm transaction. - */ - suspend fun onPostProcess() { - // Noop by default - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt deleted file mode 100644 index 1d0270d8e4..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.session.room - -import io.realm.Realm -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.LocalEcho -import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.session.UnableToDecryptEventLiveProcessor -import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor -import timber.log.Timber -import javax.inject.Inject - -internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( - private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, -) : UnableToDecryptEventLiveProcessor { - - override fun process(realm: Realm, event: Event): Boolean { - val roomId = event.roomId - return if (roomId == null) { - Timber.w("Event has no room id ${event.eventId}") - false - } else { - val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") - - return when (event.getClearType()) { - EventType.ENCRYPTED -> { - val encryptedEventContent = event.content.toModel() - processEncryptedContent( - encryptedEventContent = encryptedEventContent, - realm = realm, - event = event, - roomId = roomId, - isLocalEcho = isLocalEcho, - ) - } - else -> false - } - } - } - - private fun processEncryptedContent( - encryptedEventContent: EncryptedEventContent?, - realm: Realm, - event: Event, - roomId: String, - isLocalEcho: Boolean, - ): Boolean { - return when (encryptedEventContent?.relatesTo?.type) { - RelationType.REPLACE -> { - Timber.w("## UTD replace in room $roomId for event ${event.eventId}") - false - } - RelationType.RESPONSE -> { - // can we / should we do we something for UTD response?? - Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - false - } - RelationType.REFERENCE -> { - // can we / should we do we something for UTD reference?? - Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - val result = encryptedReferenceAggregationProcessor.handle( - realm = realm, - event = event, - isLocalEcho = isLocalEcho, - relatedEventId = encryptedEventContent.relatesTo.eventId, - ) - result - } - RelationType.ANNOTATION -> { - // can we / should we do we something for UTD annotation?? - Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - false - } - else -> false - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 0734b286b3..edc10bd187 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel @@ -60,6 +61,7 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor +import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -72,6 +74,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private val sessionManager: SessionManager, private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor, private val pollAggregationProcessor: PollAggregationProcessor, + private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, private val editValidator: EventEditValidator, private val clock: Clock, ) : EventInsertLiveProcessor { @@ -139,6 +142,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor( Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") handleReaction(realm, event, roomId, isLocalEcho) } + EventType.ENCRYPTED -> { + val encryptedEventContent = event.content.toModel() + processEncryptedContent( + encryptedEventContent = encryptedEventContent, + realm = realm, + event = event, + roomId = roomId, + isLocalEcho = isLocalEcho, + ) + } EventType.MESSAGE -> { if (event.unsignedData?.relations?.annotations != null) { Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}") @@ -223,6 +236,36 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } + private fun processEncryptedContent( + encryptedEventContent: EncryptedEventContent?, + realm: Realm, + event: Event, + roomId: String, + isLocalEcho: Boolean, + ) { + when (encryptedEventContent?.relatesTo?.type) { + RelationType.REPLACE -> { + Timber.w("## UTD replace in room $roomId for event ${event.eventId}") + } + RelationType.RESPONSE -> { + Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + } + RelationType.REFERENCE -> { + Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + encryptedReferenceAggregationProcessor.handle( + realm = realm, + event = event, + isLocalEcho = isLocalEcho, + relatedEventId = encryptedEventContent.relatesTo.eventId, + ) + } + RelationType.ANNOTATION -> { + Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + } + else -> Unit + } + } + // OPT OUT serer aggregation until API mature enough private val SHOULD_HANDLE_SERVER_AGREGGATION = false // should be true to work with e2e diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/tasks/DefaultCreateUnableToDecryptEventEntityTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/tasks/DefaultCreateUnableToDecryptEventEntityTaskTest.kt deleted file mode 100644 index 85032d809c..0000000000 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/tasks/DefaultCreateUnableToDecryptEventEntityTaskTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.tasks - -import io.mockk.unmockkAll -import io.mockk.verify -import io.realm.RealmModel -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runTest -import org.junit.After -import org.junit.Test -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity -import org.matrix.android.sdk.test.fakes.FakeRealm -import org.matrix.android.sdk.test.fakes.FakeRealmConfiguration - -@OptIn(ExperimentalCoroutinesApi::class) -internal class DefaultCreateUnableToDecryptEventEntityTaskTest { - - private val fakeRealmConfiguration = FakeRealmConfiguration() - - private val defaultCreateUnableToDecryptEventEntityTask = DefaultCreateUnableToDecryptEventEntityTask( - realmConfiguration = fakeRealmConfiguration.instance, - ) - - @After - fun tearDown() { - unmockkAll() - } - - @Test - fun `given an event id when execute then insert entity into database`() = runTest { - // Given - val anEventId = "event-id" - val params = CreateUnableToDecryptEventEntityTask.Params( - eventId = anEventId, - ) - val fakeRealm = FakeRealm() - fakeRealm.givenExecuteTransactionAsync() - fakeRealmConfiguration.givenGetRealmInstance(fakeRealm.instance) - - // When - defaultCreateUnableToDecryptEventEntityTask.execute(params) - - // Then - verify { - fakeRealm.instance.insert(match { - it is UnableToDecryptEventEntity && it.eventId == anEventId - }) - } - } -} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt deleted file mode 100644 index 9d68ed1a77..0000000000 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.session.room - -import io.mockk.every -import io.mockk.mockk -import org.amshove.kluent.shouldBeEqualTo -import org.amshove.kluent.shouldBeFalse -import org.junit.Test -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent -import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import org.matrix.android.sdk.test.fakes.FakeRealm -import org.matrix.android.sdk.test.fakes.internal.session.room.aggregation.utd.FakeEncryptedReferenceAggregationProcessor - -class EncryptedEventRelationsAggregationProcessorTest { - - private val fakeEncryptedReferenceAggregationProcessor = FakeEncryptedReferenceAggregationProcessor() - private val fakeRealm = FakeRealm() - - private val encryptedEventRelationsAggregationProcessor = EncryptedEventRelationsAggregationProcessor( - encryptedReferenceAggregationProcessor = fakeEncryptedReferenceAggregationProcessor.instance, - ) - - @Test - fun `given no room Id when process then result is false`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = null, - eventType = EventType.ENCRYPTED, - ) - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result.shouldBeFalse() - } - - @Test - fun `given an encrypted reference event when process then reference is processed`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = "room-id", - eventType = EventType.ENCRYPTED, - ) - val relatedEventId = "related-event-id" - val encryptedEventContent = givenEncryptedEventContent( - relationType = RelationType.REFERENCE, - relatedEventId = relatedEventId, - ) - every { anEvent.content } returns encryptedEventContent.toContent() - val resultOfReferenceProcess = false - fakeEncryptedReferenceAggregationProcessor.givenHandleReturns(resultOfReferenceProcess) - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result shouldBeEqualTo resultOfReferenceProcess - fakeEncryptedReferenceAggregationProcessor.verifyHandle( - realm = fakeRealm.instance, - event = anEvent, - isLocalEcho = false, - relatedEventId = relatedEventId, - ) - } - - @Test - fun `given an encrypted replace event when process then result is false`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = "room-id", - eventType = EventType.ENCRYPTED, - ) - val relatedEventId = "related-event-id" - val encryptedEventContent = givenEncryptedEventContent( - relationType = RelationType.REPLACE, - relatedEventId = relatedEventId, - ) - every { anEvent.content } returns encryptedEventContent.toContent() - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result.shouldBeFalse() - } - - @Test - fun `given an encrypted response event when process then result is false`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = "room-id", - eventType = EventType.ENCRYPTED, - ) - val relatedEventId = "related-event-id" - val encryptedEventContent = givenEncryptedEventContent( - relationType = RelationType.RESPONSE, - relatedEventId = relatedEventId, - ) - every { anEvent.content } returns encryptedEventContent.toContent() - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result.shouldBeFalse() - } - - @Test - fun `given an encrypted annotation event when process then result is false`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = "room-id", - eventType = EventType.ENCRYPTED, - ) - val relatedEventId = "related-event-id" - val encryptedEventContent = givenEncryptedEventContent( - relationType = RelationType.ANNOTATION, - relatedEventId = relatedEventId, - ) - every { anEvent.content } returns encryptedEventContent.toContent() - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result.shouldBeFalse() - } - - @Test - fun `given a non encrypted event when process then result is false`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = "room-id", - eventType = EventType.MESSAGE, - ) - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result.shouldBeFalse() - } - - private fun givenAnEvent( - eventId: String, - roomId: String?, - eventType: String, - ): Event { - return mockk().also { - every { it.eventId } returns eventId - every { it.roomId } returns roomId - every { it.getClearType() } returns eventType - } - } - - private fun givenEncryptedEventContent(relationType: String, relatedEventId: String): EncryptedEventContent { - val relationContent = RelationDefaultContent( - eventId = relatedEventId, - type = relationType, - ) - return EncryptedEventContent( - relatesTo = relationContent, - ) - } -} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessorTest.kt new file mode 100644 index 0000000000..ff803c4f1a --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessorTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room + +import io.mockk.every +import io.mockk.mockk +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeClock +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst +import org.matrix.android.sdk.test.fakes.internal.FakeEventEditValidator +import org.matrix.android.sdk.test.fakes.internal.FakeLiveLocationAggregationProcessor +import org.matrix.android.sdk.test.fakes.internal.FakePollAggregationProcessor +import org.matrix.android.sdk.test.fakes.internal.FakeSessionManager +import org.matrix.android.sdk.test.fakes.internal.session.room.aggregation.utd.FakeEncryptedReferenceAggregationProcessor + +private const val A_ROOM_ID = "room-id" +private const val AN_EVENT_ID = "event-id" + +internal class EventRelationsAggregationProcessorTest { + + private val fakeStateEventDataSource = FakeStateEventDataSource() + private val fakeSessionManager = FakeSessionManager() + private val fakeLiveLocationAggregationProcessor = FakeLiveLocationAggregationProcessor() + private val fakePollAggregationProcessor = FakePollAggregationProcessor() + private val fakeEncryptedReferenceAggregationProcessor = FakeEncryptedReferenceAggregationProcessor() + private val fakeEventEditValidator = FakeEventEditValidator() + private val fakeClock = FakeClock() + private val fakeRealm = FakeRealm() + + private val encryptedEventRelationsAggregationProcessor = EventRelationsAggregationProcessor( + userId = "userId", + stateEventDataSource = fakeStateEventDataSource.instance, + sessionId = "sessionId", + sessionManager = fakeSessionManager.instance, + liveLocationAggregationProcessor = fakeLiveLocationAggregationProcessor.instance, + pollAggregationProcessor = fakePollAggregationProcessor.instance, + encryptedReferenceAggregationProcessor = fakeEncryptedReferenceAggregationProcessor.instance, + editValidator = fakeEventEditValidator.instance, + clock = fakeClock, + ) + + @Test + fun `given an encrypted reference event when process then reference is processed`() { + // Given + val anEvent = givenAnEvent( + eventId = AN_EVENT_ID, + roomId = A_ROOM_ID, + eventType = EventType.ENCRYPTED, + ) + val relatedEventId = "related-event-id" + val encryptedEventContent = givenEncryptedEventContent( + relationType = RelationType.REFERENCE, + relatedEventId = relatedEventId, + ) + every { anEvent.content } returns encryptedEventContent.toContent() + val resultOfReferenceProcess = false + fakeEncryptedReferenceAggregationProcessor.givenHandleReturns(resultOfReferenceProcess) + givenEventAnnotationsSummary(roomId = A_ROOM_ID, eventId = AN_EVENT_ID, annotationsSummary = null) + + // When + encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + fakeEncryptedReferenceAggregationProcessor.verifyHandle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = false, + relatedEventId = relatedEventId, + ) + } + + private fun givenAnEvent( + eventId: String, + roomId: String?, + eventType: String, + ): Event { + return mockk().also { + every { it.eventId } returns eventId + every { it.roomId } returns roomId + every { it.getClearType() } returns eventType + } + } + + private fun givenEncryptedEventContent(relationType: String, relatedEventId: String): EncryptedEventContent { + val relationContent = RelationDefaultContent( + eventId = relatedEventId, + type = relationType, + ) + return EncryptedEventContent( + relatesTo = relationContent, + ) + } + + private fun givenEventAnnotationsSummary( + roomId: String, + eventId: String, + annotationsSummary: EventAnnotationsSummaryEntity? + ) { + fakeRealm.givenWhere() + .givenEqualTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId) + .givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId) + .givenFindFirst(annotationsSummary) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt index 1f9bc2a976..49d64c1835 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt @@ -23,7 +23,6 @@ import io.mockk.mockk import io.mockk.runs import io.mockk.verify import io.realm.Realm -import io.realm.Realm.Transaction import io.realm.RealmModel import io.realm.RealmObject import io.realm.RealmQuery @@ -43,13 +42,6 @@ internal class FakeRealm { inline fun verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { verify { instance.insertOrUpdate(verification()) } } - - fun givenExecuteTransactionAsync() { - every { instance.executeTransactionAsync(any()) } answers { - firstArg().execute(instance) - mockk() - } - } } inline fun RealmQuery.givenFindFirst( diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt index 3a69515140..9ad7032262 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.test.fakes import io.mockk.coEvery -import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.realm.Realm @@ -37,9 +36,4 @@ internal class FakeRealmConfiguration { secondArg<(Realm) -> T>().invoke(realm) } } - - fun givenGetRealmInstance(realm: Realm) { - mockkStatic(Realm::class) - every { Realm.getInstance(instance) } returns realm - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeEventEditValidator.kt similarity index 57% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeEventEditValidator.kt index 6393877d38..2fa36cf60d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeEventEditValidator.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,12 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.database.model +package org.matrix.android.sdk.test.fakes.internal -import io.realm.RealmObject +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.room.EventEditValidator -/** - * This class is used to get notification on new UTD events. Since these events cannot be processed - * in EventInsertEntity, we should introduce a dedicated entity for that. - */ -internal open class UnableToDecryptEventEntity( - var eventId: String = "", -) : RealmObject() +internal class FakeEventEditValidator { + + val instance: EventEditValidator = mockk() +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeLiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeLiveLocationAggregationProcessor.kt new file mode 100644 index 0000000000..6385110963 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeLiveLocationAggregationProcessor.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes.internal + +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor + +internal class FakeLiveLocationAggregationProcessor { + + val instance: LiveLocationAggregationProcessor = mockk() +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePollAggregationProcessor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePollAggregationProcessor.kt new file mode 100644 index 0000000000..5187c785ca --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePollAggregationProcessor.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes.internal + +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor + +internal class FakePollAggregationProcessor { + + val instance: PollAggregationProcessor = mockk() +} From 94dd599f137906d611d51d6cc02646de454eb833 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 12 Jan 2023 10:03:01 +0100 Subject: [PATCH 069/237] Fix after rebase --- .../room/detail/timeline/helper/PollResponseDataFactory.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt index 533397b4d8..0ef60da6de 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt @@ -44,7 +44,8 @@ class PollResponseDataFactory @Inject constructor( ) }, winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, - totalVotes = it.aggregatedContent?.totalVotes ?: 0 + totalVotes = it.aggregatedContent?.totalVotes ?: 0, + hasDecryptionError = it.encryptedRelatedEventIds.isNotEmpty(), ) } } From d1ce15bf18ea4119ece633dc4b42cf08cc5a3316 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 12 Jan 2023 10:14:47 +0100 Subject: [PATCH 070/237] Renaming field in PollResponseData for better clarity --- .../room/detail/timeline/factory/PollItemViewStateFactory.kt | 4 ++-- .../room/detail/timeline/helper/PollResponseDataFactory.kt | 2 +- .../home/room/detail/timeline/item/MessageInformationData.kt | 2 +- .../detail/timeline/factory/PollItemViewStateFactoryTest.kt | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index 28c560e161..7abc51fa51 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -83,7 +83,7 @@ class PollItemViewStateFactory @Inject constructor( totalVotes: Int, winnerVoteCount: Int?, ): PollViewState { - val totalVotesText = if (pollResponseSummary?.hasDecryptionError.orFalse()) { + val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) { stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) } else { stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes) @@ -131,7 +131,7 @@ class PollItemViewStateFactory @Inject constructor( pollResponseSummary: PollResponseData?, totalVotes: Int ): PollViewState { - val totalVotesText = if (pollResponseSummary?.hasDecryptionError.orFalse()) { + val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) { stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) } else { stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt index 0ef60da6de..8f81adcd32 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt @@ -45,7 +45,7 @@ class PollResponseDataFactory @Inject constructor( }, winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, totalVotes = it.aggregatedContent?.totalVotes ?: 0, - hasDecryptionError = it.encryptedRelatedEventIds.isNotEmpty(), + hasEncryptedRelatedEvents = it.encryptedRelatedEventIds.isNotEmpty(), ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 4bdfb948ce..a1a214785e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -91,7 +91,7 @@ data class PollResponseData( val totalVotes: Int = 0, val winnerVoteCount: Int = 0, val isClosed: Boolean = false, - val hasDecryptionError: Boolean = false, + val hasEncryptedRelatedEvents: Boolean = false, ) : Parcelable { fun getVoteSummaryOfAnOption(optionId: String) = votes?.get(optionId) diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt index 4d14458e7f..8ee55d5b6e 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt @@ -136,7 +136,7 @@ class PollItemViewStateFactoryTest { // Given val stringProvider = FakeStringProvider() val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) - val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasDecryptionError = true) + val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasEncryptedRelatedEvents = true) val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary) // When @@ -220,7 +220,7 @@ class PollItemViewStateFactoryTest { totalVotes = 1, myVote = A_POLL_OPTION_IDS[0], votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0)), - hasDecryptionError = true, + hasEncryptedRelatedEvents = true, ) val disclosedPollContent = A_POLL_CONTENT.copy( unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy( From ee3cbd988481d0d071726831c720389fd60c176a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 12 Jan 2023 11:15:38 +0100 Subject: [PATCH 071/237] Filter in only encrypted events with relatesTo content --- .../database/EventInsertLiveObserver.kt | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index 84fb14c914..2cae6c09ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -17,12 +17,16 @@ package org.matrix.android.sdk.internal.database import com.zhuinden.monarchy.Monarchy +import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmResults import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertEntity @@ -76,17 +80,17 @@ internal class EventInsertLiveObserver @Inject constructor( Timber.v("##Transaction: There are ${filteredEvents.size} events to process ") filteredEvents.forEach { eventInsert -> val eventId = eventInsert.eventId - val event = EventEntity.where(realm, eventId).findFirst() - if (event == null) { - Timber.v("Event $eventId not found") + val event = getEvent(realm, eventId) + if (event != null && canProcessEvent(event)) { + processors.filter { + it.shouldProcess(eventId, event.getClearType(), eventInsert.insertType) + }.forEach { + it.process(realm, event) + } + } else { + Timber.v("Cannot process event with id $eventId") return@forEach } - val domainEvent = event.asDomain() - processors.filter { - it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType) - }.forEach { - it.process(realm, domainEvent) - } } realm.where(EventInsertEntity::class.java) .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray()) @@ -104,6 +108,20 @@ internal class EventInsertLiveObserver @Inject constructor( } } + private fun getEvent(realm: Realm, eventId: String): Event? { + val event = EventEntity.where(realm, eventId).findFirst() + if (event == null) { + Timber.v("Event $eventId not found") + } + return event?.asDomain() + } + + private fun canProcessEvent(event: Event): Boolean { + // event should be either not encrypted or if encrypted it should contain relatesTo content + return event.getClearType() != EventType.ENCRYPTED || + event.content.toModel()?.relatesTo != null + } + private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean { return processors.any { it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType) From 90d9eaf9507ee5f456c452dc7d88c1485ca84d3e Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Mon, 9 Jan 2023 07:09:04 +0000 Subject: [PATCH 072/237] Translated using Weblate (Czech) Currently translated at 100.0% (2585 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- library/ui-strings/src/main/res/values-cs/strings.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index 0a7998deaa..2d2b91d645 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -2860,7 +2860,7 @@ Přihlásit se pomocí QR kódu Naskenovat QR kód Možnost nahrávat a odesílat hlasové vysílání na časové ose místnosti. - Povolit hlasové vysílání (v aktivním vývoji) + Povolit hlasové vysílání Domovský server nepodporuje přihlášení pomocí QR kódu. Přihlášení bylo na druhém zařízení zrušeno. Tento QR kód je neplatný. @@ -2946,4 +2946,13 @@ Odkaz Text Nastavit odkaz + Přístupový token umožňuje plný přístup k účtu. Nikomu ho nesdělujte. + Přístupový token + Přepnout na odrážky + Přepnout na číslovaný seznam + V této místnosti nejsou žádné předchozí hlasování + Předchozí hlasování + V této místnosti nejsou žádné aktivní hlasování + Aktivní hlasování + Historie hlasování \ No newline at end of file From 6cdd8096cd0889c68b2b68f792cf251c4ca40243 Mon Sep 17 00:00:00 2001 From: Christian Paul Date: Mon, 9 Jan 2023 09:39:16 +0000 Subject: [PATCH 073/237] Translated using Weblate (Esperanto) Currently translated at 75.8% (1961 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/eo/ --- library/ui-strings/src/main/res/values-eo/strings.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-eo/strings.xml b/library/ui-strings/src/main/res/values-eo/strings.xml index 4521e840a6..e417d183bf 100644 --- a/library/ui-strings/src/main/res/values-eo/strings.xml +++ b/library/ui-strings/src/main/res/values-eo/strings.xml @@ -1123,9 +1123,7 @@ Elektu landon Administri retpoŝtadresojn kaj telefonnumerojn ligitajn al via konto de Matrix Retpoŝtadresoj kaj telefonnumeroj - Ĉu montri ĉiujn mesaĝojn de %s\? -\n -\nSciu ke tiu ĉi ago reekigos la aplikaĵon, kaj tio povas daŭri iom da tempo. + Ĉu montri ĉiujn mesaĝojn de %s\? Via pasvorto ĝisdatiĝis La pasvorto ne validas Malsukcesis ĝisdatigi pasvorton @@ -2201,4 +2199,5 @@ Sonorante… Aroj - Iom uzantoj reatentita + \@room \ No newline at end of file From 07db45a1678d8cc7d989008b643f867389c602c6 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sat, 7 Jan 2023 20:46:31 +0000 Subject: [PATCH 074/237] Translated using Weblate (Hungarian) Currently translated at 100.0% (2585 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- library/ui-strings/src/main/res/values-hu/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index 1be136bb39..9fdad2dbf0 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -2890,4 +2890,13 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Hivatkozás Szöveg Hivatkozás beállítása + A hozzáférési kulcs teljes elérést biztosít a fiókhoz. Soha ne ossza meg mással. + Elérési kulcs + Lista ki-,bekapcsolása + Számozott lista ki-,bekapcsolása + Nincsenek régi szavazások ebben a szobában + Régi szavazások + Nincsenek aktív szavazások ebben a szobában + Aktív szavazások + Szavazás alakulása \ No newline at end of file From 6813571015c06959753d1aba647e12d2914e1c7e Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 12 Jan 2023 18:46:18 +0300 Subject: [PATCH 075/237] Fix rendering bug when poll is edited from another client. --- .../internal/session/room/EventEditValidator.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt index 41d0c3f6ab..5a66e7e62d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt @@ -16,13 +16,16 @@ package org.matrix.android.sdk.internal.session.room +import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import timber.log.Timber import javax.inject.Inject @@ -101,7 +104,7 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto if (originalDecrypted.type != replaceDecrypted.type) { return EditValidity.Invalid("replacement and original events must have the same type") } - if (replaceDecrypted.clearContent.toModel()?.newContent == null) { + if (!hasNewContent(replaceDecrypted.type, replaceDecrypted.clearContent)) { return EditValidity.Invalid("replacement event must have an m.new_content property") } } else { @@ -116,11 +119,18 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto if (originalEvent.type != replaceEvent.type) { return EditValidity.Invalid("replacement and original events must have the same type") } - if (replaceEvent.content.toModel()?.newContent == null) { + if (!hasNewContent(replaceEvent.type, replaceEvent.content)) { return EditValidity.Invalid("replacement event must have an m.new_content property") } } return EditValidity.Valid } + + private fun hasNewContent(eventType: String?, content: Content?): Boolean { + return when (eventType) { + in EventType.POLL_START.values -> content.toModel()?.newContent != null + else -> content.toModel()?.newContent != null + } + } } From 72e0dc4bd2c366b8dd7a30513900c5736b6075c3 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 12 Jan 2023 16:13:34 +0100 Subject: [PATCH 076/237] Voice Broadcast - only send a notification on the first chunk --- changelog.d/7845.wip | 1 + .../src/main/res/values/strings.xml | 1 + .../format/DisplayableEventFormatter.kt | 16 +++-- .../notifications/FilteredEventDetector.kt | 59 +++++++++++++++++++ .../NotificationDrawerManager.kt | 6 ++ 5 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 changelog.d/7845.wip create mode 100644 vector/src/main/java/im/vector/app/features/notifications/FilteredEventDetector.kt diff --git a/changelog.d/7845.wip b/changelog.d/7845.wip new file mode 100644 index 0000000000..8bce21499a --- /dev/null +++ b/changelog.d/7845.wip @@ -0,0 +1 @@ +[Voice Broadcast] Only display a notification on the first voice chunk diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index f1c2eb2c6d..6731248f83 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2295,6 +2295,7 @@ Verification Conclusion Shared their location Shared their live location + Started a voice broadcast Waiting… %s canceled diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index 5fa9576dd4..f57aa67800 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -27,6 +27,7 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.isLive +import im.vector.app.features.voicebroadcast.isVoiceBroadcast import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import me.gujun.android.span.image import me.gujun.android.span.span @@ -39,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageType +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getTextDisplayableContent @@ -86,10 +88,16 @@ class DisplayableEventFormatter @Inject constructor( simpleFormat(senderName, stringProvider.getString(R.string.sent_an_image), appendAuthor) } MessageType.MSGTYPE_AUDIO -> { - if ((messageContent as? MessageAudioContent)?.voiceMessageIndicator != null) { - simpleFormat(senderName, stringProvider.getString(R.string.sent_a_voice_message), appendAuthor) - } else { - simpleFormat(senderName, stringProvider.getString(R.string.sent_an_audio_file), appendAuthor) + when { + (messageContent as? MessageAudioContent)?.voiceMessageIndicator == null -> { + simpleFormat(senderName, stringProvider.getString(R.string.sent_an_audio_file), appendAuthor) + } + timelineEvent.root.asMessageAudioEvent().isVoiceBroadcast() -> { + simpleFormat(senderName, stringProvider.getString(R.string.started_a_voice_broadcast), appendAuthor) + } + else -> { + simpleFormat(senderName, stringProvider.getString(R.string.sent_a_voice_message), appendAuthor) + } } } MessageType.MSGTYPE_VIDEO -> { diff --git a/vector/src/main/java/im/vector/app/features/notifications/FilteredEventDetector.kt b/vector/src/main/java/im/vector/app/features/notifications/FilteredEventDetector.kt new file mode 100644 index 0000000000..e21462b182 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/notifications/FilteredEventDetector.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.app.features.notifications + +import im.vector.app.ActiveSessionDataSource +import im.vector.app.features.voicebroadcast.isVoiceBroadcast +import im.vector.app.features.voicebroadcast.sequence +import org.matrix.android.sdk.api.session.events.model.isVoiceMessage +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import javax.inject.Inject + +class FilteredEventDetector @Inject constructor( + private val activeSessionDataSource: ActiveSessionDataSource +) { + + /** + * Returns true if the given event should be ignored. + * Used to skip notifications if a non expected message is received. + */ + fun shouldBeIgnored(notifiableEvent: NotifiableEvent): Boolean { + val session = activeSessionDataSource.currentValue?.orNull() ?: return false + + if (notifiableEvent is NotifiableMessageEvent) { + val room = session.getRoom(notifiableEvent.roomId) ?: return false + val timelineEvent = room.getTimelineEvent(notifiableEvent.eventId) ?: return false + return timelineEvent.shouldBeIgnored() + } + return false + } + + /** + * Whether the timeline event should be ignored. + */ + private fun TimelineEvent.shouldBeIgnored(): Boolean { + if (root.isVoiceMessage()) { + val audioEvent = root.asMessageAudioEvent() + // if the event is a voice message related to a voice broadcast, only show the event on the first chunk. + return audioEvent.isVoiceBroadcast() && audioEvent?.sequence != 1 + } + + return false + } +} diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 4f05e83bd4..2d799034d9 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -47,6 +47,7 @@ class NotificationDrawerManager @Inject constructor( private val notifiableEventProcessor: NotifiableEventProcessor, private val notificationRenderer: NotificationRenderer, private val notificationEventPersistence: NotificationEventPersistence, + private val filteredEventDetector: FilteredEventDetector, private val buildMeta: BuildMeta, ) { @@ -100,6 +101,11 @@ class NotificationDrawerManager @Inject constructor( Timber.d("onNotifiableEventReceived(): is push: ${notifiableEvent.canBeReplaced}") } + if (filteredEventDetector.shouldBeIgnored(notifiableEvent)) { + Timber.d("onNotifiableEventReceived(): ignore the event") + return + } + add(notifiableEvent) } From 8a2f28bc376fd2f34c97270cdff7336332efe50e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 12 Jan 2023 18:29:56 +0100 Subject: [PATCH 077/237] Add comment to explain the error handling --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index ab4b6c2269..1cb418163f 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -282,6 +282,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } catch (failure: VoiceBroadcastFailure.ListeningError.DownloadError) { isPreparingNextPlayer = false + // Do not change the playingState if the current player is still valid, + // the error will be thrown again when switching to the next player if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) { playingState = State.Error(failure) } @@ -453,6 +455,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean { Timber.d("## Voice Broadcast | onError: what=$what, extra=$extra") + // Do not change the playingState if the current player is still valid, + // the error will be thrown again when switching to the next player if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) { playingState = State.Error(VoiceBroadcastFailure.ListeningError.UnableToPlay(what, extra)) } From d55f1efd632d2426ba1ab2e0841b73d0948e8d60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jan 2023 00:17:34 +0000 Subject: [PATCH 078/237] Bump wysiwyg from 0.15.0 to 0.17.0 Bumps [wysiwyg](https://github.com/matrix-org/matrix-wysiwyg) from 0.15.0 to 0.17.0. - [Release notes](https://github.com/matrix-org/matrix-wysiwyg/releases) - [Changelog](https://github.com/matrix-org/matrix-rich-text-editor/blob/main/CHANGELOG.md) - [Commits](https://github.com/matrix-org/matrix-wysiwyg/compare/0.15.0...0.17.0) --- updated-dependencies: - dependency-name: io.element.android:wysiwyg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 8b0933b943..f9d2850e58 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -103,7 +103,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:0.15.0" + 'wysiwyg' : "io.element.android:wysiwyg:0.17.0" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", From cad5e732dfb6af7831017bdfb44792138da0521a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Jan 2023 10:36:03 +0100 Subject: [PATCH 079/237] Fix issue of send button not displayed when starting message with a space. --- .../home/room/detail/composer/MessageComposerViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index c02eb1fa8a..56ee9ffb5a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -138,7 +138,7 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleOnTextChanged(action: MessageComposerAction.OnTextChanged) { - val needsSendButtonVisibilityUpdate = currentComposerText.isEmpty() != action.text.isEmpty() + val needsSendButtonVisibilityUpdate = currentComposerText.isBlank() != action.text.isBlank() currentComposerText = SpannableString(action.text) if (needsSendButtonVisibilityUpdate) { updateIsSendButtonVisibility(true) From 06ac22488e7f2d93d0c608ff8bbee632dfe3129f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Jan 2023 10:45:09 +0100 Subject: [PATCH 080/237] Fix typo. --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index f1c2eb2c6d..405dae0e16 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -794,7 +794,7 @@ Shows all threads you’ve participated in Keep discussions organized with threads Threads help keep your conversations on-topic and easy to track. - You\'re homeserver does not support listing threads yet. + Your homeserver does not support listing threads yet. Tip: Long tap a message and use “%s”. From a Thread From e5801a4f19a00fd76755c898be0a5a041f343aac Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 13 Jan 2023 15:56:38 +0300 Subject: [PATCH 081/237] Make verification dialog cancelable. --- .../crypto/verification/VerificationBottomSheet.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index 38b72f2022..349dfc738d 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -17,6 +17,7 @@ package im.vector.app.features.crypto.verification import android.app.Activity import android.app.Dialog +import android.content.DialogInterface import android.os.Bundle import android.os.Parcelable import android.view.KeyEvent @@ -85,7 +86,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment Date: Fri, 13 Jan 2023 15:56:47 +0300 Subject: [PATCH 082/237] Add changelog. --- changelog.d/4025.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4025.bugfix diff --git a/changelog.d/4025.bugfix b/changelog.d/4025.bugfix new file mode 100644 index 0000000000..109da1c830 --- /dev/null +++ b/changelog.d/4025.bugfix @@ -0,0 +1 @@ +Fix can't get out of a verification dialog From 169c9b221ca69039406a50744749c5336c4af85a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 13 Jan 2023 15:46:48 +0100 Subject: [PATCH 083/237] Throw an error if the media player which has completed is not the expected one --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 1cb418163f..2e1600e4e2 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -436,7 +436,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor( override fun onCompletion(mp: MediaPlayer) { // Release media player as soon as it completed mp.release() - currentMediaPlayer = null + if (currentMediaPlayer == mp) { + currentMediaPlayer = null + } else { + error("The media player which has completed mismatches the current media player instance.") + } // Next media player is already attached to this player and will start playing automatically if (nextMediaPlayer != null) return From 4a49f2ff9b6acda4cd879240034884d15ed9e31d Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 13 Jan 2023 18:15:52 +0300 Subject: [PATCH 084/237] Check if bottom sheet is cancellable. --- .../features/crypto/verification/VerificationBottomSheet.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index 349dfc738d..3b9de57be8 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -85,10 +85,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment Date: Fri, 13 Jan 2023 16:48:53 +0100 Subject: [PATCH 085/237] Check encrypted event status using the Event model --- .../database/EventInsertLiveObserver.kt | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index 2cae6c09ed..a3f38cf2c6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -55,32 +55,46 @@ internal class EventInsertLiveObserver @Inject constructor( if (!results.isLoaded || results.isEmpty()) { return@withLock } - val idsToDeleteAfterProcess = ArrayList() - val idsOfEncryptedEvents = ArrayList() - val filteredEvents = ArrayList(results.size) + val eventsToProcess = ArrayList(results.size) + val eventsToIgnore = ArrayList(results.size) + Timber.v("EventInsertEntity updated with ${results.size} results in db") results.forEach { - if (shouldProcess(it)) { - // don't use copy from realm over there - val copiedEvent = EventInsertEntity( - eventId = it.eventId, - eventType = it.eventType - ).apply { - insertType = it.insertType - } - filteredEvents.add(copiedEvent) + // don't use copy from realm over there + val copiedEvent = EventInsertEntity( + eventId = it.eventId, + eventType = it.eventType + ).apply { + insertType = it.insertType } - if (it.eventType == EventType.ENCRYPTED) { - idsOfEncryptedEvents.add(it.eventId) + + if (shouldProcess(it)) { + eventsToProcess.add(copiedEvent) } else { - idsToDeleteAfterProcess.add(it.eventId) + eventsToIgnore.add(copiedEvent) } } + awaitTransaction(realmConfiguration) { realm -> - Timber.v("##Transaction: There are ${filteredEvents.size} events to process ") - filteredEvents.forEach { eventInsert -> + Timber.v("##Transaction: There are ${eventsToProcess.size} events to process") + + val idsToDeleteAfterProcess = ArrayList() + val idsOfEncryptedEvents = ArrayList() + val getAndTriageEvent: (EventInsertEntity) -> Event? = { eventInsert -> val eventId = eventInsert.eventId val event = getEvent(realm, eventId) + if (event?.getClearType() == EventType.ENCRYPTED) { + idsOfEncryptedEvents.add(eventId) + } else { + idsToDeleteAfterProcess.add(eventId) + } + event + } + + eventsToProcess.forEach { eventInsert -> + val eventId = eventInsert.eventId + val event = getAndTriageEvent(eventInsert) + if (event != null && canProcessEvent(event)) { processors.filter { it.shouldProcess(eventId, event.getClearType(), eventInsert.insertType) @@ -92,6 +106,9 @@ internal class EventInsertLiveObserver @Inject constructor( return@forEach } } + + eventsToIgnore.forEach { getAndTriageEvent(it) } + realm.where(EventInsertEntity::class.java) .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray()) .findAll() From c3a4c43fefde1e8a12d055200641a91ef1e65ba3 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Thu, 12 Jan 2023 18:10:52 +0000 Subject: [PATCH 086/237] Translated using Weblate (Czech) Currently translated at 100.0% (2590 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- library/ui-strings/src/main/res/values-cs/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index 2d2b91d645..7d3dc1a82c 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -2955,4 +2955,9 @@ V této místnosti nejsou žádné aktivní hlasování Aktivní hlasování Historie hlasování + Ukončené hlasování + Hlasování + ukončil(a) hlasování. + Hlasování bylo ukončeno. + Váš domovský server zatím nepodporuje zobrazení seznamu vláken. \ No newline at end of file From 1eee44d9aa8deee1f2fa1bcb06c08d9d3afc135a Mon Sep 17 00:00:00 2001 From: Vri Date: Thu, 12 Jan 2023 10:51:03 +0000 Subject: [PATCH 087/237] Translated using Weblate (German) Currently translated at 100.0% (2590 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- library/ui-strings/src/main/res/values-de/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 52b8f0c716..21d2249f60 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2898,4 +2898,9 @@ In diesem Raum gibt es keine aktiven Umfragen Aktive Umfragen Umfrageverlauf + Beendete Umfrage + Umfrage + beendete eine Umfrage. + Umfrage beendet. + Dein Heim-Server unterstützt noch nicht das Auflisten von Threads. \ No newline at end of file From a9eb333fd227ef0204a22f1902daaf5cddd31f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Thu, 12 Jan 2023 11:57:33 +0000 Subject: [PATCH 088/237] Translated using Weblate (Estonian) Currently translated at 99.6% (2582 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- library/ui-strings/src/main/res/values-et/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index 1e8e2b989e..bd006c9c12 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2890,4 +2890,9 @@ Lülita täpploend sisse/välja Pääsuluba Sinu pääsuluba annab täismahulise ligipääsu sinu kasutajakontole. Palun ära jaga seda teistega. + Lõppenud küsitlus + Küsitlus on lõppenud. + lõpetas küsitluse. + Küsitlus + Sinu koduserver veel ei toeta jutulõngade loendit. \ No newline at end of file From 0cdafc96d8e25a2d8c4af38e84e192d6d01d6e9d Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Thu, 12 Jan 2023 12:50:40 +0000 Subject: [PATCH 089/237] Translated using Weblate (Persian) Currently translated at 100.0% (2590 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- library/ui-strings/src/main/res/values-fa/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ui-strings/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml index 4db3812237..49fd584248 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -2899,4 +2899,9 @@ هیچ نظرسنجی فعّالی در این اتاق وجود ندارد نظرسنجی‌های فعّال تاریخچهٔ نظرسنجی‌ها + نظرسنجی پایان یافته + نظرسنجی + به نظرسنجی‌ای پایان داد. + به نظرسنجی پایان داد. + کارساز خانگیتان هنوز از سیاهه کردن رشته‌ها پشتیبانی نمی‌کند. \ No newline at end of file From 635f2e9d4f1bd6ac6d84630475da252d993ad750 Mon Sep 17 00:00:00 2001 From: Glandos Date: Fri, 13 Jan 2023 08:22:12 +0000 Subject: [PATCH 090/237] Translated using Weblate (French) Currently translated at 100.0% (2590 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- library/ui-strings/src/main/res/values-fr/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml index cb1684f834..50b27e419e 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -2899,4 +2899,9 @@ Il n’y a aucun sondage en cours dans ce salon Sondages actifs Historique des sondages + Sondage terminé + Sondage + a terminé un sondage. + A terminé le sondage. + Votre serveur d’accueil ne prend pas encore en charge l’affichage de la liste des fils de discussion. \ No newline at end of file From bf8f8b7f8c56d18fb0c5eb9fad6d5de40a179255 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sat, 14 Jan 2023 13:51:07 +0000 Subject: [PATCH 091/237] Translated using Weblate (Hungarian) Currently translated at 100.0% (2590 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- library/ui-strings/src/main/res/values-hu/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index 9fdad2dbf0..7fdbd11bdd 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -2899,4 +2899,9 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Nincsenek aktív szavazások ebben a szobában Aktív szavazások Szavazás alakulása + Lezárt szavazások + Szavazás + befejezte a szavazást. + Szavazás vége. + A matrix szerver nem támogatja az üzenetszálak listázását. \ No newline at end of file From c823190d0ee19a242a67fd54b82dfb3582fd97ff Mon Sep 17 00:00:00 2001 From: Linerly Date: Thu, 12 Jan 2023 10:51:20 +0000 Subject: [PATCH 092/237] Translated using Weblate (Indonesian) Currently translated at 100.0% (2590 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- library/ui-strings/src/main/res/values-in/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml index 8896037037..96a85cce04 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -2845,4 +2845,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Tidak ada pemungutan suara yang aktif di ruangan ini Pemungutan suara aktif Riwayat pemungutan suara + Pemungutan suara diakhiri + Pemungutan suara + mengakhiri pemungutan suara. + Mengakhiri pemungutan suara. + Homeserver Anda belum mendukung pendaftaran utasan. \ No newline at end of file From 00ca47737f5096bc49305b0ea911fa5659396515 Mon Sep 17 00:00:00 2001 From: random Date: Fri, 13 Jan 2023 14:38:49 +0000 Subject: [PATCH 093/237] Translated using Weblate (Italian) Currently translated at 100.0% (2590 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- library/ui-strings/src/main/res/values-it/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ui-strings/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml index 729b826982..dcc1cc4490 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -2890,4 +2890,9 @@ In questa stanza non ci sono sondaggi attivi Sondaggi attivi Cronologia sondaggi + Sondaggio terminato + Sondaggio + terminato un sondaggio. + Sondaggio terminato. + Il tuo homeserver non supporta ancora l\'elenco di conversazioni. \ No newline at end of file From 1457717b3089a516c6a1bda49f3b00ca1963f471 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Thu, 12 Jan 2023 14:25:17 +0000 Subject: [PATCH 094/237] Translated using Weblate (Slovak) Currently translated at 100.0% (2590 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- library/ui-strings/src/main/res/values-sk/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml index 34155ba6a5..67a953d810 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2955,4 +2955,9 @@ V tejto miestnosti nie sú žiadne aktívne ankety Aktívne ankety História ankety + Ukončená anketa + Anketa + ukončil/a anketu. + Ukončil/a anketu. + Váš domovský server zatiaľ nepodporuje zobrazovanie vlákien. \ No newline at end of file From f9972f3378032585432dfae833b409b78f7150bb Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Thu, 12 Jan 2023 19:59:35 +0000 Subject: [PATCH 095/237] Translated using Weblate (Swedish) Currently translated at 100.0% (2590 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- .../src/main/res/values-sv/strings.xml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/library/ui-strings/src/main/res/values-sv/strings.xml b/library/ui-strings/src/main/res/values-sv/strings.xml index 373165802a..3430328f05 100644 --- a/library/ui-strings/src/main/res/values-sv/strings.xml +++ b/library/ui-strings/src/main/res/values-sv/strings.xml @@ -2882,4 +2882,26 @@ Direktsändning Du avslutade en röstsändning. %1$s avslutade en röstsändning. + Din åtkomsttoken ger full åtkomst till ditt konto. Dela den inte med någon. + Åtkomsttoken + Avslutade omröstning + Omröstning + avslutade en omröstning. + Redigera länk + Skapa en länk + Länk + Text + Växla punktlista + Växla numrerad lista + Sätt länk + Det finns inga tidigare omröstningar i det här rummet + Tidigare omröstningar + Det finns inga aktiva omröstningar i det här rummet + Aktiva omröstningar + Avslutade omröstningen. + Är du säker på att du vill avsluta din direktsändning\? Detta kommer att avsluta sändningen och den fulla inspelningen kommer att bli tillgänglig i rummet. + Avsluta röstsändning\? + Omröstningshistorik + Din hemserver har inte stöd för att lista trådar än. + Ja, sluta \ No newline at end of file From e775f2bac602908d5a3c0bb99ff74538b7958be4 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Thu, 12 Jan 2023 12:15:38 +0000 Subject: [PATCH 096/237] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2590 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- library/ui-strings/src/main/res/values-uk/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml index 2ee9685c76..c368160a78 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -3011,4 +3011,9 @@ У цій кімнаті немає активних опитувань Активні опитування Історія опитувань + Завершене опитування + Опитування + завершує опитування. + Опитування завершено. + Ваш домашній сервер поки що не підтримує створення списків гілок. \ No newline at end of file From 73432cd86d705e2f9a8a46ccc8c3a01dcbf09ec7 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 13 Jan 2023 01:59:45 +0000 Subject: [PATCH 097/237] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2590 of 2590 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- library/ui-strings/src/main/res/values-zh-rTW/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml index 14729c5b44..18076ea61f 100644 --- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml @@ -2843,4 +2843,9 @@ 此聊天室沒有正在進行的投票 進行中的投票 投票歷史紀錄 + 已結束投票 + 投票 + 已結束投票。 + 已結束投票。 + 您的家伺服器還不支援列出討論串。 \ No newline at end of file From c07d2819e70a42070f57639a5fafc63a3e781fd5 Mon Sep 17 00:00:00 2001 From: Vri Date: Thu, 12 Jan 2023 10:53:29 +0000 Subject: [PATCH 098/237] Translated using Weblate (German) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/de/ --- fastlane/metadata/android/de-DE/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/de-DE/changelogs/40105200.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/40105200.txt b/fastlane/metadata/android/de-DE/changelogs/40105200.txt new file mode 100644 index 0000000000..549880cafb --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Hauptsächlich Fehlerbeseitigungen! +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases From 40b91029824c09de370f109ac7590a08082ae865 Mon Sep 17 00:00:00 2001 From: Glandos Date: Fri, 13 Jan 2023 08:22:46 +0000 Subject: [PATCH 099/237] Translated using Weblate (French) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/ --- fastlane/metadata/android/fr-FR/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40105200.txt diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105200.txt b/fastlane/metadata/android/fr-FR/changelogs/40105200.txt new file mode 100644 index 0000000000..515dd1f882 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Principalement des corrections de bugs ! +Intégralité des changements : https://github.com/vector-im/element-android/releases From f325d04b66ece20d251073766edbee390364f63f Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sat, 14 Jan 2023 13:48:11 +0000 Subject: [PATCH 100/237] Translated using Weblate (Hungarian) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/hu/ --- fastlane/metadata/android/hu-HU/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/hu-HU/changelogs/40105200.txt diff --git a/fastlane/metadata/android/hu-HU/changelogs/40105200.txt b/fastlane/metadata/android/hu-HU/changelogs/40105200.txt new file mode 100644 index 0000000000..339f7ce136 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Leginkább hibajavítások. +Teljes változásnapló: https://github.com/vector-im/element-android/releases From a74ce99ee92c38c802454a6cc53e78151ea921a1 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Thu, 12 Jan 2023 14:23:21 +0000 Subject: [PATCH 101/237] Translated using Weblate (Slovak) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sk/ --- fastlane/metadata/android/sk/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sk/changelogs/40105200.txt diff --git a/fastlane/metadata/android/sk/changelogs/40105200.txt b/fastlane/metadata/android/sk/changelogs/40105200.txt new file mode 100644 index 0000000000..24c1221752 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Hlavne oprava chýb! +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases From a65ad14bc249045b306dc22d5d2eb9154e939632 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Thu, 12 Jan 2023 19:49:14 +0000 Subject: [PATCH 102/237] Translated using Weblate (Swedish) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sv/ --- fastlane/metadata/android/sv-SE/changelogs/40105130.txt | 2 ++ fastlane/metadata/android/sv-SE/changelogs/40105140.txt | 2 ++ fastlane/metadata/android/sv-SE/changelogs/40105160.txt | 2 ++ fastlane/metadata/android/sv-SE/changelogs/40105180.txt | 2 ++ fastlane/metadata/android/sv-SE/changelogs/40105200.txt | 2 ++ 5 files changed, 10 insertions(+) create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40105130.txt create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40105140.txt create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40105200.txt diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105130.txt b/fastlane/metadata/android/sv-SE/changelogs/40105130.txt new file mode 100644 index 0000000000..d0f9c996af --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105130.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådar är nu aktivt som förval. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105140.txt b/fastlane/metadata/android/sv-SE/changelogs/40105140.txt new file mode 100644 index 0000000000..d0f9c996af --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105140.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådar är nu aktivt som förval. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105160.txt b/fastlane/metadata/android/sv-SE/changelogs/40105160.txt new file mode 100644 index 0000000000..d0f9c996af --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådar är nu aktivt som förval. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105180.txt b/fastlane/metadata/android/sv-SE/changelogs/40105180.txt new file mode 100644 index 0000000000..d0f9c996af --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådar är nu aktivt som förval. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105200.txt b/fastlane/metadata/android/sv-SE/changelogs/40105200.txt new file mode 100644 index 0000000000..21c54d9fd3 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Huvudsakligen byggfixar! +Full ändringslogg: https://github.com/vector-im/element-android/releases From 7476a52943ffa0220cb48c2378c91ceaebb4af6e Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Thu, 12 Jan 2023 12:12:50 +0000 Subject: [PATCH 103/237] Translated using Weblate (Ukrainian) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ --- fastlane/metadata/android/uk/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/40105200.txt diff --git a/fastlane/metadata/android/uk/changelogs/40105200.txt b/fastlane/metadata/android/uk/changelogs/40105200.txt new file mode 100644 index 0000000000..202037bfdc --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Виправлення помилок! +Перелік усіх змін: https://github.com/vector-im/element-android/releases From e82fc909ec56d3fb9499303a0cb97bec8b6f5162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Thu, 12 Jan 2023 11:55:51 +0000 Subject: [PATCH 104/237] Translated using Weblate (Estonian) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40105200.txt diff --git a/fastlane/metadata/android/et/changelogs/40105200.txt b/fastlane/metadata/android/et/changelogs/40105200.txt new file mode 100644 index 0000000000..5d5a7fbbf2 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Olulisemad muutused selles versioonis: Põhiliselt veaparandused! +Ingliskeelne muudatuste logi täismahus: https://github.com/vector-im/element-android/releases From 2a33ce8bd7ccb9b6196ef63e448893ba47ce1217 Mon Sep 17 00:00:00 2001 From: random Date: Fri, 13 Jan 2023 14:39:17 +0000 Subject: [PATCH 105/237] Translated using Weblate (Italian) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/it/ --- fastlane/metadata/android/it-IT/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/40105200.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/40105200.txt b/fastlane/metadata/android/it-IT/changelogs/40105200.txt new file mode 100644 index 0000000000..6d3bda5395 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: correzione di errori! +Cronologia completa: https://github.com/vector-im/element-android/releases From ca40ea1e1b8f6aeff255a8794a3c04056c07bc2b Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Thu, 12 Jan 2023 12:51:27 +0000 Subject: [PATCH 106/237] Translated using Weblate (Persian) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/ --- fastlane/metadata/android/fa/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/fa/changelogs/40105200.txt diff --git a/fastlane/metadata/android/fa/changelogs/40105200.txt b/fastlane/metadata/android/fa/changelogs/40105200.txt new file mode 100644 index 0000000000..9643f6cbdd --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105200.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: عموماً رفع اشکال! +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases From 23630c1103d90733b3552b4069ca9ec8bbfa8e38 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 13 Jan 2023 02:00:13 +0000 Subject: [PATCH 107/237] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ --- fastlane/metadata/android/zh-TW/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40105200.txt diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105200.txt b/fastlane/metadata/android/zh-TW/changelogs/40105200.txt new file mode 100644 index 0000000000..960dc177be --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105200.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:主要是臭蟲修復! +完整的變更紀錄:https://github.com/vector-im/element-android/releases From e0472331c48bb8176e6eff4cbf754549434ca7d5 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Thu, 12 Jan 2023 18:07:42 +0000 Subject: [PATCH 108/237] Translated using Weblate (Czech) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/ --- fastlane/metadata/android/cs-CZ/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40105200.txt diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105200.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105200.txt new file mode 100644 index 0000000000..70ddac29a2 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Především opravy chyb! +Úplný seznam změn: https://github.com/vector-im/element-android/releases From fbb9e66b5495e292296b71becebd74aca409ee84 Mon Sep 17 00:00:00 2001 From: Linerly Date: Thu, 12 Jan 2023 10:49:51 +0000 Subject: [PATCH 109/237] Translated using Weblate (Indonesian) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/id/ --- fastlane/metadata/android/id/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/id/changelogs/40105200.txt diff --git a/fastlane/metadata/android/id/changelogs/40105200.txt b/fastlane/metadata/android/id/changelogs/40105200.txt new file mode 100644 index 0000000000..d80e0daa38 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Kebanyakan perbaikan kutu! +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases From a586e346dedad6e48c7780a0b120cc0bcab361c0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Jan 2023 13:52:15 +0100 Subject: [PATCH 110/237] Fix an issue on Breadcrumbs in dark theme on the draft indicator --- vector/src/main/res/layout/item_breadcrumbs.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/layout/item_breadcrumbs.xml b/vector/src/main/res/layout/item_breadcrumbs.xml index 7584d6f9d5..83624036d9 100644 --- a/vector/src/main/res/layout/item_breadcrumbs.xml +++ b/vector/src/main/res/layout/item_breadcrumbs.xml @@ -85,6 +85,7 @@ app:layout_constraintCircle="@id/breadcrumbsImageView" app:layout_constraintCircleAngle="225" app:layout_constraintCircleRadius="28dp" + app:tint="?vctr_content_primary" tools:ignore="MissingConstraints" tools:visibility="visible" /> From 19e218ead8ae04ec91a81683a7fbae718f6050ee Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Jan 2023 18:02:06 +0100 Subject: [PATCH 111/237] Small optimization --- .../sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt index c749f77fff..85bc8b0f97 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt @@ -105,7 +105,8 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( .enqueue() } - private fun handleUserIdsForCheckingTrustAndAffectedRoomShields(userIdsWithDeviceUpdate: Iterable) { + private fun handleUserIdsForCheckingTrustAndAffectedRoomShields(userIdsWithDeviceUpdate: Collection) { + if (userIdsWithDeviceUpdate.isEmpty()) return crossSigningService.checkTrustAndAffectedRoomShields(userIdsWithDeviceUpdate.toList()) } } From 822f1f293820ce315403d012cbdc30b5a29b1b9c Mon Sep 17 00:00:00 2001 From: ArakelMushegh Date: Mon, 16 Jan 2023 17:08:20 +0000 Subject: [PATCH 112/237] Added translation using Weblate (Armenian) --- library/ui-strings/src/main/res/values-hy/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 library/ui-strings/src/main/res/values-hy/strings.xml diff --git a/library/ui-strings/src/main/res/values-hy/strings.xml b/library/ui-strings/src/main/res/values-hy/strings.xml new file mode 100644 index 0000000000..a6b3daec93 --- /dev/null +++ b/library/ui-strings/src/main/res/values-hy/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 0f6927d6bc9dcb5f5dfe65e47cdc3db371cccb70 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Mon, 16 Jan 2023 06:33:39 +0000 Subject: [PATCH 113/237] Translated using Weblate (Czech) Currently translated at 100.0% (2592 of 2592 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- library/ui-strings/src/main/res/values-cs/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index 7d3dc1a82c..722ea503d0 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -2960,4 +2960,6 @@ ukončil(a) hlasování. Hlasování bylo ukončeno. Váš domovský server zatím nepodporuje zobrazení seznamu vláken. + Nelze přehrát toto hlasové vysílání. + Hlasové vysílání bylo zahájeno \ No newline at end of file From a74efb293f9d3a7c50331d078fa6358c917fa4f4 Mon Sep 17 00:00:00 2001 From: Vri Date: Mon, 16 Jan 2023 13:12:14 +0000 Subject: [PATCH 114/237] Translated using Weblate (German) Currently translated at 100.0% (2592 of 2592 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- library/ui-strings/src/main/res/values-de/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 21d2249f60..9dc8fb5912 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2893,7 +2893,7 @@ Zugriffstoken Unsortierte Liste umschalten Nummerierte Liste umschalten - In diesem Raum gibt es noch keine abgeschlossenen Umfragen + In diesem Raum gibt es keine abgeschlossenen Umfragen Vergangene Umfragen In diesem Raum gibt es keine aktiven Umfragen Aktive Umfragen @@ -2903,4 +2903,6 @@ beendete eine Umfrage. Umfrage beendet. Dein Heim-Server unterstützt noch nicht das Auflisten von Threads. + Eine Sprachübertragung wurde begonnen + Wiedergabe der Sprachübertragung nicht möglich. \ No newline at end of file From 4a311f0230635a8b92a02d7a18e198bdae894e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sun, 15 Jan 2023 15:26:07 +0000 Subject: [PATCH 115/237] Translated using Weblate (Estonian) Currently translated at 99.6% (2584 of 2592 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- library/ui-strings/src/main/res/values-et/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index bd006c9c12..e0ab3810c0 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2895,4 +2895,6 @@ lõpetas küsitluse. Küsitlus Sinu koduserver veel ei toeta jutulõngade loendit. + Alustasime ringhäälingukõnega + Selle ringhäälingukõne esitamine ei õnnestu. \ No newline at end of file From ea352701ec891c6d631133be27b4b0fa09c5bc78 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Sun, 15 Jan 2023 23:53:27 +0000 Subject: [PATCH 116/237] Translated using Weblate (Persian) Currently translated at 100.0% (2592 of 2592 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- library/ui-strings/src/main/res/values-fa/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml index 49fd584248..3fb42a3a45 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -2904,4 +2904,6 @@ به نظرسنجی‌ای پایان داد. به نظرسنجی پایان داد. کارساز خانگیتان هنوز از سیاهه کردن رشته‌ها پشتیبانی نمی‌کند. + ناتوان در پخش این صدا. + پخش صوتی را آغاز کرد \ No newline at end of file From d9dd04bed6c6fc6cf1db7f6dad820efb8473f7f3 Mon Sep 17 00:00:00 2001 From: Glandos Date: Mon, 16 Jan 2023 08:30:43 +0000 Subject: [PATCH 117/237] Translated using Weblate (French) Currently translated at 100.0% (2592 of 2592 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- library/ui-strings/src/main/res/values-fr/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml index 50b27e419e..0d5a669713 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -2904,4 +2904,6 @@ a terminé un sondage. A terminé le sondage. Votre serveur d’accueil ne prend pas encore en charge l’affichage de la liste des fils de discussion. + Impossible de lire cette diffusion audio. + A démarré une diffusion audio \ No newline at end of file From 3abff1bc155e125c30e1b8b3273191e721c4da95 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sun, 15 Jan 2023 21:12:07 +0000 Subject: [PATCH 118/237] Translated using Weblate (Hungarian) Currently translated at 100.0% (2592 of 2592 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- library/ui-strings/src/main/res/values-hu/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index 7fdbd11bdd..22d2787e5f 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -2904,4 +2904,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze befejezte a szavazást. Szavazás vége. A matrix szerver nem támogatja az üzenetszálak listázását. + A hang közvetítés nem játszható le. + Hang közvetítés indítva \ No newline at end of file From 60be24ed2299816bafb7e15d9405af49d6b0cdfe Mon Sep 17 00:00:00 2001 From: Linerly Date: Sun, 15 Jan 2023 18:54:31 +0000 Subject: [PATCH 119/237] Translated using Weblate (Indonesian) Currently translated at 100.0% (2592 of 2592 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- library/ui-strings/src/main/res/values-in/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml index 96a85cce04..4781596973 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -2850,4 +2850,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. mengakhiri pemungutan suara. Mengakhiri pemungutan suara. Homeserver Anda belum mendukung pendaftaran utasan. + Tidak dapat memutar siaran suara ini. + Memulai sebuah siaran suara \ No newline at end of file From c48185310ce7a4048a88f5c7fef85d0735c0a96b Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Sun, 15 Jan 2023 18:03:38 +0000 Subject: [PATCH 120/237] Translated using Weblate (Slovak) Currently translated at 100.0% (2592 of 2592 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- library/ui-strings/src/main/res/values-sk/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml index 67a953d810..45bc8b3729 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2960,4 +2960,6 @@ ukončil/a anketu. Ukončil/a anketu. Váš domovský server zatiaľ nepodporuje zobrazovanie vlákien. + Toto hlasové vysielanie nie je možné prehrať. + Spustil/a hlasové vysielanie \ No newline at end of file From f85c6180cd17866403cb54335a8dbb3fcfaaec36 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Sun, 15 Jan 2023 14:50:17 +0000 Subject: [PATCH 121/237] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2592 of 2592 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- library/ui-strings/src/main/res/values-uk/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml index c368160a78..081e34555d 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -3016,4 +3016,6 @@ завершує опитування. Опитування завершено. Ваш домашній сервер поки що не підтримує створення списків гілок. + Неможливо відтворити цю голосову трансляцію. + Розпочато голосову трансляцію \ No newline at end of file From 8224c44a981d768a5eac1e9c2bc95a0c404b7ff8 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 16 Jan 2023 01:57:46 +0000 Subject: [PATCH 122/237] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2592 of 2592 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- library/ui-strings/src/main/res/values-zh-rTW/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml index 18076ea61f..1144af1f9a 100644 --- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml @@ -2848,4 +2848,6 @@ 已結束投票。 已結束投票。 您的家伺服器還不支援列出討論串。 + 無法播放此語音廣播。 + 已開始語音廣播 \ No newline at end of file From 42227613633f585c906c3b9c18ad8c067fec73e9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Jan 2023 20:15:07 +0100 Subject: [PATCH 123/237] More cleanup --- .../internal/session/room/timeline/LoadTimelineStrategy.kt | 1 - .../sdk/internal/session/room/timeline/TimelineChunk.kt | 4 ---- .../session/room/timeline/decorator/UiEchoDecorator.kt | 4 ++-- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt index 9faf301fe0..dade15a36b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -360,7 +360,6 @@ internal class LoadTimelineStrategy constructor( initialEventId = mode.originEventId(), onBuiltEvents = dependencies.onEventsUpdated, onEventsDeleted = dependencies.onEventsDeleted, - realm = dependencies.realm, localEchoEventFactory = dependencies.localEchoEventFactory, decorator = createTimelineEventDecorator() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index c9785e7ea1..00b83f886c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.OrderedCollectionChangeSet import io.realm.OrderedRealmCollectionChangeListener -import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmObjectChangeListener import io.realm.RealmQuery @@ -48,7 +47,6 @@ import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenes import timber.log.Timber import java.util.Collections import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicReference /** * This is a wrapper around a ChunkEntity in the database. @@ -72,7 +70,6 @@ internal class TimelineChunk( private val initialEventId: String?, private val onBuiltEvents: (Boolean) -> Unit, private val onEventsDeleted: () -> Unit, - private val realm: AtomicReference, private val decorator: TimelineEventDecorator, val localEchoEventFactory: LocalEchoEventFactory, ) { @@ -616,7 +613,6 @@ internal class TimelineChunk( onBuiltEvents = this.onBuiltEvents, onEventsDeleted = this.onEventsDeleted, decorator = this.decorator, - realm = realm, localEchoEventFactory = localEchoEventFactory ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/decorator/UiEchoDecorator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/decorator/UiEchoDecorator.kt index 778a9d27d9..8a347ed35b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/decorator/UiEchoDecorator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/decorator/UiEchoDecorator.kt @@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.session.room.timeline.decorator import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.session.room.timeline.UIEchoManager -internal class UiEchoDecorator(private val uiEchoManager: UIEchoManager?) : TimelineEventDecorator { +internal class UiEchoDecorator(private val uiEchoManager: UIEchoManager) : TimelineEventDecorator { override fun decorate(timelineEvent: TimelineEvent): TimelineEvent { - return uiEchoManager?.decorateEventWithReactionUiEcho(timelineEvent) ?: timelineEvent + return uiEchoManager.decorateEventWithReactionUiEcho(timelineEvent) } } From a8d2f40475616269f4669e74589803510b027055 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 23:02:58 +0000 Subject: [PATCH 124/237] Bump flipper from 0.176.1 to 0.177.0 Bumps `flipper` from 0.176.1 to 0.177.0. Updates `flipper` from 0.176.1 to 0.177.0 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.176.1...v0.177.0) Updates `flipper-network-plugin` from 0.176.1 to 0.177.0 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.176.1...v0.177.0) --- updated-dependencies: - dependency-name: com.facebook.flipper:flipper dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.facebook.flipper:flipper-network-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index f6be35edc0..ff94b18943 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -18,7 +18,7 @@ def markwon = "4.6.2" def moshi = "1.14.0" def lifecycle = "2.5.1" def flowBinding = "1.2.0" -def flipper = "0.176.1" +def flipper = "0.177.0" def epoxy = "5.0.0" def mavericks = "3.0.1" def glide = "4.14.2" From bc4f1f1ec054c382e9f6355ec7b4279105ee046e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 23:03:48 +0000 Subject: [PATCH 125/237] Bump wysiwyg from 0.17.0 to 0.18.0 Bumps [wysiwyg](https://github.com/matrix-org/matrix-wysiwyg) from 0.17.0 to 0.18.0. - [Release notes](https://github.com/matrix-org/matrix-wysiwyg/releases) - [Changelog](https://github.com/matrix-org/matrix-rich-text-editor/blob/main/CHANGELOG.md) - [Commits](https://github.com/matrix-org/matrix-wysiwyg/compare/0.17.0...0.18.0) --- updated-dependencies: - dependency-name: io.element.android:wysiwyg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index f6be35edc0..03873f8d25 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -103,7 +103,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:0.17.0" + 'wysiwyg' : "io.element.android:wysiwyg:0.18.0" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", From 4c06bdc14e0c72d77b601e06c5906598e19d642b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Jan 2023 11:24:19 +0100 Subject: [PATCH 126/237] Avoid creating too many Job, which ends up to OOM --- .../vector/app/features/home/room/detail/TimelineFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 980e8ebac5..7b94508b37 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -797,7 +797,7 @@ class TimelineFragment : } // We use a custom layout for this menu item, so we need to set a ClickListener menu.findItem(R.id.open_matrix_apps)?.let { menuItem -> - menuItem.actionView?.debouncedClicks { + menuItem.actionView?.setOnClickListener { handleMenuItemSelected(menuItem) } } @@ -808,7 +808,7 @@ class TimelineFragment : // Custom thread notification menu item menu.findItem(R.id.menu_timeline_thread_list)?.let { menuItem -> - menuItem.actionView?.debouncedClicks { + menuItem.actionView?.setOnClickListener { handleMenuItemSelected(menuItem) } } From b3a50d4f689571c872ddcc251c84d7cd600b96dc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Jan 2023 15:08:20 +0100 Subject: [PATCH 127/237] Remove unused RealmConfiguration --- .../sdk/internal/session/room/timeline/DefaultTimeline.kt | 1 - .../sdk/internal/session/room/timeline/LoadTimelineStrategy.kt | 3 --- .../sdk/internal/session/room/timeline/TimelineChunk.kt | 3 --- 3 files changed, 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 3ce8ea658d..08ed59adc7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -102,7 +102,6 @@ internal class DefaultTimeline( realm = backgroundRealm, eventDecryptor = eventDecryptor, paginationTask = paginationTask, - realmConfiguration = realmConfiguration, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, fetchThreadTimelineTask = fetchThreadTimelineTask, getContextOfEventTask = getEventTask, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt index dade15a36b..6654eeadfc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.OrderedCollectionChangeSet import io.realm.OrderedRealmCollectionChangeListener import io.realm.Realm -import io.realm.RealmConfiguration import io.realm.RealmResults import io.realm.kotlin.createObject import io.realm.kotlin.executeTransactionAwait @@ -97,7 +96,6 @@ internal class LoadTimelineStrategy constructor( val realm: AtomicReference, val eventDecryptor: TimelineEventDecryptor, val paginationTask: PaginationTask, - val realmConfiguration: RealmConfiguration, val fetchThreadTimelineTask: FetchThreadTimelineTask, val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, val getContextOfEventTask: GetContextOfEventTask, @@ -351,7 +349,6 @@ internal class LoadTimelineStrategy constructor( fetchThreadTimelineTask = dependencies.fetchThreadTimelineTask, eventDecryptor = dependencies.eventDecryptor, paginationTask = dependencies.paginationTask, - realmConfiguration = dependencies.realmConfiguration, fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask, timelineEventMapper = dependencies.timelineEventMapper, uiEchoManager = uiEchoManager, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 00b83f886c..d04b98ef76 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.OrderedCollectionChangeSet import io.realm.OrderedRealmCollectionChangeListener -import io.realm.RealmConfiguration import io.realm.RealmObjectChangeListener import io.realm.RealmQuery import io.realm.RealmResults @@ -61,7 +60,6 @@ internal class TimelineChunk( private val fetchThreadTimelineTask: FetchThreadTimelineTask, private val eventDecryptor: TimelineEventDecryptor, private val paginationTask: PaginationTask, - private val realmConfiguration: RealmConfiguration, private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, private val timelineEventMapper: TimelineEventMapper, private val uiEchoManager: UIEchoManager?, @@ -602,7 +600,6 @@ internal class TimelineChunk( timelineId = timelineId, eventDecryptor = eventDecryptor, paginationTask = paginationTask, - realmConfiguration = realmConfiguration, fetchThreadTimelineTask = fetchThreadTimelineTask, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, timelineEventMapper = timelineEventMapper, From 960bb77c2f90a4729a0fb17833cb5f94e7629945 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 17 Jan 2023 15:32:44 +0100 Subject: [PATCH 128/237] Fix playback scrubbing not working if playback is stopped --- changelog.d/7961.bugfix | 1 + .../listening/VoiceBroadcastPlayerImpl.kt | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 changelog.d/7961.bugfix diff --git a/changelog.d/7961.bugfix b/changelog.d/7961.bugfix new file mode 100644 index 0000000000..86c53b46bb --- /dev/null +++ b/changelog.d/7961.bugfix @@ -0,0 +1 @@ + Voice Broadcast - Fix playback scrubbing not working if the playback is in a stopped state diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 2e1600e4e2..d18638fc28 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -182,15 +182,16 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } State.Buffering -> { - val nextItem = if (isLiveListening && playlist.currentSequence == null) { - // live listening, jump to the last item if playback has not started - playlist.lastOrNull() - } else { - // not live or playback already started, request next item - playlist.getNextItem() - } - if (nextItem != null) { - startPlayback(nextItem.startTime) + val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } + when { + // resume playback from the next sequence item + playlist.currentSequence != null -> playlist.getNextItem()?.let { startPlayback(it.startTime) } + // resume playback from the saved position, if any + savedPosition != null -> startPlayback(savedPosition) + // live listening, jump to the last item + isLiveListening -> playlist.lastOrNull()?.let { startPlayback(it.startTime) } + // start playback from the beginning + else -> startPlayback(0) } } is State.Error -> Unit From 479b573dbbb50467443b49e1acde79910e40897e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 11 Jan 2023 11:08:32 +0100 Subject: [PATCH 129/237] Adding changelog entry --- changelog.d/7864.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7864.wip diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip new file mode 100644 index 0000000000..703318ba2e --- /dev/null +++ b/changelog.d/7864.wip @@ -0,0 +1 @@ +[Poll] Load more UI mechanism From e8e94b51896d54ef0bf94160f93940a8c1ed6890 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 11 Jan 2023 14:41:25 +0100 Subject: [PATCH 130/237] Adding load more item at the end of the list of polls --- .../src/main/res/values/strings.xml | 1 + .../polls/list/RoomPollLoadMoreItem.kt | 42 +++++++++++++++++++ .../polls/list/RoomPollsController.kt | 11 +++++ .../polls/list/RoomPollsListFragment.kt | 6 +++ .../main/res/layout/item_poll_load_more.xml | 29 +++++++++++++ 5 files changed, 89 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollLoadMoreItem.kt create mode 100644 vector/src/main/res/layout/item_poll_load_more.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 9fd121b3f7..f6e2491674 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3201,6 +3201,7 @@ There are no active polls in this room Past polls There are no past polls in this room + Load more polls Share location diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollLoadMoreItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollLoadMoreItem.kt new file mode 100644 index 0000000000..a684fa5c9e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollLoadMoreItem.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.polls.list + +import android.widget.Button +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick + +@EpoxyModelClass +abstract class RoomPollLoadMoreItem : VectorEpoxyModel(R.layout.item_poll_load_more) { + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var clickListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.loadMoreButton.onClick(clickListener) + } + + class Holder : VectorEpoxyHolder() { + val loadMoreButton by bind