Skip to content

Commit

Permalink
Merge pull request #2674 from element-hq/feature/bma/roomSuggestion
Browse files Browse the repository at this point in the history
Room / User suggestions
  • Loading branch information
bmarty authored Apr 8, 2024
2 parents 8128a59 + 16d289e commit 014061f
Show file tree
Hide file tree
Showing 34 changed files with 573 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
Expand All @@ -61,6 +62,7 @@ class RoomLoadedFlowNode @AssistedInject constructor(
private val roomDetailsEntryPoint: RoomDetailsEntryPoint,
private val appNavigationStateService: AppNavigationStateService,
private val appCoroutineScope: CoroutineScope,
private val matrixClient: MatrixClient,
roomComponentFactory: RoomComponentFactory,
roomMembershipObserver: RoomMembershipObserver,
) : BaseFlowNode<RoomLoadedFlowNode.NavTarget>(
Expand Down Expand Up @@ -92,6 +94,7 @@ class RoomLoadedFlowNode @AssistedInject constructor(
Timber.v("OnCreate => ${inputs.room.roomId}")
appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId)
fetchRoomMembers()
trackVisitedRoom()
},
onResume = {
appCoroutineScope.launch {
Expand All @@ -117,6 +120,10 @@ class RoomLoadedFlowNode @AssistedInject constructor(
inputs<Inputs>()
}

private fun trackVisitedRoom() = lifecycleScope.launch {
matrixClient.trackRecentlyVisitedRoom(inputs.room.roomId)
}

private fun fetchRoomMembers() = lifecycleScope.launch {
inputs.room.updateMembers()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
import io.element.android.libraries.architecture.childNode
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -101,6 +102,7 @@ class RoomFlowNodeTest {
roomMembershipObserver = RoomMembershipObserver(),
appCoroutineScope = coroutineScope,
roomComponentFactory = FakeRoomComponentFactory(),
matrixClient = FakeMatrixClient(),
)

@Test
Expand Down
1 change: 1 addition & 0 deletions changelog.d/2634.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Show users from last visited DM as suggestion when starting a Chat or when creating a Room.
2 changes: 2 additions & 0 deletions features/createroom/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ dependencies {
testImplementation(projects.libraries.usersearch.test)
testImplementation(projects.features.createroom.test)
testImplementation(projects.tests.testutils)
testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)

ksp(libs.showkase.processor)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package io.element.android.features.createroom.impl.addpeople
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.createroom.impl.userlist.SelectionMode
import io.element.android.features.createroom.impl.userlist.UserListState
import io.element.android.features.createroom.impl.userlist.aRecentDirectRoomList
import io.element.android.features.createroom.impl.userlist.aUserListState
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
Expand All @@ -29,13 +30,13 @@ open class AddPeopleUserListStateProvider : PreviewParameterProvider<UserListSta
override val values: Sequence<UserListState>
get() = sequenceOf(
aUserListState(),
aUserListState().copy(
aUserListState(
searchResults = SearchBarResultState.Results(aMatrixUserList().toImmutableList()),
selectedUsers = aMatrixUserList().toImmutableList(),
isSearchActive = false,
selectionMode = SelectionMode.Multiple,
),
aUserListState().copy(
aUserListState(
searchResults = SearchBarResultState.Results(
aMatrixUserList()
.mapIndexed { index, matrixUser ->
Expand All @@ -46,6 +47,9 @@ open class AddPeopleUserListStateProvider : PreviewParameterProvider<UserListSta
selectedUsers = aMatrixUserList().toImmutableList(),
isSearchActive = true,
selectionMode = SelectionMode.Multiple,
)
),
aUserListState(
recentDirectRooms = aRecentDirectRoomList(),
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@

package io.element.android.features.createroom.impl.addpeople

import androidx.compose.foundation.layout.Column
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.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -64,21 +62,16 @@ fun AddPeopleView(
)
}
) { padding ->
Column(
UserListView(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.consumeWindowInsets(padding),
) {
UserListView(
modifier = Modifier
.fillMaxWidth(),
state = state,
showBackButton = false,
onUserSelected = { },
onUserDeselected = {},
)
}
state = state,
showBackButton = false,
onUserSelected = {},
onUserDeselected = {},
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,27 @@ package io.element.android.features.createroom.impl.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
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.createroom.impl.userlist.UserListEvents
import io.element.android.features.createroom.impl.userlist.UserListState
import io.element.android.features.createroom.impl.userlist.UserListStateProvider
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.CheckableUserRow
import io.element.android.libraries.matrix.ui.components.CheckableUserRowData
import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.getBestName
import io.element.android.libraries.ui.strings.CommonStrings

@Composable
fun UserListView(
Expand Down Expand Up @@ -74,6 +84,43 @@ fun UserListView(
},
)
}
if (!state.isSearchActive && state.recentDirectRooms.isNotEmpty()) {
LazyColumn {
item {
ListSectionHeader(
title = stringResource(id = CommonStrings.common_suggestions),
hasDivider = false,
)
}
state.recentDirectRooms.forEachIndexed { index, recentDirectRoom ->
item {
val isSelected = state.selectedUsers.any {
recentDirectRoom.matrixUser.userId == it.userId
}
CheckableUserRow(
checked = isSelected,
onCheckedChange = {
if (isSelected) {
state.eventSink(UserListEvents.RemoveFromSelection(recentDirectRoom.matrixUser))
onUserDeselected(recentDirectRoom.matrixUser)
} else {
state.eventSink(UserListEvents.AddToSelection(recentDirectRoom.matrixUser))
onUserSelected(recentDirectRoom.matrixUser)
}
},
data = CheckableUserRowData.Resolved(
avatarData = recentDirectRoom.matrixUser.getAvatarData(AvatarSize.UserListItem),
name = recentDirectRoom.matrixUser.getBestName(),
subtext = recentDirectRoom.matrixUser.userId.value,
),
)
if (index < state.recentDirectRooms.lastIndex) {
HorizontalDivider()
}
}
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
package io.element.android.features.createroom.impl.root

import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.createroom.impl.userlist.UserListState
import io.element.android.features.createroom.impl.userlist.aRecentDirectRoomList
import io.element.android.features.createroom.impl.userlist.aUserListState
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.usersearch.api.UserSearchResult
import kotlinx.collections.immutable.persistentListOf
Expand All @@ -28,7 +31,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
override val values: Sequence<CreateRoomRootState>
get() = sequenceOf(
aCreateRoomRootState(),
aCreateRoomRootState().copy(
aCreateRoomRootState(
startDmAction = AsyncAction.Loading,
userListState = aMatrixUser().let {
aUserListState().copy(
Expand All @@ -39,7 +42,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
)
}
),
aCreateRoomRootState().copy(
aCreateRoomRootState(
startDmAction = AsyncAction.Failure(Throwable("error")),
userListState = aMatrixUser().let {
aUserListState().copy(
Expand All @@ -50,12 +53,22 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
)
}
),
aCreateRoomRootState(
userListState = aUserListState(
recentDirectRooms = aRecentDirectRoomList()
)
),
)
}

fun aCreateRoomRootState() = CreateRoomRootState(
eventSink = {},
applicationName = "Element X Preview",
startDmAction = AsyncAction.Uninitialized,
userListState = aUserListState(),
fun aCreateRoomRootState(
applicationName: String = "Element X Preview",
userListState: UserListState = aUserListState(),
startDmAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
eventSink: (CreateRoomRootEvents) -> Unit = {},
) = CreateRoomRootState(
applicationName = applicationName,
userListState = userListState,
startDmAction = startDmAction,
eventSink = eventSink,
)
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
Expand All @@ -46,11 +47,14 @@ 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.ListSectionHeader
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.persistentListOf

@Composable
fun CreateRoomRootView(
Expand All @@ -77,7 +81,11 @@ fun CreateRoomRootView(
) {
UserListView(
modifier = Modifier.fillMaxWidth(),
state = state.userListState,
// Do not render suggestions in this case, the suggestion will be rendered
// by CreateRoomActionButtonsList
state = state.userListState.copy(
recentDirectRooms = persistentListOf(),
),
onUserSelected = {
state.eventSink(CreateRoomRootEvents.StartDM(it))
},
Expand All @@ -89,6 +97,7 @@ fun CreateRoomRootView(
state = state,
onNewRoomClicked = onNewRoomClicked,
onInvitePeopleClicked = onInviteFriendsClicked,
onDmClicked = onOpenDM,
)
}
}
Expand All @@ -106,7 +115,7 @@ fun CreateRoomRootView(
onRetry = {
state.userListState.selectedUsers.firstOrNull()
?.let { state.eventSink(CreateRoomRootEvents.StartDM(it)) }
// Cancel start DM if there is no more selected user (should not happen)
// Cancel start DM if there is no more selected user (should not happen)
?: state.eventSink(CreateRoomRootEvents.CancelStartDM)
},
onErrorDismiss = { state.eventSink(CreateRoomRootEvents.CancelStartDM) },
Expand Down Expand Up @@ -139,18 +148,43 @@ private fun CreateRoomActionButtonsList(
state: CreateRoomRootState,
onNewRoomClicked: () -> Unit,
onInvitePeopleClicked: () -> Unit,
onDmClicked: (RoomId) -> Unit,
) {
Column {
CreateRoomActionButton(
iconRes = CompoundDrawables.ic_compound_plus,
text = stringResource(id = R.string.screen_create_room_action_create_room),
onClick = onNewRoomClicked,
)
CreateRoomActionButton(
iconRes = CompoundDrawables.ic_compound_share_android,
text = stringResource(id = CommonStrings.action_invite_friends_to_app, state.applicationName),
onClick = onInvitePeopleClicked,
)
LazyColumn {
item {
CreateRoomActionButton(
iconRes = CompoundDrawables.ic_compound_plus,
text = stringResource(id = R.string.screen_create_room_action_create_room),
onClick = onNewRoomClicked,
)
}
item {
CreateRoomActionButton(
iconRes = CompoundDrawables.ic_compound_share_android,
text = stringResource(id = CommonStrings.action_invite_friends_to_app, state.applicationName),
onClick = onInvitePeopleClicked,
)
}
if (state.userListState.recentDirectRooms.isNotEmpty()) {
item {
ListSectionHeader(
title = stringResource(id = CommonStrings.common_suggestions),
hasDivider = false,
)
}
state.userListState.recentDirectRooms.forEach { recentDirectRoom ->
item {
MatrixUserRow(
modifier = Modifier.clickable(
onClick = {
onDmClicked(recentDirectRoom.roomId)
}
),
matrixUser = recentDirectRoom.matrixUser,
)
}
}
}
}
}

Expand Down
Loading

0 comments on commit 014061f

Please sign in to comment.