Merge remote-tracking branch 'origin/develop' into feature/fre/invite_people_action
This commit is contained in:
commit
44a3f48306
138 changed files with 1022 additions and 697 deletions
|
|
@ -35,7 +35,6 @@ import io.element.android.libraries.architecture.animation.rememberDefaultTransi
|
|||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(RoomScope::class)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@ package io.element.android.features.roomdetails.impl
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
|
@ -43,8 +45,8 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.roomdetails.blockuser.BlockUserDialogs
|
||||
import io.element.android.features.roomdetails.blockuser.BlockUserSection
|
||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs
|
||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberHeaderSection
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberMainActionsSection
|
||||
import io.element.android.libraries.architecture.isLoading
|
||||
|
|
@ -68,7 +70,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
|||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.ui.strings.R as StringR
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun RoomDetailsView(
|
||||
state: RoomDetailsState,
|
||||
|
|
@ -91,6 +93,7 @@ fun RoomDetailsView(
|
|||
) { padding ->
|
||||
Column(modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
when (state.roomType) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.blockuser
|
||||
package io.element.android.features.roomdetails.impl.blockuser
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Block
|
||||
|
|
@ -26,6 +26,7 @@ import androidx.compose.runtime.setValue
|
|||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -41,7 +42,7 @@ class RoomMemberListPresenter @Inject constructor(
|
|||
var roomMembers by remember { mutableStateOf<Async<RoomMembers>>(Async.Loading()) }
|
||||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||
var searchResults by remember {
|
||||
mutableStateOf<RoomMemberSearchResultState>(RoomMemberSearchResultState.NotSearching)
|
||||
mutableStateOf<SearchBarResultState<RoomMembers>>(SearchBarResultState.NotSearching())
|
||||
}
|
||||
var isSearchActive by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
|
|
@ -60,11 +61,11 @@ class RoomMemberListPresenter @Inject constructor(
|
|||
LaunchedEffect(searchQuery) {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
searchResults = if (searchQuery.isEmpty()) {
|
||||
RoomMemberSearchResultState.NotSearching
|
||||
SearchBarResultState.NotSearching()
|
||||
} else {
|
||||
val results = roomMemberListDataSource.search(searchQuery).groupBy { it.membership }
|
||||
if (results.isEmpty()) RoomMemberSearchResultState.NoResults
|
||||
else RoomMemberSearchResultState.Results(
|
||||
if (results.isEmpty()) SearchBarResultState.NoResults()
|
||||
else SearchBarResultState.Results(
|
||||
RoomMembers(
|
||||
invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(),
|
||||
joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList()).toImmutableList(),
|
||||
|
|
|
|||
|
|
@ -17,13 +17,14 @@
|
|||
package io.element.android.features.roomdetails.impl.members
|
||||
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class RoomMemberListState(
|
||||
val roomMembers: Async<RoomMembers>,
|
||||
val searchQuery: String,
|
||||
val searchResults: RoomMemberSearchResultState,
|
||||
val searchResults: SearchBarResultState<RoomMembers>,
|
||||
val isSearchActive: Boolean,
|
||||
val eventSink: (RoomMemberListEvents) -> Unit,
|
||||
)
|
||||
|
|
@ -32,14 +33,3 @@ data class RoomMembers(
|
|||
val invited: ImmutableList<RoomMember>,
|
||||
val joined: ImmutableList<RoomMember>
|
||||
)
|
||||
|
||||
sealed interface RoomMemberSearchResultState {
|
||||
/** No search results are available yet (e.g. because the user hasn't entered a (long enough) search term). */
|
||||
object NotSearching : RoomMemberSearchResultState
|
||||
|
||||
/** The search has completed, but no results were found. */
|
||||
object NoResults : RoomMemberSearchResultState
|
||||
|
||||
/** The search has completed, and some matching users were found. */
|
||||
data class Results(val results: RoomMembers) : RoomMemberSearchResultState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ package io.element.android.features.roomdetails.impl.members
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMemberListState> {
|
||||
override val values: Sequence<RoomMemberListState>
|
||||
|
|
@ -42,7 +42,7 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
|
|||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "@someone:matrix.org",
|
||||
searchResults = RoomMemberSearchResultState.Results(
|
||||
searchResults = SearchBarResultState.Results(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(aVictor()),
|
||||
joined = persistentListOf(anAlice()),
|
||||
|
|
@ -52,14 +52,14 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
|
|||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "something-with-no-results",
|
||||
searchResults = RoomMemberSearchResultState.NoResults
|
||||
searchResults = SearchBarResultState.NoResults()
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun aRoomMemberListState(
|
||||
roomMembers: Async<RoomMembers> = Async.Uninitialized,
|
||||
searchResults: RoomMemberSearchResultState = RoomMemberSearchResultState.NotSearching,
|
||||
searchResults: SearchBarResultState<RoomMembers> = SearchBarResultState.NotSearching(),
|
||||
) = RoomMemberListState(
|
||||
roomMembers = roomMembers,
|
||||
searchQuery = "",
|
||||
|
|
|
|||
|
|
@ -20,27 +20,20 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SearchBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
|
@ -59,10 +52,9 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
|||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBar
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
|
|
@ -71,6 +63,7 @@ import io.element.android.libraries.matrix.ui.components.MatrixUserRow
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
import io.element.android.libraries.ui.strings.R as StringR
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun RoomMemberListView(
|
||||
state: RoomMemberListState,
|
||||
|
|
@ -93,21 +86,20 @@ fun RoomMemberListView(
|
|||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(padding),
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
Column {
|
||||
RoomMemberSearchBar(
|
||||
query = state.searchQuery,
|
||||
state = state.searchResults,
|
||||
active = state.isSearchActive,
|
||||
placeHolderTitle = stringResource(StringR.string.common_search_for_someone),
|
||||
onActiveChanged = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) },
|
||||
onTextChanged = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) },
|
||||
onUserSelected = ::onUserSelected,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
RoomMemberSearchBar(
|
||||
query = state.searchQuery,
|
||||
state = state.searchResults,
|
||||
active = state.isSearchActive,
|
||||
placeHolderTitle = stringResource(StringR.string.common_search_for_someone),
|
||||
onActiveChanged = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) },
|
||||
onTextChanged = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) },
|
||||
onUserSelected = ::onUserSelected,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
if (!state.isSearchActive) {
|
||||
if (state.roomMembers is Async.Success) {
|
||||
|
|
@ -216,11 +208,10 @@ private fun RoomMemberListTopBar(
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun RoomMemberSearchBar(
|
||||
query: String,
|
||||
state: RoomMemberSearchResultState,
|
||||
state: SearchBarResultState<RoomMembers>,
|
||||
active: Boolean,
|
||||
placeHolderTitle: String,
|
||||
onActiveChanged: (Boolean) -> Unit,
|
||||
|
|
@ -228,72 +219,21 @@ private fun RoomMemberSearchBar(
|
|||
onUserSelected: (RoomMember) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (!active) {
|
||||
onTextChanged("")
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
|
||||
SearchBar(
|
||||
query = query,
|
||||
onQueryChange = onTextChanged,
|
||||
onSearch = { focusManager.clearFocus() },
|
||||
active = active,
|
||||
onActiveChange = onActiveChanged,
|
||||
modifier = modifier
|
||||
.padding(horizontal = if (!active) 16.dp else 0.dp),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = placeHolderTitle,
|
||||
modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine)
|
||||
modifier = modifier,
|
||||
placeHolderTitle = placeHolderTitle,
|
||||
resultState = state,
|
||||
resultHandler = { results ->
|
||||
RoomMemberList(
|
||||
roomMembers = results,
|
||||
showMembersCount = false,
|
||||
onUserSelected = { onUserSelected(it) }
|
||||
)
|
||||
},
|
||||
leadingIcon = if (active) {
|
||||
{ BackButton(onClick = { onActiveChanged(false) }) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
trailingIcon = when {
|
||||
active && query.isNotEmpty() -> {
|
||||
{
|
||||
IconButton(onClick = { onTextChanged("") }) {
|
||||
Icon(Icons.Default.Close, stringResource(StringR.string.action_clear))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
!active -> {
|
||||
{
|
||||
Icon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = stringResource(StringR.string.action_search),
|
||||
modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
},
|
||||
colors = if (!active) SearchBarDefaults.colors() else SearchBarDefaults.colors(containerColor = Color.Transparent),
|
||||
content = {
|
||||
if (state is RoomMemberSearchResultState.Results) {
|
||||
RoomMemberList(
|
||||
roomMembers = state.results,
|
||||
showMembersCount = false,
|
||||
onUserSelected = onUserSelected
|
||||
)
|
||||
} else if (state is RoomMemberSearchResultState.NoResults) {
|
||||
Spacer(Modifier.size(80.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(StringR.string.common_no_results),
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.tertiary,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@ package io.element.android.features.roomdetails.impl.members.details
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
|
@ -39,8 +41,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.roomdetails.blockuser.BlockUserDialogs
|
||||
import io.element.android.features.roomdetails.blockuser.BlockUserSection
|
||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs
|
||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection
|
||||
import io.element.android.libraries.designsystem.ElementTextStyles
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
|
|
@ -57,7 +59,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.R as StringR
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun RoomMemberDetailsView(
|
||||
state: RoomMemberDetailsState,
|
||||
|
|
@ -74,6 +76,7 @@ fun RoomMemberDetailsView(
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
RoomMemberHeaderSection(
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ import io.element.android.features.roomdetails.aMatrixRoom
|
|||
import io.element.android.features.roomdetails.impl.members.RoomMemberListDataSource
|
||||
import io.element.android.features.roomdetails.impl.members.RoomMemberListEvents
|
||||
import io.element.android.features.roomdetails.impl.members.RoomMemberListPresenter
|
||||
import io.element.android.features.roomdetails.impl.members.RoomMemberSearchResultState
|
||||
import io.element.android.features.roomdetails.impl.members.aRoomMemberList
|
||||
import io.element.android.features.roomdetails.impl.members.aVictor
|
||||
import io.element.android.features.roomdetails.impl.members.aWalter
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
|
|
@ -49,7 +49,7 @@ class RoomMemberListPresenterTests {
|
|||
val initialState = awaitItem()
|
||||
Truth.assertThat(initialState.roomMembers).isInstanceOf(Async.Loading::class.java)
|
||||
Truth.assertThat(initialState.searchQuery).isEmpty()
|
||||
Truth.assertThat(initialState.searchResults).isEqualTo(RoomMemberSearchResultState.NotSearching)
|
||||
Truth.assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java)
|
||||
Truth.assertThat(initialState.isSearchActive).isFalse()
|
||||
|
||||
val loadedState = awaitItem()
|
||||
|
|
@ -89,7 +89,7 @@ class RoomMemberListPresenterTests {
|
|||
val searchQueryUpdatedState = awaitItem()
|
||||
Truth.assertThat((searchQueryUpdatedState.searchQuery)).isEqualTo("something")
|
||||
val searchSearchResultDelivered = awaitItem()
|
||||
Truth.assertThat((searchSearchResultDelivered.searchResults)).isInstanceOf(RoomMemberSearchResultState.NoResults::class.java)
|
||||
Truth.assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.NoResults::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,8 +107,8 @@ class RoomMemberListPresenterTests {
|
|||
val searchQueryUpdatedState = awaitItem()
|
||||
Truth.assertThat((searchQueryUpdatedState.searchQuery)).isEqualTo("Alice")
|
||||
val searchSearchResultDelivered = awaitItem()
|
||||
Truth.assertThat((searchSearchResultDelivered.searchResults)).isInstanceOf(RoomMemberSearchResultState.Results::class.java)
|
||||
Truth.assertThat((searchSearchResultDelivered.searchResults as RoomMemberSearchResultState.Results).results.joined.first().displayName)
|
||||
Truth.assertThat((searchSearchResultDelivered.searchResults)).isInstanceOf(SearchBarResultState.Results::class.java)
|
||||
Truth.assertThat((searchSearchResultDelivered.searchResults as SearchBarResultState.Results).results.joined.first().displayName)
|
||||
.isEqualTo("Alice")
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue