Room directory : change names and adapt ui

This commit is contained in:
ganfra 2024-03-21 17:22:00 +01:00
parent b0894fcd11
commit fa7a889a3f
10 changed files with 176 additions and 104 deletions

View file

@ -26,7 +26,7 @@ import com.bumble.appyx.navmodel.backstack.BackStack
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.roomdirectory.impl.search.RoomDirectorySearchNode
import io.element.android.features.roomdirectory.impl.root.RoomDirectoryNode
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.createNode
@ -54,7 +54,7 @@ class RoomDirectoryFlowNode @AssistedInject constructor(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.Root -> {
createNode<RoomDirectorySearchNode>(buildContext)
createNode<RoomDirectoryNode>(buildContext)
}
}
}

View file

@ -14,12 +14,13 @@
* limitations under the License.
*/
package io.element.android.features.roomdirectory.impl.search
package io.element.android.features.roomdirectory.impl.root
import io.element.android.libraries.matrix.api.core.RoomId
sealed interface RoomDirectorySearchEvents {
data class Search(val query: String) : RoomDirectorySearchEvents
data object LoadMore : RoomDirectorySearchEvents
data class JoinRoom(val roomId: RoomId) : RoomDirectorySearchEvents
sealed interface RoomDirectoryEvents {
data class Search(val query: String) : RoomDirectoryEvents
data object LoadMore : RoomDirectoryEvents
data class JoinRoom(val roomId: RoomId) : RoomDirectoryEvents
data class SearchActiveChange(val isActive: Boolean) : RoomDirectoryEvents
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.roomdirectory.impl.search
package io.element.android.features.roomdirectory.impl.root
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -27,16 +27,16 @@ import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.di.SessionScope
@ContributesNode(SessionScope::class)
class RoomDirectorySearchNode @AssistedInject constructor(
class RoomDirectoryNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: RoomDirectorySearchPresenter,
private val presenter: RoomDirectoryPresenter,
) : Node(buildContext, plugins = plugins) {
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
RoomDirectorySearchView(
RoomDirectoryView(
state = state,
onBackPressed = ::navigateUp,
modifier = modifier

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.roomdirectory.impl.search
package io.element.android.features.roomdirectory.impl.root
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -24,27 +24,31 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import io.element.android.features.roomdirectory.impl.search.datasource.RoomDirectorySearchDataSource
import io.element.android.features.roomdirectory.impl.root.datasource.RoomDirectoryDataSource
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.core.RoomId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
class RoomDirectorySearchPresenter @Inject constructor(
class RoomDirectoryPresenter @Inject constructor(
private val client: MatrixClient,
private val dataSource: RoomDirectorySearchDataSource,
) : Presenter<RoomDirectorySearchState> {
private val dataSource: RoomDirectoryDataSource,
) : Presenter<RoomDirectoryState> {
@Composable
override fun present(): RoomDirectorySearchState {
override fun present(): RoomDirectoryState {
var searchQuery by rememberSaveable {
mutableStateOf("")
}
var isSearchActive by rememberSaveable {
mutableStateOf(false)
}
val results by dataSource.searchResults.collectAsState()
val roomSummaries by dataSource.all.collectAsState()
val coroutineScope = rememberCoroutineScope()
@ -52,25 +56,33 @@ class RoomDirectorySearchPresenter @Inject constructor(
dataSource.updateSearchQuery(searchQuery)
}
fun handleEvents(event: RoomDirectorySearchEvents) {
fun handleEvents(event: RoomDirectoryEvents) {
when (event) {
is RoomDirectorySearchEvents.JoinRoom -> {
is RoomDirectoryEvents.JoinRoom -> {
coroutineScope.joinRoom(event.roomId)
}
RoomDirectorySearchEvents.LoadMore -> {
RoomDirectoryEvents.LoadMore -> {
coroutineScope.launch {
dataSource.loadMore()
}
}
is RoomDirectorySearchEvents.Search -> {
is RoomDirectoryEvents.Search -> {
searchQuery = event.query
}
is RoomDirectoryEvents.SearchActiveChange -> {
isSearchActive = event.isActive
if (!isSearchActive) {
searchQuery = ""
}
}
}
}
return RoomDirectorySearchState(
return RoomDirectoryState(
query = searchQuery,
results = results,
isSearchActive = isSearchActive,
roomSummaries = roomSummaries,
searchResults = SearchBarResultState.Initial(),
eventSink = ::handleEvents
)
}

View file

@ -14,13 +14,16 @@
* limitations under the License.
*/
package io.element.android.features.roomdirectory.impl.search
package io.element.android.features.roomdirectory.impl.root
import io.element.android.features.roomdirectory.impl.search.model.RoomDirectorySearchResult
import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import kotlinx.collections.immutable.ImmutableList
data class RoomDirectorySearchState(
data class RoomDirectoryState(
val query: String,
val results: ImmutableList<RoomDirectorySearchResult>,
val eventSink: (RoomDirectorySearchEvents) -> Unit
val roomSummaries: ImmutableList<RoomDirectoryRoomSummary>,
val searchResults: SearchBarResultState<ImmutableList<RoomDirectoryRoomSummary>>,
val isSearchActive: Boolean,
val eventSink: (RoomDirectoryEvents) -> Unit
)

View file

@ -14,24 +14,25 @@
* limitations under the License.
*/
package io.element.android.features.roomdirectory.impl.search
package io.element.android.features.roomdirectory.impl.root
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.roomdirectory.impl.search.model.RoomDirectorySearchResult
import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
open class RoomDirectorySearchStateProvider : PreviewParameterProvider<RoomDirectorySearchState> {
override val values: Sequence<RoomDirectorySearchState>
open class RoomDirectorySearchStateProvider : PreviewParameterProvider<RoomDirectoryState> {
override val values: Sequence<RoomDirectoryState>
get() = sequenceOf(
aRoomDirectorySearchState(),
aRoomDirectorySearchState(
aRoomDirectoryState(),
aRoomDirectoryState(
query = "Element",
results = persistentListOf(
RoomDirectorySearchResult(
roomSummaries = persistentListOf(
RoomDirectoryRoomSummary(
roomId = RoomId("@exa:matrix.org"),
name = "Element X Android",
description = "Element X is a secure, private and decentralized messenger.",
@ -43,7 +44,7 @@ open class RoomDirectorySearchStateProvider : PreviewParameterProvider<RoomDirec
),
canBeJoined = true,
),
RoomDirectorySearchResult(
RoomDirectoryRoomSummary(
roomId = RoomId("@exi:matrix.org"),
name = "Element X iOS",
description = "Element X is a secure, private and decentralized messenger.",
@ -60,11 +61,15 @@ open class RoomDirectorySearchStateProvider : PreviewParameterProvider<RoomDirec
)
}
fun aRoomDirectorySearchState(
fun aRoomDirectoryState(
query: String = "",
results: ImmutableList<RoomDirectorySearchResult> = persistentListOf(),
) = RoomDirectorySearchState(
isSearchActive: Boolean = false,
roomSummaries: ImmutableList<RoomDirectoryRoomSummary> = persistentListOf(),
searchResults: SearchBarResultState<ImmutableList<RoomDirectoryRoomSummary>> = SearchBarResultState.Initial(),
) = RoomDirectoryState(
query = query,
results = results,
eventSink = { }
isSearchActive = isSearchActive,
roomSummaries = roomSummaries,
searchResults = searchResults,
eventSink = {},
)

View file

@ -14,14 +14,16 @@
* limitations under the License.
*/
package io.element.android.features.roomdirectory.impl.search
package io.element.android.features.roomdirectory.impl.root
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
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
import androidx.compose.foundation.layout.padding
@ -29,10 +31,8 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -47,46 +47,39 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.roomdirectory.impl.search.model.RoomDirectorySearchResult
import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
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.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
@Composable
fun RoomDirectorySearchView(
state: RoomDirectorySearchState,
fun RoomDirectoryView(
state: RoomDirectoryState,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
) {
fun onQueryChanged(query: String) {
state.eventSink(RoomDirectorySearchEvents.Search(query))
}
Scaffold(
modifier = modifier,
topBar = {
RoomDirectorySearchTopBar(
query = state.query,
onQueryChanged = ::onQueryChanged,
onBackPressed = onBackPressed,
)
RoomDirectoryTopBar(onBackPressed = onBackPressed)
},
content = { padding ->
RoomDirectorySearchContent(
RoomDirectoryContent(
state = state,
onResultClicked = { roomId ->
state.eventSink(RoomDirectorySearchEvents.JoinRoom(roomId))
state.eventSink(RoomDirectoryEvents.JoinRoom(roomId))
},
modifier = Modifier
.padding(padding)
@ -96,16 +89,75 @@ fun RoomDirectorySearchView(
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun RoomDirectorySearchContent(
state: RoomDirectorySearchState,
private fun RoomDirectoryTopBar(
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
) {
TopAppBar(
modifier = modifier,
navigationIcon = {
BackButton(onClick = onBackPressed)
},
title = {
Text(
text = "Room directory",
style = ElementTheme.typography.aliasScreenTitle,
)
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun RoomDirectoryContent(
state: RoomDirectoryState,
onResultClicked: (RoomId) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(modifier = modifier) {
items(state.results) { result ->
RoomDirectorySearchResultRow(
result = result,
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
SearchBar(
query = state.query,
onQueryChange = { query ->
state.eventSink(RoomDirectoryEvents.Search(query))
},
active = state.isSearchActive,
onActiveChange = {
state.eventSink(RoomDirectoryEvents.SearchActiveChange(it))
},
resultState = state.searchResults,
placeHolderTitle = stringResource(id = CommonStrings.action_search),
) { results ->
RoomDirectoryRoomList(
rooms = results,
onResultClicked = onResultClicked,
)
}
if (!state.isSearchActive) {
RoomDirectoryRoomList(
rooms = state.roomSummaries,
onResultClicked = onResultClicked,
)
}
}
}
@Composable
private fun RoomDirectoryRoomList(
rooms: ImmutableList<RoomDirectoryRoomSummary>,
onResultClicked: (RoomId) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(
modifier = modifier,
) {
items(rooms) { room ->
RoomDirectoryRoomRow(
room = room,
onClick = onResultClicked,
)
}
@ -113,15 +165,15 @@ private fun RoomDirectorySearchContent(
}
@Composable
private fun RoomDirectorySearchResultRow(
result: RoomDirectorySearchResult,
private fun RoomDirectoryRoomRow(
room: RoomDirectoryRoomSummary,
onClick: (RoomId) -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable { onClick(result.roomId) }
.clickable { onClick(room.roomId) }
.padding(
top = 12.dp,
bottom = 12.dp,
@ -130,7 +182,7 @@ private fun RoomDirectorySearchResultRow(
.height(IntrinsicSize.Min),
) {
Avatar(
avatarData = result.avatarData,
avatarData = room.avatarData,
modifier = Modifier.align(Alignment.CenterVertically)
)
Column(
@ -139,30 +191,28 @@ private fun RoomDirectorySearchResultRow(
.padding(start = 16.dp)
) {
Text(
text = result.name,
text = room.name,
maxLines = 1,
style = ElementTheme.typography.fontBodyLgRegular,
color = ElementTheme.colors.textPrimary,
overflow = TextOverflow.Ellipsis,
)
Text(
text = result.description,
text = room.description,
maxLines = 1,
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,
overflow = TextOverflow.Ellipsis,
)
}
if (result.canBeJoined) {
CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textSuccessPrimary) {
TextButton(
text = stringResource(id = CommonStrings.action_join),
onClick = { onClick(result.roomId) },
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(start = 4.dp, end = 12.dp)
)
}
if (room.canBeJoined) {
Text(
text = stringResource(id = CommonStrings.action_join),
color = ElementTheme.colors.textSuccessPrimary,
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(start = 4.dp, end = 12.dp)
)
} else {
Spacer(modifier = Modifier.width(24.dp))
}
@ -229,8 +279,8 @@ private fun RoomDirectorySearchTopBar(
@PreviewsDayNight
@Composable
fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectorySearchState) = ElementPreview {
RoomDirectorySearchView(
fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectoryState) = ElementPreview {
RoomDirectoryView(
state = state,
onBackPressed = {},
)

View file

@ -14,24 +14,23 @@
* limitations under the License.
*/
package io.element.android.features.roomdirectory.impl.search.datasource
package io.element.android.features.roomdirectory.impl.root.datasource
import io.element.android.features.roomdirectory.impl.search.model.RoomDirectorySearchResult
import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
import javax.inject.Inject
class RoomDirectorySearchDataSource @Inject constructor(
) {
private val _searchResults = MutableStateFlow<ImmutableList<RoomDirectorySearchResult>>(persistentListOf())
class RoomDirectoryDataSource @Inject constructor() {
private val _searchResults = MutableStateFlow<ImmutableList<RoomDirectoryRoomSummary>>(persistentListOf())
suspend fun updateSearchQuery(searchQuery: String) {
//TODO branch to matrix sdk
@ -39,7 +38,9 @@ class RoomDirectorySearchDataSource @Inject constructor(
_searchResults.value = persistentListOf()
} else {
delay(100)
emitFakeResults()
_searchResults.value = all.value.filter {
it.name.contains(searchQuery)
}.toImmutableList()
}
}
@ -47,9 +48,10 @@ class RoomDirectorySearchDataSource @Inject constructor(
//TODO branch to matrix sdk
}
private fun emitFakeResults() {
_searchResults.value = persistentListOf(
RoomDirectorySearchResult(
val all: StateFlow<ImmutableList<RoomDirectoryRoomSummary>> = MutableStateFlow(
persistentListOf(
RoomDirectoryRoomSummary(
roomId = RoomId("!exa:matrix.org"),
name = "Element X Android",
description = "Element X is a secure, private and decentralized messenger.",
@ -61,7 +63,7 @@ class RoomDirectorySearchDataSource @Inject constructor(
),
canBeJoined = true,
),
RoomDirectorySearchResult(
RoomDirectoryRoomSummary(
roomId = RoomId("!exi:matrix.org"),
name = "Element X iOS",
description = "Element X is a secure, private and decentralized messenger.",
@ -74,7 +76,6 @@ class RoomDirectorySearchDataSource @Inject constructor(
canBeJoined = false,
)
)
}
val searchResults: StateFlow<ImmutableList<RoomDirectorySearchResult>> = _searchResults
)
val searchResults: StateFlow<ImmutableList<RoomDirectoryRoomSummary>> = _searchResults
}

View file

@ -14,12 +14,12 @@
* limitations under the License.
*/
package io.element.android.features.roomdirectory.impl.search.model
package io.element.android.features.roomdirectory.impl.root.model
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.RoomId
data class RoomDirectorySearchResult(
data class RoomDirectoryRoomSummary(
val roomId: RoomId,
val name: String,
val description: String,

View file

@ -208,7 +208,7 @@ private fun RoomDirectorySearchButton(
modifier: Modifier = Modifier
) {
Button(
text = "Room directory",
text = "Browse all rooms",
leadingIcon = IconSource.Vector(CompoundIcons.ListBulleted()),
onClick = onClick,
modifier = modifier,