Invite : remove invitelist entry points

This commit is contained in:
ganfra 2024-04-16 11:41:55 +02:00
parent 43e336cb72
commit 4dbcd072c0
29 changed files with 16 additions and 1237 deletions

View file

@ -1,45 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.invite.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.invite.api.InviteListEntryPoint
import io.element.android.features.invite.impl.invitelist.InviteListNode
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultInviteListEntryPoint @Inject constructor() : InviteListEntryPoint {
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): InviteListEntryPoint.NodeBuilder {
val plugins = ArrayList<Plugin>()
return object : InviteListEntryPoint.NodeBuilder {
override fun callback(callback: InviteListEntryPoint.Callback): InviteListEntryPoint.NodeBuilder {
plugins += callback
return this
}
override fun build(): Node {
return parentNode.createNode<InviteListNode>(buildContext, plugins)
}
}
}
}

View file

@ -1,56 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.invite.impl
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringSetPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.invite.api.SeenInvitesStore
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_seeninvites")
private val seenInvitesKey = stringSetPreferencesKey("seenInvites")
@ContributesBinding(SessionScope::class)
class DefaultSeenInvitesStore @Inject constructor(
@ApplicationContext context: Context
) : SeenInvitesStore {
private val store = context.dataStore
override fun seenRoomIds(): Flow<Set<RoomId>> =
store.data.map { prefs ->
prefs[seenInvitesKey]
.orEmpty()
.map { RoomId(it) }
.toSet()
}
override suspend fun markAsSeen(roomIds: Set<RoomId>) {
store.edit { prefs ->
prefs[seenInvitesKey] = roomIds.map { it.value }.toSet()
}
}
}

View file

@ -1,195 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.invite.impl.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.invite.impl.R
import io.element.android.features.invite.impl.model.InviteListInviteSummary
import io.element.android.features.invite.impl.model.InviteListInviteSummaryProvider
import io.element.android.features.invite.impl.model.InviteSender
import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom
import io.element.android.libraries.designsystem.components.avatar.Avatar
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.ButtonSize
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
private val minHeight = 72.dp
@Composable
internal fun InviteSummaryRow(
invite: InviteListInviteSummary,
onAcceptClicked: () -> Unit,
onDeclineClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.fillMaxWidth()
.heightIn(min = minHeight)
) {
DefaultInviteSummaryRow(
invite = invite,
onAcceptClicked = onAcceptClicked,
onDeclineClicked = onDeclineClicked,
)
}
}
@Composable
private fun DefaultInviteSummaryRow(
invite: InviteListInviteSummary,
onAcceptClicked: () -> Unit,
onDeclineClicked: () -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.height(IntrinsicSize.Min),
verticalAlignment = Alignment.Top
) {
Avatar(
invite.roomAvatarData,
)
Column(
modifier = Modifier
.padding(start = 16.dp, end = 4.dp)
.alignByBaseline()
.weight(1f)
) {
val bonusPadding = if (invite.isNew) 12.dp else 0.dp
// Name
Text(
text = invite.roomName,
color = MaterialTheme.colorScheme.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = ElementTheme.typography.fontBodyLgMedium,
modifier = Modifier.padding(end = bonusPadding),
)
// ID or Alias
invite.roomAlias?.let {
Text(
style = ElementTheme.typography.fontBodyMdRegular,
text = it,
color = MaterialTheme.colorScheme.secondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(end = bonusPadding),
)
}
// Sender
invite.sender?.let { sender ->
SenderRow(sender = sender)
}
// CTAs
Row(Modifier.padding(top = 12.dp)) {
OutlinedButton(
text = stringResource(CommonStrings.action_decline),
onClick = onDeclineClicked,
modifier = Modifier.weight(1f),
size = ButtonSize.Medium,
)
Spacer(modifier = Modifier.width(12.dp))
Button(
text = stringResource(CommonStrings.action_accept),
onClick = onAcceptClicked,
modifier = Modifier.weight(1f),
size = ButtonSize.Medium,
)
}
}
UnreadIndicatorAtom(isVisible = invite.isNew)
}
}
@Composable
private fun SenderRow(sender: InviteSender) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.padding(top = 6.dp),
) {
Avatar(
avatarData = sender.avatarData,
)
Text(
text = stringResource(R.string.screen_invites_invited_you, sender.displayName, sender.userId.value).let { text ->
val senderNameStart = LocalContext.current.getString(R.string.screen_invites_invited_you).indexOf("%1\$s")
AnnotatedString(
text = text,
spanStyles = listOf(
AnnotatedString.Range(
SpanStyle(
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.primary
),
start = senderNameStart,
end = senderNameStart + sender.displayName.length
)
)
)
},
style = ElementTheme.typography.fontBodyMdRegular,
color = MaterialTheme.colorScheme.secondary,
)
}
}
@PreviewsDayNight
@Composable
internal fun InviteSummaryRowPreview(@PreviewParameter(InviteListInviteSummaryProvider::class) data: InviteListInviteSummary) = ElementPreview {
InviteSummaryRow(
invite = data,
onAcceptClicked = {},
onDeclineClicked = {},
)
}

View file

@ -1,24 +0,0 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.invite.impl.invitelist
import io.element.android.features.invite.impl.model.InviteListInviteSummary
sealed interface InviteListEvents {
data class AcceptInvite(val invite: InviteListInviteSummary) : InviteListEvents
data class DeclineInvite(val invite: InviteListInviteSummary) : InviteListEvents
}

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.invite.impl.invitelist
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.invite.api.InviteListEntryPoint
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId
@ContributesNode(SessionScope::class)
class InviteListNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: InviteListPresenter,
) : Node(buildContext, plugins = plugins) {
private fun onBackClicked() {
plugins<InviteListEntryPoint.Callback>().forEach { it.onBackClicked() }
}
private fun onInviteAccepted(roomId: RoomId) {
plugins<InviteListEntryPoint.Callback>().forEach { it.onInviteAccepted(roomId) }
}
private fun onInviteClicked(roomId: RoomId) {
plugins<InviteListEntryPoint.Callback>().forEach { it.onInviteClicked(roomId) }
}
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
InviteListView(
state = state,
onBackClicked = ::onBackClicked,
onInviteAccepted = ::onInviteAccepted,
onInviteDeclined = {},
onInviteClicked = ::onInviteClicked,
)
}
}

View file

@ -1,157 +0,0 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.invite.impl.invitelist
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import io.element.android.features.invite.api.SeenInvitesStore
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.api.response.InviteData
import io.element.android.features.invite.impl.model.InviteListInviteSummary
import io.element.android.features.invite.impl.model.InviteSender
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
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.first
import javax.inject.Inject
class InviteListPresenter @Inject constructor(
private val client: MatrixClient,
private val store: SeenInvitesStore,
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
) : Presenter<InviteListState> {
@Composable
override fun present(): InviteListState {
val invites by client
.roomListService
.invites
.summaries
.collectAsState(initial = emptyList())
var seenInvites by remember { mutableStateOf<Set<RoomId>>(emptySet()) }
LaunchedEffect(Unit) {
seenInvites = store.seenRoomIds().first()
}
LaunchedEffect(invites) {
store.markAsSeen(
invites
.filterIsInstance<RoomSummary.Filled>()
.map { it.details.roomId }
.toSet()
)
}
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
fun handleEvent(event: InviteListEvents) {
when (event) {
is InviteListEvents.AcceptInvite -> {
acceptDeclineInviteState.eventSink(
AcceptDeclineInviteEvents.AcceptInvite(event.invite.toInviteData())
)
}
is InviteListEvents.DeclineInvite -> {
acceptDeclineInviteState.eventSink(
AcceptDeclineInviteEvents.DeclineInvite(event.invite.toInviteData())
)
}
}
}
val inviteList = remember(seenInvites, invites) {
invites
.filterIsInstance<RoomSummary.Filled>()
.map {
it.toInviteSummary(seenInvites.contains(it.details.roomId))
}
.toPersistentList()
}
return InviteListState(
inviteList = inviteList,
acceptDeclineInviteState = acceptDeclineInviteState,
eventSink = ::handleEvent
)
}
private fun RoomSummary.Filled.toInviteSummary(seen: Boolean) = details.run {
val i = inviter
val avatarData = if (isDirect && i != null) {
AvatarData(
id = i.userId.value,
name = i.displayName,
url = i.avatarUrl,
size = AvatarSize.RoomInviteItem,
)
} else {
AvatarData(
id = roomId.value,
name = name,
url = avatarUrl,
size = AvatarSize.RoomInviteItem,
)
}
val alias = if (isDirect) {
inviter?.userId?.value
} else {
canonicalAlias
}
InviteListInviteSummary(
roomId = roomId,
roomName = name,
roomAlias = alias,
roomAvatarData = avatarData,
isDirect = isDirect,
isNew = !seen,
sender = inviter
?.takeIf { !isDirect }
?.run {
InviteSender(
userId = userId,
displayName = displayName ?: "",
avatarData = AvatarData(
id = userId.value,
name = displayName,
url = avatarUrl,
size = AvatarSize.InviteSender,
),
)
},
)
}
private fun InviteListInviteSummary.toInviteData() = InviteData(
roomId = roomId,
roomName = roomName,
isDirect = isDirect,
)
}

View file

@ -1,29 +0,0 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.invite.impl.invitelist
import androidx.compose.runtime.Immutable
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.impl.model.InviteListInviteSummary
import kotlinx.collections.immutable.ImmutableList
@Immutable
data class InviteListState(
val inviteList: ImmutableList<InviteListInviteSummary>,
val acceptDeclineInviteState: AcceptDeclineInviteState,
val eventSink: (InviteListEvents) -> Unit
)

View file

@ -1,77 +0,0 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.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>
get() = sequenceOf(
anInviteListState(),
anInviteListState(inviteList = persistentListOf()),
) + acceptDeclineInviteStateProvider.values.map { acceptDeclineInviteState ->
anInviteListState(acceptDeclineInviteState = acceptDeclineInviteState)
}
}
internal fun anInviteListState(
inviteList: ImmutableList<InviteListInviteSummary> = aInviteListInviteSummaryList(),
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
eventSink: (InviteListEvents) -> Unit = {}
) = InviteListState(
inviteList = inviteList,
acceptDeclineInviteState = acceptDeclineInviteState,
eventSink = eventSink,
)
internal fun aInviteListInviteSummaryList(): ImmutableList<InviteListInviteSummary> {
return persistentListOf(
InviteListInviteSummary(
roomId = RoomId("!id1:example.com"),
roomName = "Room 1",
roomAlias = "#room:example.org",
sender = InviteSender(
userId = UserId("@alice:example.org"),
displayName = "Alice"
),
),
InviteListInviteSummary(
roomId = RoomId("!id2:example.com"),
roomName = "Room 2",
sender = InviteSender(
userId = UserId("@bob:example.org"),
displayName = "Bob"
),
),
InviteListInviteSummary(
roomId = RoomId("!id3:example.com"),
roomName = "Alice",
roomAlias = "@alice:example.com"
),
)
}

View file

@ -1,148 +0,0 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.invite.impl.invitelist
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.invite.impl.R
import io.element.android.features.invite.impl.components.InviteSummaryRow
import io.element.android.features.invite.impl.response.AcceptDeclineInviteView
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.HorizontalDivider
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.CommonStrings
@Composable
fun InviteListView(
state: InviteListState,
onBackClicked: () -> Unit,
onInviteAccepted: (RoomId) -> Unit,
onInviteDeclined: (RoomId) -> Unit,
onInviteClicked: (RoomId) -> Unit,
modifier: Modifier = Modifier,
) {
InviteListContent(
state = state,
modifier = modifier,
onInviteClicked = onInviteClicked,
onBackClicked = onBackClicked,
)
AcceptDeclineInviteView(
state = state.acceptDeclineInviteState,
onInviteAccepted = onInviteAccepted,
onInviteDeclined = onInviteDeclined,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun InviteListContent(
state: InviteListState,
onBackClicked: () -> Unit,
onInviteClicked: (RoomId) -> Unit,
modifier: Modifier = Modifier,
) {
Scaffold(
modifier = modifier,
topBar = {
TopAppBar(
navigationIcon = {
BackButton(onClick = onBackClicked)
},
title = {
Text(
text = stringResource(CommonStrings.action_invites_list),
style = ElementTheme.typography.aliasScreenTitle,
)
}
)
},
content = { padding ->
Column(
modifier = Modifier
.padding(padding)
.consumeWindowInsets(padding)
) {
if (state.inviteList.isEmpty()) {
Spacer(Modifier.size(80.dp))
Text(
text = stringResource(R.string.screen_invites_empty_list),
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.fillMaxWidth()
)
} else {
LazyColumn(
modifier = Modifier.weight(1f)
) {
itemsIndexed(
items = state.inviteList,
) { index, invite ->
InviteSummaryRow(
modifier = Modifier.clickable(
onClick = { onInviteClicked(invite.roomId) }
),
invite = invite,
onAcceptClicked = { state.eventSink(InviteListEvents.AcceptInvite(invite)) },
onDeclineClicked = { state.eventSink(InviteListEvents.DeclineInvite(invite)) },
)
if (index != state.inviteList.lastIndex) {
HorizontalDivider()
}
}
}
}
}
}
)
}
@PreviewsDayNight
@Composable
internal fun InviteListViewPreview(@PreviewParameter(InviteListStateProvider::class) state: InviteListState) = ElementPreview {
InviteListView(
state = state,
onBackClicked = {},
onInviteAccepted = {},
onInviteDeclined = {},
onInviteClicked = {},
)
}

View file

@ -1,40 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.invite.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
@Immutable
data class InviteListInviteSummary(
val roomId: RoomId,
val roomName: String = "",
val roomAlias: String? = null,
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(
val userId: UserId,
val displayName: String,
val avatarData: AvatarData = AvatarData(userId.value, displayName, size = AvatarSize.InviteSender),
)

View file

@ -1,41 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.invite.impl.model
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
open class InviteListInviteSummaryProvider : PreviewParameterProvider<InviteListInviteSummary> {
override val values: Sequence<InviteListInviteSummary>
get() = sequenceOf(
aInviteListInviteSummary(),
aInviteListInviteSummary().copy(roomAlias = "#someroom-with-a-long-alias:example.com"),
aInviteListInviteSummary().copy(roomAlias = "#someroom-with-a-long-alias:example.com", isNew = true),
aInviteListInviteSummary().copy(roomName = "Alice", sender = null),
aInviteListInviteSummary().copy(isNew = true)
)
}
fun aInviteListInviteSummary() = InviteListInviteSummary(
roomId = RoomId("!room1:example.com"),
roomName = "Some room with a long name that will truncate",
sender = InviteSender(
userId = UserId("@alice-with-a-long-mxid:example.org"),
displayName = "Alice with a long name"
),
)

View file

@ -21,7 +21,6 @@ import app.cash.molecule.moleculeFlow
import app.cash.turbine.TurbineTestContext
import app.cash.turbine.test
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.test.FakeSeenInvitesStore