Merge branch 'develop' into feature/fga/room_list_api

This commit is contained in:
ganfra 2023-06-28 15:14:06 +02:00
commit 8e5c2a749a
935 changed files with 6059 additions and 3293 deletions

View file

@ -25,16 +25,20 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.features.invitelist.api.SeenInvitesStore
import io.element.android.features.invitelist.impl.model.InviteListInviteSummary
import io.element.android.features.invitelist.impl.model.InviteSender
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute
import io.element.android.libraries.architecture.runCatchingUpdatingState
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.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.api.extensions.toAnalyticsJoinedRoom
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
@ -44,6 +48,7 @@ import javax.inject.Inject
class InviteListPresenter @Inject constructor(
private val client: MatrixClient,
private val store: SeenInvitesStore,
private val analyticsService: AnalyticsService,
) : Presenter<InviteListState> {
@Composable
@ -133,9 +138,10 @@ class InviteListPresenter @Inject constructor(
suspend {
client.getRoom(roomId)?.use {
it.acceptInvitation().getOrThrow()
analyticsService.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite))
}
roomId
}.execute(acceptedAction)
}.runCatchingUpdatingState(acceptedAction)
}
private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState<Async<Unit>>) = launch {
@ -143,7 +149,7 @@ class InviteListPresenter @Inject constructor(
client.getRoom(roomId)?.use {
it.rejectInvitation().getOrThrow()
} ?: Unit
}.execute(declinedAction)
}.runCatchingUpdatingState(declinedAction)
}
private fun RoomSummary.Filled.toInviteSummary(seen: Boolean) = details.run {
@ -153,12 +159,14 @@ class InviteListPresenter @Inject constructor(
id = i.userId.value,
name = i.displayName,
url = i.avatarUrl,
size = AvatarSize.RoomInviteItem,
)
else
AvatarData(
id = roomId.value,
name = name,
url = avatarURLString
url = avatarURLString,
size = AvatarSize.RoomInviteItem,
)
val alias = if (isDirect)
@ -181,6 +189,7 @@ class InviteListPresenter @Inject constructor(
id = userId.value,
name = displayName,
url = avatarUrl,
size = AvatarSize.InviteSender,
),
)
},

View file

@ -47,7 +47,7 @@ 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.ui.strings.R as StringR
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun InviteListView(
@ -58,7 +58,7 @@ fun InviteListView(
) {
if (state.acceptedAction is Async.Success) {
LaunchedEffect(state.acceptedAction) {
onInviteAccepted(state.acceptedAction.state)
onInviteAccepted(state.acceptedAction.data)
}
}
@ -82,8 +82,8 @@ fun InviteListView(
ConfirmationDialog(
content = stringResource(contentResource, state.declineConfirmationDialog.name),
title = stringResource(titleResource),
submitText = stringResource(StringR.string.action_decline),
cancelText = stringResource(StringR.string.action_cancel),
submitText = stringResource(CommonStrings.action_decline),
cancelText = stringResource(CommonStrings.action_cancel),
emphasizeSubmitButton = true,
onSubmitClicked = { state.eventSink(InviteListEvents.ConfirmDeclineInvite) },
onDismiss = { state.eventSink(InviteListEvents.CancelDeclineInvite) }
@ -92,18 +92,18 @@ fun InviteListView(
if (state.acceptedAction is Async.Failure) {
ErrorDialog(
content = stringResource(StringR.string.error_unknown),
title = stringResource(StringR.string.common_error),
submitText = stringResource(StringR.string.action_ok),
content = stringResource(CommonStrings.error_unknown),
title = stringResource(CommonStrings.common_error),
submitText = stringResource(CommonStrings.action_ok),
onDismiss = { state.eventSink(InviteListEvents.DismissAcceptError) }
)
}
if (state.declinedAction is Async.Failure) {
ErrorDialog(
content = stringResource(StringR.string.error_unknown),
title = stringResource(StringR.string.common_error),
submitText = stringResource(StringR.string.action_ok),
content = stringResource(CommonStrings.error_unknown),
title = stringResource(CommonStrings.common_error),
submitText = stringResource(CommonStrings.action_ok),
onDismiss = { state.eventSink(InviteListEvents.DismissDeclineError) }
)
}
@ -124,7 +124,7 @@ fun InviteListContent(
BackButton(onClick = onBackClicked)
},
title = {
Text(text = stringResource(StringR.string.action_invites_list))
Text(text = stringResource(CommonStrings.action_invites_list))
}
)
},

View file

@ -16,7 +16,6 @@
package io.element.android.features.invitelist.impl.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -28,14 +27,11 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@ -52,6 +48,7 @@ import io.element.android.features.invitelist.impl.model.InviteListInviteSummary
import io.element.android.features.invitelist.impl.model.InviteListInviteSummaryProvider
import io.element.android.features.invitelist.impl.model.InviteSender
import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
@ -61,7 +58,7 @@ import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.noFontPadding
import io.element.android.libraries.designsystem.theme.roomListUnreadIndicator
import io.element.android.libraries.ui.strings.R as StringR
import io.element.android.libraries.ui.strings.CommonStrings
private val minHeight = 72.dp
@ -74,8 +71,8 @@ internal fun InviteSummaryRow(
) {
Box(
modifier = modifier
.fillMaxWidth()
.heightIn(min = minHeight)
.fillMaxWidth()
.heightIn(min = minHeight)
) {
DefaultInviteSummaryRow(
invite = invite,
@ -93,20 +90,20 @@ internal fun DefaultInviteSummaryRow(
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.height(IntrinsicSize.Min),
.fillMaxWidth()
.padding(16.dp)
.height(IntrinsicSize.Min),
verticalAlignment = Alignment.Top
) {
Avatar(
invite.roomAvatarData.copy(size = AvatarSize.Custom(52.dp)),
invite.roomAvatarData,
)
Column(
modifier = Modifier
.padding(start = 16.dp, end = 4.dp)
.alignByBaseline()
.weight(1f)
.padding(start = 16.dp, end = 4.dp)
.alignByBaseline()
.weight(1f)
) {
val bonusPadding = if (invite.isNew) 12.dp else 0.dp
@ -143,31 +140,29 @@ internal fun DefaultInviteSummaryRow(
// CTAs
Row(Modifier.padding(top = 12.dp)) {
OutlinedButton(
content = { Text(stringResource(StringR.string.action_decline), style = ElementTextStyles.Button) },
content = { Text(stringResource(CommonStrings.action_decline), style = ElementTextStyles.Button) },
onClick = onDeclineClicked,
modifier = Modifier.weight(1f).heightIn(max = 36.dp),
modifier = Modifier
.weight(1f)
.heightIn(max = 36.dp),
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 0.dp),
)
Spacer(modifier = Modifier.width(12.dp))
Button(
content = { Text(stringResource(StringR.string.action_accept), style = ElementTextStyles.Button) },
content = { Text(stringResource(CommonStrings.action_accept), style = ElementTextStyles.Button) },
onClick = onAcceptClicked,
modifier = Modifier.weight(1f).heightIn(max = 36.dp),
modifier = Modifier
.weight(1f)
.heightIn(max = 36.dp),
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 0.dp),
)
}
}
val unreadIndicatorColor = if (invite.isNew) MaterialTheme.roomListUnreadIndicator() else Color.Transparent
Box(
modifier = Modifier
.size(12.dp)
.clip(CircleShape)
.background(unreadIndicatorColor)
)
UnreadIndicatorAtom(color = unreadIndicatorColor)
}
}
@ -178,7 +173,7 @@ private fun SenderRow(sender: InviteSender) {
modifier = Modifier.padding(top = 6.dp),
) {
Avatar(
avatarData = sender.avatarData.copy(size = AvatarSize.Custom(16.dp)),
avatarData = sender.avatarData,
)
Text(
text = stringResource(R.string.screen_invites_invited_you, sender.displayName, sender.userId.value).let { text ->

View file

@ -18,6 +18,7 @@ package io.element.android.features.invitelist.impl.model
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 io.element.android.libraries.matrix.api.core.UserId
@ -26,14 +27,14 @@ data class InviteListInviteSummary(
val roomId: RoomId,
val roomName: String = "",
val roomAlias: String? = null,
val roomAvatarData: AvatarData = AvatarData(roomId.value, roomName),
val roomAvatarData: AvatarData = AvatarData(roomId.value, roomName, size = AvatarSize.RoomInviteItem),
val sender: InviteSender? = null,
val isDirect: Boolean = false,
val isNew: Boolean = false,
)
data class InviteSender(
data class InviteSender constructor(
val userId: UserId,
val displayName: String,
val avatarData: AvatarData = AvatarData(userId.value, displayName),
val avatarData: AvatarData = AvatarData(userId.value, displayName, size = AvatarSize.InviteSender),
)

View file

@ -20,9 +20,11 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import io.element.android.features.analytics.test.FakeAnalyticsService
import io.element.android.features.invitelist.test.FakeSeenInvitesStore
import io.element.android.libraries.architecture.Async
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 io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipState
@ -50,6 +52,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
),
FakeSeenInvitesStore(),
FakeAnalyticsService(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -74,6 +77,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
),
FakeSeenInvitesStore(),
FakeAnalyticsService(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -88,6 +92,7 @@ class InviteListPresenterTests {
id = A_USER_ID.value,
name = A_USER_NAME,
url = AN_AVATAR_URL,
size = AvatarSize.RoomInviteItem,
)
)
Truth.assertThat(withInviteState.inviteList[0].sender).isNull()
@ -102,6 +107,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
),
FakeSeenInvitesStore(),
FakeAnalyticsService(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -115,6 +121,7 @@ class InviteListPresenterTests {
id = A_USER_ID.value,
name = A_USER_NAME,
url = AN_AVATAR_URL,
size = AvatarSize.InviteSender,
)
)
}
@ -128,6 +135,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
),
FakeSeenInvitesStore(),
FakeAnalyticsService(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -152,6 +160,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
),
FakeSeenInvitesStore(),
FakeAnalyticsService(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -176,6 +185,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
),
FakeSeenInvitesStore(),
FakeAnalyticsService(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -199,7 +209,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
)
val room = FakeMatrixRoom()
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
val presenter = InviteListPresenter(client, FakeSeenInvitesStore(), FakeAnalyticsService())
client.givenGetRoomResult(A_ROOM_ID, room)
moleculeFlow(RecompositionClock.Immediate) {
@ -225,7 +235,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
)
val room = FakeMatrixRoom()
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
val presenter = InviteListPresenter(client, FakeSeenInvitesStore(), FakeAnalyticsService())
val ex = Throwable("Ruh roh!")
room.givenRejectInviteResult(Result.failure(ex))
client.givenGetRoomResult(A_ROOM_ID, room)
@ -256,7 +266,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
)
val room = FakeMatrixRoom()
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
val presenter = InviteListPresenter(client, FakeSeenInvitesStore(), FakeAnalyticsService())
val ex = Throwable("Ruh roh!")
room.givenRejectInviteResult(Result.failure(ex))
client.givenGetRoomResult(A_ROOM_ID, room)
@ -288,7 +298,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
)
val room = FakeMatrixRoom()
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
val presenter = InviteListPresenter(client, FakeSeenInvitesStore(), FakeAnalyticsService())
client.givenGetRoomResult(A_ROOM_ID, room)
moleculeFlow(RecompositionClock.Immediate) {
@ -311,7 +321,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
)
val room = FakeMatrixRoom()
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
val presenter = InviteListPresenter(client, FakeSeenInvitesStore(), FakeAnalyticsService())
val ex = Throwable("Ruh roh!")
room.givenAcceptInviteResult(Result.failure(ex))
client.givenGetRoomResult(A_ROOM_ID, room)
@ -336,7 +346,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
)
val room = FakeMatrixRoom()
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
val presenter = InviteListPresenter(client, FakeSeenInvitesStore(), FakeAnalyticsService())
val ex = Throwable("Ruh roh!")
room.givenAcceptInviteResult(Result.failure(ex))
client.givenGetRoomResult(A_ROOM_ID, room)
@ -365,6 +375,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
),
store,
FakeAnalyticsService(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -401,6 +412,7 @@ class InviteListPresenterTests {
roomSummaryDataSource = roomSummaryDataSource,
),
store,
FakeAnalyticsService(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()