Ensure that room where is user is invited are not listed when forwarding a message.
This commit is contained in:
committed by
Benoit Marty
parent
ff697f68fc
commit
417492683d
@@ -19,6 +19,7 @@ package io.element.android.libraries.roomselect.impl
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -28,17 +29,14 @@ import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
class RoomSelectPresenter @AssistedInject constructor(
|
||||
@Assisted private val mode: RoomSelectMode,
|
||||
private val client: MatrixClient,
|
||||
private val dataSource: RoomSelectSearchDataSource,
|
||||
) : Presenter<RoomSelectState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
@@ -48,22 +46,26 @@ class RoomSelectPresenter @AssistedInject constructor(
|
||||
@Composable
|
||||
override fun present(): RoomSelectState {
|
||||
var selectedRooms by remember { mutableStateOf(persistentListOf<RoomSummaryDetails>()) }
|
||||
var query by remember { mutableStateOf("") }
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
var isSearchActive by remember { mutableStateOf(false) }
|
||||
var results: SearchBarResultState<ImmutableList<RoomSummaryDetails>> by remember { mutableStateOf(SearchBarResultState.Initial()) }
|
||||
|
||||
val summaries by client.roomListService.allRooms.summaries.collectAsState(initial = emptyList())
|
||||
LaunchedEffect(Unit) {
|
||||
dataSource.load()
|
||||
}
|
||||
|
||||
LaunchedEffect(query, summaries) {
|
||||
val filteredSummaries = summaries.filterIsInstance<RoomSummary.Filled>()
|
||||
.map { it.details }
|
||||
.filter { it.name.orEmpty().contains(query, ignoreCase = true) }
|
||||
.distinctBy { it.roomId } // This should be removed once we're sure no duplicate Rooms can be received
|
||||
.toPersistentList()
|
||||
results = if (filteredSummaries.isNotEmpty()) {
|
||||
SearchBarResultState.Results(filteredSummaries)
|
||||
} else {
|
||||
SearchBarResultState.NoResultsFound()
|
||||
LaunchedEffect(searchQuery) {
|
||||
dataSource.setSearchQuery(searchQuery)
|
||||
}
|
||||
|
||||
val roomSummaryDetailsList by dataSource.roomSummaries.collectAsState(initial = persistentListOf())
|
||||
|
||||
val searchResults by remember {
|
||||
derivedStateOf {
|
||||
when {
|
||||
roomSummaryDetailsList.isNotEmpty() -> SearchBarResultState.Results(roomSummaryDetailsList.toImmutableList())
|
||||
isSearchActive -> SearchBarResultState.NoResultsFound()
|
||||
else -> SearchBarResultState.Initial()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,15 +82,15 @@ class RoomSelectPresenter @AssistedInject constructor(
|
||||
// }
|
||||
}
|
||||
RoomSelectEvents.RemoveSelectedRoom -> selectedRooms = persistentListOf()
|
||||
is RoomSelectEvents.UpdateQuery -> query = event.query
|
||||
is RoomSelectEvents.UpdateQuery -> searchQuery = event.query
|
||||
RoomSelectEvents.ToggleSearchActive -> isSearchActive = !isSearchActive
|
||||
}
|
||||
}
|
||||
|
||||
return RoomSelectState(
|
||||
mode = mode,
|
||||
resultState = results,
|
||||
query = query,
|
||||
resultState = searchResults,
|
||||
query = searchQuery,
|
||||
isSearchActive = isSearchActive,
|
||||
selectedRooms = selectedRooms,
|
||||
eventSink = { handleEvents(it) }
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.libraries.roomselect.impl
|
||||
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val PAGE_SIZE = 30
|
||||
|
||||
class RoomSelectSearchDataSource @Inject constructor(
|
||||
roomListService: RoomListService,
|
||||
coroutineDispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
private val roomList = roomListService.createRoomList(
|
||||
pageSize = PAGE_SIZE,
|
||||
initialFilter = RoomListFilter.all(),
|
||||
source = RoomList.Source.All,
|
||||
)
|
||||
|
||||
val roomSummaries: Flow<PersistentList<RoomSummaryDetails>> = roomList.filteredSummaries
|
||||
.map { roomSummaries ->
|
||||
roomSummaries
|
||||
.filterIsInstance<RoomSummary.Filled>()
|
||||
.map { it.details }
|
||||
.filter { it.currentUserMembership == CurrentUserMembership.JOINED }
|
||||
.distinctBy { it.roomId } // This should be removed once we're sure no duplicate Rooms can be received
|
||||
.toPersistentList()
|
||||
}
|
||||
.flowOn(coroutineDispatchers.computation)
|
||||
|
||||
suspend fun load() = coroutineScope {
|
||||
roomList.loadAllIncrementally(this)
|
||||
}
|
||||
|
||||
suspend fun setSearchQuery(searchQuery: String) = coroutineScope {
|
||||
val filter = if (searchQuery.isBlank()) {
|
||||
RoomListFilter.all()
|
||||
} else {
|
||||
RoomListFilter.NormalizedMatchRoomName(searchQuery)
|
||||
}
|
||||
roomList.updateFilter(filter)
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,16 @@ import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummaryDetails
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.roomselect.api.RoomSelectMode
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -38,7 +41,7 @@ class RoomSelectPresenterTest {
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = aPresenter()
|
||||
val presenter = createRoomSelectPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -46,24 +49,18 @@ class RoomSelectPresenterTest {
|
||||
assertThat(initialState.selectedRooms).isEmpty()
|
||||
assertThat(initialState.resultState).isInstanceOf(SearchBarResultState.Initial::class.java)
|
||||
assertThat(initialState.isSearchActive).isFalse()
|
||||
// Search is run automatically
|
||||
val searchState = awaitItem()
|
||||
assertThat(searchState.resultState).isInstanceOf(SearchBarResultState.NoResultsFound::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - toggle search active`() = runTest {
|
||||
val presenter = aPresenter()
|
||||
val presenter = createRoomSelectPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
skipItems(1)
|
||||
|
||||
initialState.eventSink(RoomSelectEvents.ToggleSearchActive)
|
||||
assertThat(awaitItem().isSearchActive).isTrue()
|
||||
|
||||
initialState.eventSink(RoomSelectEvents.ToggleSearchActive)
|
||||
assertThat(awaitItem().isSearchActive).isFalse()
|
||||
}
|
||||
@@ -74,43 +71,59 @@ class RoomSelectPresenterTest {
|
||||
val roomListService = FakeRoomListService().apply {
|
||||
postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetails())))
|
||||
}
|
||||
val client = FakeMatrixClient(roomListService = roomListService)
|
||||
val presenter = aPresenter(client = client)
|
||||
val presenter = createRoomSelectPresenter(
|
||||
roomListService = roomListService
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(awaitItem().resultState as? SearchBarResultState.Results).isEqualTo(SearchBarResultState.Results(listOf(aRoomSummaryDetails())))
|
||||
|
||||
initialState.eventSink(RoomSelectEvents.ToggleSearchActive)
|
||||
skipItems(1)
|
||||
initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained"))
|
||||
assertThat(
|
||||
roomListService.allRooms.currentFilter.value
|
||||
).isEqualTo(
|
||||
RoomListFilter.NormalizedMatchRoomName("string not contained")
|
||||
)
|
||||
assertThat(awaitItem().query).isEqualTo("string not contained")
|
||||
roomListService.postAllRooms(
|
||||
emptyList()
|
||||
)
|
||||
assertThat(awaitItem().resultState).isInstanceOf(SearchBarResultState.NoResultsFound::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - select and remove a room`() = runTest {
|
||||
val presenter = aPresenter()
|
||||
val roomListService = FakeRoomListService().apply {
|
||||
postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetails())))
|
||||
}
|
||||
val presenter = createRoomSelectPresenter(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
skipItems(1)
|
||||
val summary = aRoomSummaryDetails()
|
||||
|
||||
initialState.eventSink(RoomSelectEvents.SetSelectedRoom(summary))
|
||||
assertThat(awaitItem().selectedRooms).isEqualTo(persistentListOf(summary))
|
||||
|
||||
initialState.eventSink(RoomSelectEvents.RemoveSelectedRoom)
|
||||
assertThat(awaitItem().selectedRooms).isEmpty()
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun aPresenter(
|
||||
private fun TestScope.createRoomSelectPresenter(
|
||||
mode: RoomSelectMode = RoomSelectMode.Forward,
|
||||
client: FakeMatrixClient = FakeMatrixClient(),
|
||||
roomListService: RoomListService = FakeRoomListService(),
|
||||
) = RoomSelectPresenter(
|
||||
mode = mode,
|
||||
client = client,
|
||||
dataSource = RoomSelectSearchDataSource(
|
||||
roomListService = roomListService,
|
||||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user