From 69e180829084afe89793f41e8f6d5c6edea77879 Mon Sep 17 00:00:00 2001 From: Danny Seymour Date: Thu, 7 Apr 2022 02:45:10 -0700 Subject: [PATCH 01/54] fix: increase font size for messages On phones with a rather large screen, the given font sizing is small even for the default theme. Increasing the size helps with readability and reduces strain on the eyes. Signed-off-by: Danny Seymour --- changelog.d/5717.misc | 1 + .../features/home/room/detail/timeline/item/MessageTextItem.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/5717.misc diff --git a/changelog.d/5717.misc b/changelog.d/5717.misc new file mode 100644 index 0000000000..0b191e249a --- /dev/null +++ b/changelog.d/5717.misc @@ -0,0 +1 @@ +fix: increase font size for messages diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt index bc9e4a7ff1..3d00cac396 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -85,7 +85,7 @@ abstract class MessageTextItem : AbsMessageItem() { if (useBigFont) { holder.messageView.textSize = 44F } else { - holder.messageView.textSize = 14F + holder.messageView.textSize = 15.5F } if (searchForPills) { message?.charSequence?.findPillsAndProcess(coroutineScope) { From 61b83580132917e59d02f9d57cb9c40279f7e340 Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Mon, 10 Oct 2022 10:19:01 +0200 Subject: [PATCH 02/54] 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 03/54] 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 75d589bedd0dbff1467b807c343889d69154322a Mon Sep 17 00:00:00 2001 From: ByeongsuPark Date: Sat, 22 Oct 2022 00:15:44 +0900 Subject: [PATCH 04/54] Add trim to username input on sign-in Add trim to username input on the app side and SDK side Signed-off-by: ByeongsuPark --- changelog.d/7111.misc | 1 + .../android/sdk/internal/auth/login/DefaultLoginWizard.kt | 2 +- .../main/java/im/vector/app/features/login/LoginViewModel.kt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog.d/7111.misc diff --git a/changelog.d/7111.misc b/changelog.d/7111.misc new file mode 100644 index 0000000000..bb6d4ac689 --- /dev/null +++ b/changelog.d/7111.misc @@ -0,0 +1 @@ +Add trim to username input on the app side and SDK side when sign-in diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt index 468e998407..0a8c58de16 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt @@ -69,7 +69,7 @@ internal class DefaultLoginWizard( ) } else { PasswordLoginParams.userIdentifier( - user = login, + user = login.trim(), password = password, deviceDisplayName = initialDeviceName, deviceId = deviceId diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt index 79d06a0864..585597ab01 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt @@ -684,7 +684,7 @@ class LoginViewModel @AssistedInject constructor( currentJob = viewModelScope.launch { try { safeLoginWizard.login( - action.username, + action.username.trim(), action.password, action.initialDeviceName ) 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 05/54] 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 3f3db1abdaf4fd3370869b5938525bd81b42f77f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Dec 2022 23:01:56 +0000 Subject: [PATCH 06/54] Bump lazythreetenbp from 0.12.0 to 0.13.0 Bumps [lazythreetenbp](https://github.com/gabrielittner/lazythreetenbp) from 0.12.0 to 0.13.0. - [Release notes](https://github.com/gabrielittner/lazythreetenbp/releases) - [Changelog](https://github.com/gabrielittner/lazythreetenbp/blob/main/CHANGELOG.md) - [Commits](https://github.com/gabrielittner/lazythreetenbp/compare/0.12.0...0.13.0) --- updated-dependencies: - dependency-name: com.gabrielittner.threetenbp:lazythreetenbp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index 83af7ecc04..c9870ef2f4 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -132,7 +132,7 @@ dependencies { implementation libs.androidx.biometric api "org.threeten:threetenbp:1.4.0:no-tzdb" - api "com.gabrielittner.threetenbp:lazythreetenbp:0.12.0" + api "com.gabrielittner.threetenbp:lazythreetenbp:0.13.0" implementation libs.squareup.moshi kapt libs.squareup.moshiKotlin 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 07/54] 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 08/54] 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 09/54] 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 10/54] 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 11/54] 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 12/54] 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 13/54] 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 14/54] 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 15/54] 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 16/54] 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 17/54] 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 18/54] 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 19/54] 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 0511191e51cc83c8346496e1ed49264e0a726e10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jan 2023 23:04:16 +0000 Subject: [PATCH 20/54] Bump libphonenumber from 8.13.3 to 8.13.4 Bumps [libphonenumber](https://github.com/google/libphonenumber) from 8.13.3 to 8.13.4. - [Release notes](https://github.com/google/libphonenumber/releases) - [Changelog](https://github.com/google/libphonenumber/blob/master/making-metadata-changes.md) - [Commits](https://github.com/google/libphonenumber/compare/v8.13.3...v8.13.4) --- updated-dependencies: - dependency-name: com.googlecode.libphonenumber:libphonenumber 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..ee056c1e25 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -86,7 +86,7 @@ ext.libs = [ 'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution", 'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution", // Phone number https://github.com/google/libphonenumber - 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.3" + 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.4" ], dagger : [ 'dagger' : "com.google.dagger:dagger:$dagger", From 4860fae7c4e7c75c7e3478b8609fe2e4c3bca395 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 08:56:28 +0000 Subject: [PATCH 21/54] Bump oss-licenses-plugin from 0.10.5 to 0.10.6 Bumps oss-licenses-plugin from 0.10.5 to 0.10.6. --- updated-dependencies: - dependency-name: com.google.android.gms:oss-licenses-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cdbfcc44b7..1ebe910e80 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ buildscript { classpath 'com.google.firebase:firebase-appdistribution-gradle:3.1.1' 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.5' + classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6' classpath "com.likethesalad.android:stem-plugin:2.2.3" classpath 'org.owasp:dependency-check-gradle:7.4.4' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20" From 2b26f2b22161f305eca45e0f929a9ebeca3c538a Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 9 Jan 2023 16:00:24 +0300 Subject: [PATCH 22/54] 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 cceb1cd66cdb2b0c8594f0f3c6c7e05aeaaa1734 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Jan 2023 16:39:12 +0100 Subject: [PATCH 23/54] Add constraint on T. It has to extend `VectorViewEvents` --- .../main/java/im/vector/app/core/utils/SharedEvent.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt index e712769c48..779270c092 100644 --- a/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt +++ b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt @@ -16,16 +16,17 @@ package im.vector.app.core.utils +import im.vector.app.core.platform.VectorViewEvents import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.transform import java.util.concurrent.CopyOnWriteArraySet -interface SharedEvents { +interface SharedEvents { fun stream(consumerId: String): Flow } -class EventQueue(capacity: Int) : SharedEvents { +class EventQueue(capacity: Int) : SharedEvents { private val innerQueue = MutableSharedFlow>(replay = capacity) @@ -42,7 +43,7 @@ class EventQueue(capacity: Int) : SharedEvents { * * Keeps track of who has already handled its content. */ -private class OneTimeEvent(private val content: T) { +private class OneTimeEvent(private val content: T) { private val handlers = CopyOnWriteArraySet() @@ -53,6 +54,6 @@ private class OneTimeEvent(private val content: T) { fun getIfNotHandled(asker: String): T? = if (handlers.add(asker)) content else null } -private fun Flow>.filterNotHandledBy(consumerId: String): Flow = transform { event -> +private fun Flow>.filterNotHandledBy(consumerId: String): Flow = transform { event -> event.getIfNotHandled(consumerId)?.let { emit(it) } } From 6eece5b27064122bd3c6ab33588c1af260997e2d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Jan 2023 18:07:47 +0100 Subject: [PATCH 24/54] Remove the "device" field from get `/pushrules` response model. --- .../sdk/internal/session/pushers/GetPushRulesResponse.kt | 6 ------ .../sdk/internal/session/pushers/SavePushRulesTask.kt | 1 - 2 files changed, 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt index 5f35c919fc..e359410f17 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt @@ -30,10 +30,4 @@ internal data class GetPushRulesResponse( */ @Json(name = "global") val global: RuleSet, - - /** - * Device specific rules, apply only to current device. - */ - @Json(name = "device") - val device: RuleSet? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt index 88c78aa460..4a46f56a70 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt @@ -42,7 +42,6 @@ internal class DefaultSavePushRulesTask @Inject constructor(@SessionDatabase pri .findAll() .forEach { it.deleteOnCascade() } - // Save only global rules for the moment val globalRules = params.pushRules.global val content = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.CONTENT } From da7fcbcf8363b6a6b0068d998dc49e7ac58506b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 22:01:21 +0000 Subject: [PATCH 25/54] Bump git from 1.11.0 to 1.13.0 Bumps [git](https://github.com/ruby-git/ruby-git) from 1.11.0 to 1.13.0. - [Release notes](https://github.com/ruby-git/ruby-git/releases) - [Changelog](https://github.com/ruby-git/ruby-git/blob/master/CHANGELOG.md) - [Commits](https://github.com/ruby-git/ruby-git/compare/v1.11.0...v1.13.0) --- updated-dependencies: - dependency-name: git dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 276f4ae66a..33ebbc1b70 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -127,7 +127,8 @@ GEM xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) gh_inspector (1.1.3) - git (1.11.0) + git (1.13.0) + addressable (~> 2.8) rchardet (~> 1.8) google-apis-androidpublisher_v3 (0.25.0) google-apis-core (>= 0.7, < 2.a) 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 26/54] 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 02c61d3fb5ce650b9162a957b06f71a59667d34c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Jan 2023 10:39:49 +0100 Subject: [PATCH 27/54] Fix view event replay --- .../src/main/java/im/vector/app/core/utils/SharedEvent.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt index 779270c092..081a4f6192 100644 --- a/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt +++ b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt @@ -19,6 +19,7 @@ package im.vector.app.core.utils import im.vector.app.core.platform.VectorViewEvents import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.transform import java.util.concurrent.CopyOnWriteArraySet @@ -34,7 +35,12 @@ class EventQueue(capacity: Int) : SharedEvents { innerQueue.tryEmit(OneTimeEvent(event)) } - override fun stream(consumerId: String): Flow = innerQueue.filterNotHandledBy(consumerId) + override fun stream(consumerId: String): Flow = innerQueue + .onEach { + // Ensure that buffered Events will not be sent again to new subscribers. + innerQueue.resetReplayCache() + } + .filterNotHandledBy(consumerId) } /** From a8b111dc8c8a525b23537c1138eae08e23b5f9e8 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 10 Jan 2023 15:04:13 +0300 Subject: [PATCH 28/54] 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 29/54] 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 30/54] 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 a11c74d21cb2eba470191448caed642c85bcc822 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Jan 2023 16:21:33 +0100 Subject: [PATCH 31/54] Changelog for version 1.5.20 --- CHANGES.md | 27 +++++++++++++++++++++++++++ changelog.d/5546.bugfix | 1 - changelog.d/5717.misc | 1 - changelog.d/7111.misc | 1 - changelog.d/7724.bugfix | 1 - changelog.d/7853.bugfix | 1 - changelog.d/7864.wip | 2 -- changelog.d/7879.bugfix | 1 - changelog.d/7887.feature | 1 - changelog.d/7899.bugfix | 1 - changelog.d/7913.bugfix | 1 - 11 files changed, 27 insertions(+), 11 deletions(-) delete mode 100644 changelog.d/5546.bugfix delete mode 100644 changelog.d/5717.misc delete mode 100644 changelog.d/7111.misc delete mode 100644 changelog.d/7724.bugfix delete mode 100644 changelog.d/7853.bugfix delete mode 100644 changelog.d/7864.wip delete mode 100644 changelog.d/7879.bugfix delete mode 100644 changelog.d/7887.feature delete mode 100644 changelog.d/7899.bugfix delete mode 100644 changelog.d/7913.bugfix diff --git a/CHANGES.md b/CHANGES.md index e742d79c1e..15b0a76b23 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,30 @@ +Changes in Element v1.5.20 (2023-01-10) +======================================= + +Features ✨ +---------- + - "[Rich text editor] Add list formatting buttons to the rich text editor" ([#7887](https://github.com/vector-im/element-android/issues/7887)) + +Bugfixes 🐛 +---------- + - ReplyTo are not updated if the original message is edited or deleted. ([#5546](https://github.com/vector-im/element-android/issues/5546)) + - Observe ViewEvents only when resumed and ensure ViewEvents are not lost. ([#7724](https://github.com/vector-im/element-android/issues/7724)) + - [Session manager] Missing info when a session does not support encryption ([#7853](https://github.com/vector-im/element-android/issues/7853)) + - Reduce number of crypto database transactions when handling the sync response ([#7879](https://github.com/vector-im/element-android/issues/7879)) + - [Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number ([#7899](https://github.com/vector-im/element-android/issues/7899)) + - Handle network error on API `rooms/{roomId}/threads` ([#7913](https://github.com/vector-im/element-android/issues/7913)) + +In development 🚧 +---------------- + - [Poll] Render active polls list of a room + - [Poll] Render past polls list of a room ([#7864](https://github.com/vector-im/element-android/issues/7864)) + +Other changes +------------- + - fix: increase font size for messages ([#5717](https://github.com/vector-im/element-android/issues/5717)) + - Add trim to username input on the app side and SDK side when sign-in ([#7111](https://github.com/vector-im/element-android/issues/7111)) + + Changes in Element v1.5.18 (2023-01-02) ======================================= diff --git a/changelog.d/5546.bugfix b/changelog.d/5546.bugfix deleted file mode 100644 index a3ff48a4a2..0000000000 --- a/changelog.d/5546.bugfix +++ /dev/null @@ -1 +0,0 @@ -ReplyTo are not updated if the original message is edited or deleted. diff --git a/changelog.d/5717.misc b/changelog.d/5717.misc deleted file mode 100644 index 0b191e249a..0000000000 --- a/changelog.d/5717.misc +++ /dev/null @@ -1 +0,0 @@ -fix: increase font size for messages diff --git a/changelog.d/7111.misc b/changelog.d/7111.misc deleted file mode 100644 index bb6d4ac689..0000000000 --- a/changelog.d/7111.misc +++ /dev/null @@ -1 +0,0 @@ -Add trim to username input on the app side and SDK side when sign-in diff --git a/changelog.d/7724.bugfix b/changelog.d/7724.bugfix deleted file mode 100644 index 685f7ad4e2..0000000000 --- a/changelog.d/7724.bugfix +++ /dev/null @@ -1 +0,0 @@ - Observe ViewEvents only when resumed and ensure ViewEvents are not lost. diff --git a/changelog.d/7853.bugfix b/changelog.d/7853.bugfix deleted file mode 100644 index 885233553e..0000000000 --- a/changelog.d/7853.bugfix +++ /dev/null @@ -1 +0,0 @@ -[Session manager] Missing info when a session does not support encryption diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip deleted file mode 100644 index e1187ee1e7..0000000000 --- a/changelog.d/7864.wip +++ /dev/null @@ -1,2 +0,0 @@ -[Poll] Render active polls list of a room -[Poll] Render past polls list of a room diff --git a/changelog.d/7879.bugfix b/changelog.d/7879.bugfix deleted file mode 100644 index be828ec2cc..0000000000 --- a/changelog.d/7879.bugfix +++ /dev/null @@ -1 +0,0 @@ -Reduce number of crypto database transactions when handling the sync response diff --git a/changelog.d/7887.feature b/changelog.d/7887.feature deleted file mode 100644 index 1f1c29761a..0000000000 --- a/changelog.d/7887.feature +++ /dev/null @@ -1 +0,0 @@ -"[Rich text editor] Add list formatting buttons to the rich text editor" \ No newline at end of file diff --git a/changelog.d/7899.bugfix b/changelog.d/7899.bugfix deleted file mode 100644 index d95af29d8d..0000000000 --- a/changelog.d/7899.bugfix +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number diff --git a/changelog.d/7913.bugfix b/changelog.d/7913.bugfix deleted file mode 100644 index 32b821f14d..0000000000 --- a/changelog.d/7913.bugfix +++ /dev/null @@ -1 +0,0 @@ -Handle network error on API `rooms/{roomId}/threads` From b0cb694f813c110101f6d0787f4ae37d0d86a749 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Jan 2023 16:21:56 +0100 Subject: [PATCH 32/54] Adding fastlane file for version 1.5.20 --- fastlane/metadata/android/en-US/changelogs/40105200.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40105200.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40105200.txt b/fastlane/metadata/android/en-US/changelogs/40105200.txt new file mode 100644 index 0000000000..6f549d094a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Main changes in this version: Mainly bugfixing! +Full changelog: https://github.com/vector-im/element-android/releases From b4e6656c42f36924bf4e504f3a1c460d86a23095 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Jan 2023 16:24:24 +0100 Subject: [PATCH 33/54] 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 34/54] 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 35/54] 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 36/54] 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 37/54] [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 38/54] 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 39/54] 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 40/54] 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 41/54] 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 42/54] 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 43/54] 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 44/54] 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 45/54] 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 46/54] 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 47/54] 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 f2d183520d1c43f259e6dbf40dce9aa90173c5ea Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Jan 2023 15:18:28 +0100 Subject: [PATCH 48/54] 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 49/54] 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 50/54] 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 51/54] 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 52/54] 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 53/54] 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 54/54] 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 } }