Add an empty state for the space screen if the user can modify its graph (#6064)
* Add an empty state for the space screen if the user can modify its graph. It adds a new 'create room' button that allows you to open the create room screen with some preset values. * When computing the editable spaces in `ConfigureRoomPresenter`, also set up the initial selected parent space if possible * Use `Builder` pattern for `CreateRoomEntryPoint` * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
committed by
GitHub
parent
cc4bf95cac
commit
4b4492681b
@@ -484,7 +484,10 @@ class LoggedInFlowNode(
|
||||
backstack.replace(NavTarget.Room(roomIdOrAlias = RoomIdOrAlias.Id(roomId), serverNames = emptyList()))
|
||||
}
|
||||
}
|
||||
createRoomEntryPoint.createNode(isSpace = true, parentNode = this, buildContext = buildContext, callback = callback)
|
||||
createRoomEntryPoint
|
||||
.builder(parentNode = this, buildContext = buildContext, callback = callback)
|
||||
.setIsSpace(true)
|
||||
.build()
|
||||
}
|
||||
is NavTarget.SecureBackup -> {
|
||||
secureBackupEntryPoint.createNode(
|
||||
|
||||
@@ -15,12 +15,13 @@ import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
interface CreateRoomEntryPoint : FeatureEntryPoint {
|
||||
fun createNode(
|
||||
isSpace: Boolean,
|
||||
parentNode: Node,
|
||||
buildContext: BuildContext,
|
||||
callback: Callback,
|
||||
): Node
|
||||
interface Builder {
|
||||
fun setIsSpace(isSpace: Boolean): Builder
|
||||
fun setParentSpace(parentSpaceId: RoomId): Builder
|
||||
fun build(): Node
|
||||
}
|
||||
|
||||
fun builder(parentNode: Node, buildContext: BuildContext, callback: Callback): Builder
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onRoomCreated(roomId: RoomId)
|
||||
|
||||
@@ -38,7 +38,7 @@ class CreateRoomFlowNode(
|
||||
@Assisted plugins: List<Plugin>,
|
||||
) : BaseFlowNode<CreateRoomFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.ConfigureRoom(isSpace = plugins.filterIsInstance<Inputs>().first().isSpace),
|
||||
initialElement = initialElementFromInputs(plugins.filterIsInstance<Inputs>().first()),
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
@@ -46,7 +46,8 @@ class CreateRoomFlowNode(
|
||||
) {
|
||||
@Parcelize
|
||||
data class Inputs(
|
||||
val isSpace: Boolean
|
||||
val isSpace: Boolean,
|
||||
val parentSpaceId: RoomId?,
|
||||
) : NodeInputs, Parcelable
|
||||
|
||||
private val callback: CreateRoomEntryPoint.Callback = callback()
|
||||
@@ -54,7 +55,7 @@ class CreateRoomFlowNode(
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
is NavTarget.ConfigureRoom -> {
|
||||
val inputs = ConfigureRoomNode.Inputs(isSpace = navTarget.isSpace)
|
||||
val inputs = ConfigureRoomNode.Inputs(isSpace = navTarget.isSpace, parentSpaceId = navTarget.parentSpaceId)
|
||||
val callback = object : ConfigureRoomNode.Callback {
|
||||
override fun onCreateRoomSuccess(roomId: RoomId) {
|
||||
backstack.replace(NavTarget.AddPeople(roomId))
|
||||
@@ -81,9 +82,14 @@ class CreateRoomFlowNode(
|
||||
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
data class ConfigureRoom(val isSpace: Boolean) : NavTarget
|
||||
data class ConfigureRoom(val isSpace: Boolean, val parentSpaceId: RoomId?) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class AddPeople(val roomId: RoomId) : NavTarget
|
||||
}
|
||||
}
|
||||
|
||||
private fun initialElementFromInputs(inputs: CreateRoomFlowNode.Inputs) = CreateRoomFlowNode.NavTarget.ConfigureRoom(
|
||||
isSpace = inputs.isSpace,
|
||||
parentSpaceId = inputs.parentSpaceId,
|
||||
)
|
||||
|
||||
@@ -14,16 +14,35 @@ import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultCreateRoomEntryPoint : CreateRoomEntryPoint {
|
||||
override fun createNode(
|
||||
isSpace: Boolean,
|
||||
parentNode: Node,
|
||||
buildContext: BuildContext,
|
||||
callback: CreateRoomEntryPoint.Callback,
|
||||
): Node {
|
||||
val inputs = CreateRoomFlowNode.Inputs(isSpace)
|
||||
return parentNode.createNode<CreateRoomFlowNode>(buildContext, listOf(inputs, callback))
|
||||
class Builder(
|
||||
private val parentNode: Node,
|
||||
private val buildContext: BuildContext,
|
||||
private val callback: CreateRoomEntryPoint.Callback,
|
||||
) : CreateRoomEntryPoint.Builder {
|
||||
private var isSpace = false
|
||||
private var parentSpaceId: RoomId? = null
|
||||
|
||||
override fun setIsSpace(isSpace: Boolean): Builder {
|
||||
this.isSpace = isSpace
|
||||
return this
|
||||
}
|
||||
|
||||
override fun setParentSpace(parentSpaceId: RoomId): Builder {
|
||||
this.parentSpaceId = parentSpaceId
|
||||
return this
|
||||
}
|
||||
|
||||
override fun build(): Node {
|
||||
val inputs = CreateRoomFlowNode.Inputs(isSpace = isSpace, parentSpaceId = parentSpaceId)
|
||||
return parentNode.createNode<CreateRoomFlowNode>(buildContext, listOf(inputs, callback))
|
||||
}
|
||||
}
|
||||
|
||||
override fun builder(parentNode: Node, buildContext: BuildContext, callback: CreateRoomEntryPoint.Callback): CreateRoomEntryPoint.Builder {
|
||||
return Builder(parentNode, buildContext, callback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,12 @@ class ConfigureRoomNode(
|
||||
@Parcelize
|
||||
data class Inputs(
|
||||
val isSpace: Boolean,
|
||||
val parentSpaceId: RoomId?,
|
||||
) : NodeInputs, Parcelable
|
||||
|
||||
private val inputs = inputs<Inputs>()
|
||||
|
||||
private val presenter = presenterFactory.create(inputs.isSpace)
|
||||
private val presenter = presenterFactory.create(inputs.isSpace, inputs.parentSpaceId)
|
||||
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
|
||||
@@ -63,6 +63,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||
@AssistedInject
|
||||
class ConfigureRoomPresenter(
|
||||
@Assisted private val isSpace: Boolean,
|
||||
@Assisted private val initialParentSpaceId: RoomId?,
|
||||
private val dataStore: CreateRoomConfigStore,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val mediaPickerProvider: PickerProvider,
|
||||
@@ -75,7 +76,7 @@ class ConfigureRoomPresenter(
|
||||
) : Presenter<ConfigureRoomState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(isSpace: Boolean): ConfigureRoomPresenter
|
||||
fun create(isSpace: Boolean, parentSpaceId: RoomId?): ConfigureRoomPresenter
|
||||
}
|
||||
|
||||
private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA)
|
||||
@@ -122,6 +123,9 @@ class ConfigureRoomPresenter(
|
||||
} else {
|
||||
persistentListOf()
|
||||
}
|
||||
|
||||
val parentSpace = spaces.find { it.roomId == initialParentSpaceId }
|
||||
parentSpace?.let { dataStore.setParentSpace(it) }
|
||||
}
|
||||
|
||||
LaunchedEffect(cameraPermissionState.permissionGranted) {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
<string name="screen_create_room_name_placeholder">"Add name…"</string>
|
||||
<string name="screen_create_room_new_room_title">"New room"</string>
|
||||
<string name="screen_create_room_new_space_title">"New space"</string>
|
||||
<string name="screen_create_room_parent_space_home_description">"(no space)"</string>
|
||||
<string name="screen_create_room_parent_space_home_title">"Home"</string>
|
||||
<string name="screen_create_room_private_option_description">"Only people invited can join."</string>
|
||||
<string name="screen_create_room_private_option_title">"Private"</string>
|
||||
<string name="screen_create_room_public_option_description">"Anyone can find this room.
|
||||
|
||||
@@ -532,6 +532,7 @@ class ConfigureRoomPresenterTest {
|
||||
|
||||
private fun createConfigureRoomPresenter(
|
||||
isSpace: Boolean = false,
|
||||
initialParenSpaceId: RoomId? = null,
|
||||
roomAliasHelper: RoomAliasHelper = FakeRoomAliasHelper(),
|
||||
dataStore: CreateRoomConfigStore = CreateRoomConfigStore(roomAliasHelper),
|
||||
matrixClient: MatrixClient = createMatrixClient(),
|
||||
@@ -543,6 +544,7 @@ class ConfigureRoomPresenterTest {
|
||||
mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(),
|
||||
) = ConfigureRoomPresenter(
|
||||
isSpace = isSpace,
|
||||
initialParentSpaceId = initialParenSpaceId,
|
||||
dataStore = dataStore,
|
||||
matrixClient = matrixClient,
|
||||
mediaPickerProvider = pickerProvider,
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.node.TestParentNode
|
||||
import org.junit.Rule
|
||||
@@ -36,15 +37,16 @@ class DefaultCreateRoomEntryPointTest {
|
||||
plugins = plugins,
|
||||
)
|
||||
}
|
||||
val buildContext = BuildContext.root(null)
|
||||
|
||||
val callback = object : CreateRoomEntryPoint.Callback {
|
||||
override fun onRoomCreated(roomId: RoomId) = lambdaError()
|
||||
}
|
||||
val result = entryPoint.createNode(
|
||||
isSpace = false,
|
||||
parentNode = parentNode,
|
||||
buildContext = BuildContext.root(null),
|
||||
callback = callback,
|
||||
)
|
||||
val result = entryPoint
|
||||
.builder(parentNode, buildContext, callback)
|
||||
.setIsSpace(true)
|
||||
.setParentSpace(A_ROOM_ID)
|
||||
.build()
|
||||
assertThat(result.plugins).contains(callback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,5 +16,6 @@ android {
|
||||
dependencies {
|
||||
implementation(projects.features.createroom.api)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
|
||||
@@ -10,13 +10,19 @@ package io.element.android.features.createroom.api
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeCreateRoomEntryPoint : CreateRoomEntryPoint {
|
||||
override fun createNode(
|
||||
isSpace: Boolean,
|
||||
class Builder : CreateRoomEntryPoint.Builder {
|
||||
override fun setIsSpace(isSpace: Boolean): Builder = this
|
||||
override fun setParentSpace(parentSpaceId: RoomId): Builder = this
|
||||
override fun build(): Node = lambdaError()
|
||||
}
|
||||
|
||||
override fun builder(
|
||||
parentNode: Node,
|
||||
buildContext: BuildContext,
|
||||
callback: CreateRoomEntryPoint.Callback,
|
||||
): Node = lambdaError()
|
||||
): Builder = lambdaError()
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ dependencies {
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.features.createroom.api)
|
||||
implementation(projects.features.invite.api)
|
||||
implementation(projects.libraries.previewutils)
|
||||
implementation(projects.features.securityandprivacy.api)
|
||||
@@ -49,5 +50,6 @@ dependencies {
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.featureflag.test)
|
||||
testImplementation(projects.features.createroom.test)
|
||||
testImplementation(projects.features.invite.test)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.bumble.appyx.navmodel.backstack.operation.push
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
||||
import io.element.android.features.space.api.SpaceEntryPoint
|
||||
import io.element.android.features.space.impl.addroom.AddRoomToSpaceNode
|
||||
import io.element.android.features.space.impl.di.SpaceFlowGraph
|
||||
@@ -49,6 +50,7 @@ class SpaceFlowNode(
|
||||
room: JoinedRoom,
|
||||
spaceService: SpaceService,
|
||||
graphFactory: SpaceFlowGraph.Factory,
|
||||
private val createRoomEntryPoint: CreateRoomEntryPoint,
|
||||
) : BaseFlowNode<SpaceFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Root,
|
||||
@@ -71,6 +73,9 @@ class SpaceFlowNode(
|
||||
@Parcelize
|
||||
data object Leave : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object CreateRoom : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object AddRoom : NavTarget
|
||||
}
|
||||
@@ -116,6 +121,10 @@ class SpaceFlowNode(
|
||||
backstack.push(NavTarget.Leave)
|
||||
}
|
||||
|
||||
override fun onCreateRoom() {
|
||||
backstack.push(NavTarget.CreateRoom)
|
||||
}
|
||||
|
||||
override fun navigateToAddRoom() {
|
||||
backstack.push(NavTarget.AddRoom)
|
||||
}
|
||||
@@ -140,6 +149,21 @@ class SpaceFlowNode(
|
||||
}
|
||||
createNode<SpaceSettingsFlowNode>(buildContext, listOf(callback))
|
||||
}
|
||||
is NavTarget.CreateRoom -> {
|
||||
val callback = object : CreateRoomEntryPoint.Callback {
|
||||
override fun onRoomCreated(roomId: RoomId) {
|
||||
callback.navigateToRoom(roomId, emptyList())
|
||||
}
|
||||
}
|
||||
createRoomEntryPoint
|
||||
.builder(
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
callback = callback,
|
||||
)
|
||||
.setParentSpace(spaceRoomList.roomId)
|
||||
.build()
|
||||
}
|
||||
NavTarget.AddRoom -> {
|
||||
val callback = object : AddRoomToSpaceNode.Callback {
|
||||
override fun onFinish() {
|
||||
|
||||
@@ -47,6 +47,8 @@ class SpaceNode(
|
||||
fun navigateToRoomMemberList()
|
||||
fun startLeaveSpaceFlow()
|
||||
fun navigateToAddRoom()
|
||||
|
||||
fun onCreateRoom()
|
||||
}
|
||||
|
||||
private val callback: Callback = callback()
|
||||
@@ -105,6 +107,7 @@ class SpaceNode(
|
||||
modifier = Modifier
|
||||
)
|
||||
},
|
||||
onCreateRoomClick = callback::onCreateRoom,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ open class SpaceStateProvider : PreviewParameterProvider<SpaceState> {
|
||||
spaceInfo = aSpaceInfo(),
|
||||
children = aListOfSpaceRooms(),
|
||||
joiningRooms = setOf(RoomId("!spaceId0:example.com")),
|
||||
hasMoreToLoad = false
|
||||
hasMoreToLoad = true,
|
||||
),
|
||||
aSpaceState(
|
||||
topicViewerState = TopicViewerState.Shown(topic = "Space description goes here." + LoremIpsum(20).values.first()),
|
||||
@@ -71,7 +71,7 @@ fun aSpaceState(
|
||||
joiningRooms: Set<RoomId> = emptySet(),
|
||||
joinActions: Map<RoomId, AsyncAction<Unit>> = joiningRooms.associateWith { AsyncAction.Loading },
|
||||
hideInvitesAvatar: Boolean = false,
|
||||
hasMoreToLoad: Boolean = true,
|
||||
hasMoreToLoad: Boolean = false,
|
||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||
topicViewerState: TopicViewerState = TopicViewerState.Hidden,
|
||||
canAccessSpaceSettings: Boolean = true,
|
||||
|
||||
@@ -50,7 +50,9 @@ import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.space.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.InviteButtonsRowMolecule
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.ClickableLinkText
|
||||
import io.element.android.libraries.designsystem.components.SimpleModalBottomSheet
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
@@ -65,6 +67,7 @@ import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.Checkbox
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
|
||||
@@ -72,6 +75,7 @@ import io.element.android.libraries.designsystem.theme.components.DropdownMenuIt
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
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.IconSource
|
||||
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.TextButton
|
||||
@@ -98,6 +102,7 @@ fun SpaceView(
|
||||
onLeaveSpaceClick: () -> Unit,
|
||||
onSettingsClick: () -> Unit,
|
||||
onViewMembersClick: () -> Unit,
|
||||
onCreateRoomClick: () -> Unit,
|
||||
onAddRoomClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
acceptDeclineInviteView: @Composable () -> Unit,
|
||||
@@ -161,7 +166,8 @@ fun SpaceView(
|
||||
},
|
||||
onTopicClick = { topic ->
|
||||
state.eventSink(SpaceEvents.ShowTopicViewer(topic))
|
||||
}
|
||||
},
|
||||
onCreateRoomClick = onCreateRoomClick,
|
||||
)
|
||||
JoinFailuresEffect(
|
||||
hasAnyFailure = state.hasAnyJoinFailures,
|
||||
@@ -234,6 +240,7 @@ private fun SpaceViewContent(
|
||||
state: SpaceState,
|
||||
onRoomClick: (spaceRoom: SpaceRoom) -> Unit,
|
||||
onTopicClick: (String) -> Unit,
|
||||
onCreateRoomClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(modifier.fillMaxSize()) {
|
||||
@@ -259,61 +266,90 @@ private fun SpaceViewContent(
|
||||
}
|
||||
}
|
||||
}
|
||||
itemsIndexed(
|
||||
items = state.children,
|
||||
key = { _, spaceRoom -> spaceRoom.roomId }
|
||||
) { index, spaceRoom ->
|
||||
val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED
|
||||
val isCurrentlyJoining = state.isJoining(spaceRoom.roomId)
|
||||
val isSelected = state.isSelected(spaceRoom.roomId)
|
||||
val showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites && !state.isManageMode
|
||||
SpaceRoomItemView(
|
||||
spaceRoom = spaceRoom,
|
||||
showUnreadIndicator = showUnreadIndicator,
|
||||
hideAvatars = isInvitation && state.hideInvitesAvatar,
|
||||
onClick = {
|
||||
onRoomClick(spaceRoom)
|
||||
},
|
||||
onLongClick = {
|
||||
// TODO
|
||||
},
|
||||
trailingAction = if (state.isManageMode) {
|
||||
{
|
||||
Checkbox(
|
||||
checked = isSelected,
|
||||
onCheckedChange = null,
|
||||
|
||||
if (state.children.isEmpty() && state.canEditSpaceGraph && !state.hasMoreToLoad) {
|
||||
item {
|
||||
EmptySpaceView(onCreateRoomClick = onCreateRoomClick)
|
||||
}
|
||||
} else {
|
||||
itemsIndexed(
|
||||
items = state.children,
|
||||
key = { _, spaceRoom -> spaceRoom.roomId }
|
||||
) { index, spaceRoom ->
|
||||
val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED
|
||||
val isCurrentlyJoining = state.isJoining(spaceRoom.roomId)
|
||||
val isSelected = state.isSelected(spaceRoom.roomId)
|
||||
val showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites && !state.isManageMode
|
||||
SpaceRoomItemView(
|
||||
spaceRoom = spaceRoom,
|
||||
showUnreadIndicator = showUnreadIndicator,
|
||||
hideAvatars = isInvitation && state.hideInvitesAvatar,
|
||||
onClick = {
|
||||
onRoomClick(spaceRoom)
|
||||
},
|
||||
onLongClick = {
|
||||
// TODO
|
||||
},
|
||||
trailingAction = if (state.isManageMode) {
|
||||
{
|
||||
Checkbox(
|
||||
checked = isSelected,
|
||||
onCheckedChange = null,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) {
|
||||
state.eventSink(SpaceEvents.Join(spaceRoom))
|
||||
}
|
||||
},
|
||||
bottomAction = if (state.isManageMode) {
|
||||
null
|
||||
} else {
|
||||
spaceRoom.inviteButtons(
|
||||
onAcceptClick = {
|
||||
state.eventSink(SpaceEvents.AcceptInvite(spaceRoom))
|
||||
},
|
||||
onDeclineClick = {
|
||||
state.eventSink(SpaceEvents.DeclineInvite(spaceRoom))
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) {
|
||||
state.eventSink(SpaceEvents.Join(spaceRoom))
|
||||
}
|
||||
},
|
||||
bottomAction = if (state.isManageMode) {
|
||||
null
|
||||
} else {
|
||||
spaceRoom.inviteButtons(
|
||||
onAcceptClick = {
|
||||
state.eventSink(SpaceEvents.AcceptInvite(spaceRoom))
|
||||
},
|
||||
onDeclineClick = {
|
||||
state.eventSink(SpaceEvents.DeclineInvite(spaceRoom))
|
||||
}
|
||||
)
|
||||
)
|
||||
if (index != state.children.lastIndex) {
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
||||
if (state.hasMoreToLoad) {
|
||||
item {
|
||||
LoadingMoreIndicator(eventSink = state.eventSink)
|
||||
}
|
||||
)
|
||||
if (index != state.children.lastIndex) {
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
if (state.hasMoreToLoad) {
|
||||
item {
|
||||
LoadingMoreIndicator(eventSink = state.eventSink)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptySpaceView(onCreateRoomClick: () -> Unit) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(bottom = 24.dp),
|
||||
) {
|
||||
IconTitleSubtitleMolecule(
|
||||
title = stringResource(R.string.screen_space_empty_state_title),
|
||||
subTitle = null,
|
||||
iconStyle = BigIcon.Style.Default(CompoundIcons.Room()),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.padding(top = 40.dp, start = 24.dp, end = 24.dp, bottom = 24.dp),
|
||||
)
|
||||
Button(
|
||||
text = stringResource(R.string.screen_space_add_room_action),
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Plus()),
|
||||
onClick = onCreateRoomClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingMoreIndicator(
|
||||
eventSink: (SpaceEvents) -> Unit,
|
||||
@@ -611,6 +647,7 @@ internal fun SpaceViewPreview(
|
||||
acceptDeclineInviteView = {},
|
||||
onSettingsClick = {},
|
||||
onViewMembersClick = {},
|
||||
onCreateRoomClick = {},
|
||||
onAddRoomClick = {},
|
||||
onBackClick = {},
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.createroom.api.FakeCreateRoomEntryPoint
|
||||
import io.element.android.features.space.api.SpaceEntryPoint
|
||||
import io.element.android.features.space.impl.di.FakeSpaceFlowGraph
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
@@ -43,7 +44,8 @@ class DefaultSpaceEntryPointTest {
|
||||
spaceRoomListResult = { _: RoomId -> FakeSpaceRoomList(A_ROOM_ID) }
|
||||
),
|
||||
room = FakeJoinedRoom(),
|
||||
graphFactory = FakeSpaceFlowGraph.Factory
|
||||
graphFactory = FakeSpaceFlowGraph.Factory,
|
||||
createRoomEntryPoint = FakeCreateRoomEntryPoint(),
|
||||
)
|
||||
}
|
||||
val callback = object : SpaceEntryPoint.Callback {
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.space.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
@@ -30,6 +31,7 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.pressBack
|
||||
import io.element.android.tests.testutils.pressBackKey
|
||||
import org.junit.Rule
|
||||
@@ -200,6 +202,22 @@ class SpaceViewTest {
|
||||
rule.clickOn(CommonStrings.action_remove, inDialog = true)
|
||||
eventsRecorder.assertSingle(SpaceEvents.ConfirmRoomRemoval)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking create room button calls the expected callback`() {
|
||||
val onCreateRoomClick = lambdaRecorder<Unit> { }
|
||||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
children = emptyList(),
|
||||
hasMoreToLoad = false,
|
||||
isManageMode = true,
|
||||
canManageRooms = true,
|
||||
),
|
||||
onCreateRoomClick = onCreateRoomClick,
|
||||
)
|
||||
rule.clickOn(R.string.screen_space_add_room_action)
|
||||
onCreateRoomClick.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceView(
|
||||
@@ -210,6 +228,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpace
|
||||
onLeaveSpaceClick: () -> Unit = EnsureNeverCalled(),
|
||||
onSettingsClick: () -> Unit = EnsureNeverCalled(),
|
||||
onViewMembersClick: () -> Unit = EnsureNeverCalled(),
|
||||
onCreateRoomClick: () -> Unit = EnsureNeverCalled(),
|
||||
onAddRoomClick: () -> Unit = EnsureNeverCalled(),
|
||||
acceptDeclineInviteView: @Composable () -> Unit = {},
|
||||
) {
|
||||
@@ -224,6 +243,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpace
|
||||
onViewMembersClick = onViewMembersClick,
|
||||
onAddRoomClick = onAddRoomClick,
|
||||
acceptDeclineInviteView = acceptDeclineInviteView,
|
||||
onCreateRoomClick = onCreateRoomClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,12 +80,14 @@ class StartChatFlowNode(
|
||||
navigator.onRoomCreated(roomId.toRoomIdOrAlias(), emptyList())
|
||||
}
|
||||
}
|
||||
createRoomEntryPoint.createNode(
|
||||
isSpace = false,
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
callback = callback,
|
||||
)
|
||||
createRoomEntryPoint
|
||||
.builder(
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
callback = callback,
|
||||
)
|
||||
.setIsSpace(false)
|
||||
.build()
|
||||
}
|
||||
NavTarget.JoinByAddress -> {
|
||||
createNode<JoinRoomByAddressNode>(buildContext = buildContext, plugins = listOf(navigator))
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user