Room navigation : some clean up

This commit is contained in:
ganfra 2024-04-12 15:58:15 +02:00
parent a2329fc7df
commit 11979167c5
28 changed files with 107 additions and 142 deletions

View file

@ -18,17 +18,16 @@ package io.element.android.features.invite.impl.invitelist
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.impl.model.InviteListInviteSummary
import io.element.android.features.invite.impl.model.InviteSender
import io.element.android.features.invite.api.response.AcceptDeclineInviteStateProvider
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
import io.element.android.features.invite.impl.model.InviteListInviteSummary
import io.element.android.features.invite.impl.model.InviteSender
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
open class InviteListStateProvider : PreviewParameterProvider<InviteListState> {
private val acceptDeclineInviteStateProvider = AcceptDeclineInviteStateProvider()
override val values: Sequence<InviteListState>

View file

@ -23,7 +23,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import com.squareup.anvil.annotations.ContributesBinding
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
@ -32,7 +31,6 @@ import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.architecture.runUpdatingState
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.push.api.notifications.NotificationDrawerManager
@ -49,10 +47,8 @@ class AcceptDeclineInvitePresenter @Inject constructor(
private val analyticsService: AnalyticsService,
private val notificationDrawerManager: NotificationDrawerManager,
) : Presenter<AcceptDeclineInviteState> {
@Composable
override fun present(): AcceptDeclineInviteState {
val localCoroutineScope = rememberCoroutineScope()
val acceptedAction: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
val declinedAction: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }

View file

@ -27,7 +27,6 @@ import javax.inject.Inject
@ContributesBinding(SessionScope::class)
class AcceptDeclineInviteViewWrapper @Inject constructor() : AcceptDeclineInviteView {
@Composable
override fun Render(
state: AcceptDeclineInviteState,

View file

@ -18,7 +18,7 @@ package io.element.android.features.invite.impl.response
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
sealed interface InternalAcceptDeclineInviteEvents: AcceptDeclineInviteEvents {
sealed interface InternalAcceptDeclineInviteEvents : AcceptDeclineInviteEvents {
data object ConfirmDeclineInvite : InternalAcceptDeclineInviteEvents
data object CancelDeclineInvite : InternalAcceptDeclineInviteEvents
data object DismissAcceptError : InternalAcceptDeclineInviteEvents

View file

@ -24,11 +24,7 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.invite.api.SeenInvitesStore
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
import io.element.android.features.invite.impl.invitelist.InviteListEvents
import io.element.android.features.invite.impl.invitelist.InviteListPresenter
import io.element.android.features.invite.impl.invitelist.InviteListState
import io.element.android.features.invite.test.FakeSeenInvitesStore
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
@ -43,14 +39,9 @@ import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.matrix.test.room.aRoomSummaryDetails
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.coroutines.test.runTest
import org.junit.Rule

View file

@ -40,7 +40,6 @@ import org.junit.Test
import java.util.Optional
class AcceptDeclineInvitePresenterTest {
@get:Rule
val warmUpRule = WarmUpRule()

View file

@ -25,7 +25,6 @@ import io.element.android.libraries.matrix.api.core.RoomId
import java.util.Optional
interface JoinRoomEntryPoint : FeatureEntryPoint {
fun createNode(parentNode: Node, buildContext: BuildContext, inputs: Inputs): Node
data class Inputs(
@ -33,4 +32,3 @@ interface JoinRoomEntryPoint : FeatureEntryPoint {
val roomDescription: Optional<RoomDescription>,
) : NodeInputs
}

View file

@ -43,7 +43,6 @@ dependencies {
implementation(projects.features.roomdirectory.api)
implementation(projects.libraries.uiStrings)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime)

View file

@ -26,7 +26,6 @@ import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultJoinRoomEntryPoint @Inject constructor() : JoinRoomEntryPoint {
override fun createNode(parentNode: Node, buildContext: BuildContext, inputs: JoinRoomEntryPoint.Inputs): Node {
return parentNode.createNode<JoinRoomNode>(
buildContext = buildContext,

View file

@ -17,7 +17,7 @@
package io.element.android.features.joinroom.impl
sealed interface JoinRoomEvents {
data object JoinRoom: JoinRoomEvents
data object JoinRoom : JoinRoomEvents
data object AcceptInvite : JoinRoomEvents
data object DeclineInvite : JoinRoomEvents
}

View file

@ -36,7 +36,6 @@ class JoinRoomNode @AssistedInject constructor(
presenterFactory: JoinRoomPresenter.Factory,
private val acceptDeclineInviteView: AcceptDeclineInviteView,
) : Node(buildContext, plugins = plugins) {
private val inputs: JoinRoomEntryPoint.Inputs = inputs()
private val presenter = presenterFactory.create(inputs.roomId, inputs.roomDescription)

View file

@ -40,7 +40,6 @@ class JoinRoomPresenter @AssistedInject constructor(
private val matrixClient: MatrixClient,
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
) : Presenter<JoinRoomState> {
interface Factory {
fun create(roomId: RoomId, roomDescription: Optional<RoomDescription>): JoinRoomPresenter
}

View file

@ -28,7 +28,7 @@ data class JoinRoomState(
val acceptDeclineInviteState: AcceptDeclineInviteState,
val eventSink: (JoinRoomEvents) -> Unit
) {
val joinAuthorisationStatus = when(contentState) {
val joinAuthorisationStatus = when (contentState) {
is ContentState.Loaded -> contentState.joinAuthorisationStatus
else -> JoinAuthorisationStatus.Unknown
}

View file

@ -75,4 +75,3 @@ fun aJoinRoomState(
acceptDeclineInviteState = acceptDeclineInviteState,
eventSink = eventSink
)

View file

@ -16,7 +16,6 @@
package io.element.android.features.joinroom.impl
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -32,7 +31,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@ -138,59 +136,56 @@ private fun JoinRoomContent(
contentState: ContentState,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
when (contentState) {
is ContentState.Loaded -> {
ContentScaffold(
avatar = {
Avatar(contentState.avatarData(AvatarSize.RoomHeader))
},
title = {
Title(contentState.computedTitle)
},
subtitle = {
Subtitle(contentState.computedSubtitle)
},
description = {
Description(contentState.topic ?: "")
},
memberCount = {
if (contentState.showMemberCount) {
MembersCount(memberCount = contentState.numberOfMembers ?: 0)
}
when (contentState) {
is ContentState.Loaded -> {
ContentScaffold(
modifier = modifier,
avatar = {
Avatar(contentState.avatarData(AvatarSize.RoomHeader))
},
title = {
Title(contentState.computedTitle)
},
subtitle = {
Subtitle(contentState.computedSubtitle)
},
description = {
Description(contentState.topic ?: "")
},
memberCount = {
if (contentState.showMemberCount) {
MembersCount(memberCount = contentState.numberOfMembers ?: 0)
}
)
}
is ContentState.UnknownRoom -> {
ContentScaffold(
avatar = {
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
},
title = {
Title(stringResource(R.string.screen_join_room_title_no_preview))
},
subtitle = {
Subtitle(stringResource(R.string.screen_join_room_subtitle_no_preview))
},
)
}
is ContentState.Loading -> {
ContentScaffold(
avatar = {
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
},
title = {
PlaceholderAtom(width = 200.dp, height = 22.dp)
},
subtitle = {
PlaceholderAtom(width = 140.dp, height = 20.dp)
},
)
}
}
)
}
is ContentState.UnknownRoom -> {
ContentScaffold(
modifier = modifier,
avatar = {
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
},
title = {
Title(stringResource(R.string.screen_join_room_title_no_preview))
},
subtitle = {
Subtitle(stringResource(R.string.screen_join_room_subtitle_no_preview))
},
)
}
is ContentState.Loading -> {
ContentScaffold(
modifier = modifier,
avatar = {
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
},
title = {
PlaceholderAtom(width = 200.dp, height = 22.dp)
},
subtitle = {
PlaceholderAtom(width = 140.dp, height = 20.dp)
},
)
}
}
}
@ -200,23 +195,29 @@ private fun ContentScaffold(
avatar: @Composable () -> Unit,
title: @Composable () -> Unit,
subtitle: @Composable () -> Unit,
modifier: Modifier = Modifier,
description: @Composable (() -> Unit)? = null,
memberCount: @Composable (() -> Unit)? = null,
) {
avatar()
Spacer(modifier = Modifier.height(16.dp))
title()
Spacer(modifier = Modifier.height(8.dp))
subtitle()
Spacer(modifier = Modifier.height(8.dp))
if (memberCount != null) {
memberCount()
Column(
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
avatar()
Spacer(modifier = Modifier.height(16.dp))
title()
Spacer(modifier = Modifier.height(8.dp))
subtitle()
Spacer(modifier = Modifier.height(8.dp))
if (memberCount != null) {
memberCount()
}
Spacer(modifier = Modifier.height(8.dp))
if (description != null) {
description()
}
Spacer(modifier = Modifier.height(24.dp))
}
Spacer(modifier = Modifier.height(8.dp))
if (description != null) {
description()
}
Spacer(modifier = Modifier.height(24.dp))
}
@Composable
@ -256,12 +257,11 @@ private fun Description(description: String, modifier: Modifier = Modifier) {
@Composable
private fun MembersCount(memberCount: Long) {
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier
.background(color = ElementTheme.colors.bgSubtleSecondary, shape = CircleShape)
.widthIn(min = 48.dp)
.padding(all = 2.dp),
.background(color = ElementTheme.colors.bgSubtleSecondary, shape = CircleShape)
.widthIn(min = 48.dp)
.padding(all = 2.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {

View file

@ -41,7 +41,6 @@ import org.junit.Test
import java.util.Optional
class JoinRoomPresenterTest {
@get:Rule
val warmUpRule = WarmUpRule()
@ -136,7 +135,6 @@ class JoinRoomPresenterTest {
listOf(value(AcceptDeclineInviteEvents.AcceptInvite(inviteData))),
listOf(value(AcceptDeclineInviteEvents.DeclineInvite(inviteData))),
)
}
}
}

View file

@ -164,7 +164,7 @@ class LeaveRoomPresenterImplTest {
givenGetRoomResult(
roomId = A_ROOM_ID,
result = FakeMatrixRoom().apply {
this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!"))}
this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) }
},
)
}
@ -210,7 +210,7 @@ class LeaveRoomPresenterImplTest {
givenGetRoomResult(
roomId = A_ROOM_ID,
result = FakeMatrixRoom().apply {
this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!"))}
this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) }
},
)
}

View file

@ -18,11 +18,9 @@ package io.element.android.features.location.impl.common.actions
import com.google.common.truth.Truth.assertThat
import io.element.android.features.location.api.Location
import org.junit.Ignore
import org.junit.Test
import java.net.URLEncoder
@Ignore
internal class AndroidLocationActionsTest {
// We use an Android-native encoder in the actual app, switch to an equivalent JVM one for the tests
private fun urlEncoder(input: String) = URLEncoder.encode(input, "US-ASCII")

View file

@ -21,6 +21,7 @@ import androidx.compose.runtime.Immutable
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.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Parcelize
@ -34,15 +35,16 @@ data class RoomDescription(
val joinRule: JoinRule,
val numberOfMembers: Long,
) : Parcelable {
enum class JoinRule {
PUBLIC,
KNOCK,
UNKNOWN
}
@IgnoredOnParcel
val computedName = name ?: alias ?: roomId.value
@IgnoredOnParcel
val computedDescription: String
get() {
return when {
@ -53,7 +55,8 @@ data class RoomDescription(
}
}
fun canBeJoined() = joinRule == JoinRule.PUBLIC || joinRule == JoinRule.KNOCK
@IgnoredOnParcel
val canJoinOrKnock = joinRule == JoinRule.PUBLIC || joinRule == JoinRule.KNOCK
fun avatarData(size: AvatarSize) = AvatarData(
id = roomId.value,

View file

@ -36,7 +36,6 @@ class RoomDirectoryNode @AssistedInject constructor(
@Assisted plugins: List<Plugin>,
private val presenter: RoomDirectoryPresenter,
) : Node(buildContext, plugins = plugins) {
private fun onResultClicked(roomDescription: RoomDescription) {
plugins<RoomDirectoryEntryPoint.Callback>().forEach {
it.onResultClicked(roomDescription)

View file

@ -91,8 +91,8 @@ fun RoomDirectoryView(
onResultClicked = onResultClicked,
onJoinClicked = ::joinRoom,
modifier = Modifier
.padding(padding)
.consumeWindowInsets(padding)
.padding(padding)
.consumeWindowInsets(padding)
)
}
)
@ -199,10 +199,10 @@ private fun RoomDirectoryRoomList(
@Composable
private fun LoadMoreIndicator(modifier: Modifier = Modifier) {
Box(
modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(24.dp),
modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(24.dp),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
@ -273,14 +273,14 @@ private fun RoomDirectoryRoomRow(
) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(
top = 12.dp,
bottom = 12.dp,
start = 16.dp,
)
.height(IntrinsicSize.Min),
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(
top = 12.dp,
bottom = 12.dp,
start = 16.dp,
)
.height(IntrinsicSize.Min),
) {
Avatar(
avatarData = roomDescription.avatarData(AvatarSize.RoomDirectoryItem),
@ -288,8 +288,8 @@ private fun RoomDirectoryRoomRow(
)
Column(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp)
.weight(1f)
.padding(start = 16.dp)
) {
Text(
text = roomDescription.computedName,
@ -306,15 +306,15 @@ private fun RoomDirectoryRoomRow(
overflow = TextOverflow.Ellipsis,
)
}
if (roomDescription.canBeJoined()) {
if (roomDescription.canJoinOrKnock) {
Text(
text = stringResource(id = CommonStrings.action_join),
color = ElementTheme.colors.textSuccessPrimary,
modifier = Modifier
.align(Alignment.CenterVertically)
.clickable(onClick = onJoinClick)
.padding(start = 4.dp, end = 12.dp)
.testTag(TestTags.callToAction.value)
.align(Alignment.CenterVertically)
.clickable(onClick = onJoinClick)
.padding(start = 4.dp, end = 12.dp)
.testTag(TestTags.callToAction.value)
)
} else {
Spacer(modifier = Modifier.width(24.dp))

View file

@ -20,7 +20,6 @@ import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription as MatrixRoomDescription
fun MatrixRoomDescription.toFeatureModel(): RoomDescription {
return RoomDescription(
roomId = roomId,
name = name,

View file

@ -17,12 +17,9 @@
package io.element.android.features.roomdirectory.impl.root
import androidx.activity.ComponentActivity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onChild
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
@ -33,7 +30,6 @@ import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder
@ -74,7 +70,7 @@ class RoomDirectoryViewTest {
state = state,
onResultClicked = callback,
)
rule.onNodeWithText(clickedRoom.name).performClick()
rule.onNodeWithText(clickedRoom.computedName).performClick()
}
}

View file

@ -35,6 +35,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
/**
* @param modifier Classical modifier.
* @param paddingValues padding values to apply to the content.
* @param background optional background component.
* @param topBar optional topBar.
* @param header optional header.

View file

@ -27,7 +27,6 @@ import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
@ -100,4 +99,3 @@ interface MatrixClient : Closeable {
suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit>
suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>>
}

View file

@ -79,11 +79,8 @@ interface RoomListService {
* The state of the service as a flow.
*/
val state: StateFlow<State>
}
fun RoomList.loadedStateFlow(): Flow<RoomList.LoadingState.Loaded> {
return loadingState.filterIsInstance()
}

View file

@ -276,5 +276,4 @@ class FakeMatrixClient(
}
override fun getRoomInfoFlow(roomId: RoomId) = getRoomInfoFlowLambda(roomId)
}

View file

@ -107,7 +107,7 @@ object TestTags {
val searchTextField = TestTag("search_text_field")
/**
* Generic call to action
* Generic call to action.
*/
val callToAction = TestTag("call_to_action")
}