Merge pull request #3206 from element-hq/feature/bma/updateGrammar
Update grammar on Matrix Ids to be more spec compliant and render error instead of infinite loading in room member list screen
This commit is contained in:
commit
dea8f78cd6
9 changed files with 280 additions and 137 deletions
|
|
@ -31,6 +31,7 @@ import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
|
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
|
||||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
|
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
|
@ -59,10 +60,10 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomMemberListState {
|
override fun present(): RoomMemberListState {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var roomMembers by remember { mutableStateOf(RoomMembers.loading()) }
|
var roomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
|
||||||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||||
var searchResults by remember {
|
var searchResults by remember {
|
||||||
mutableStateOf<SearchBarResultState<RoomMembers>>(SearchBarResultState.Initial())
|
mutableStateOf<SearchBarResultState<AsyncData<RoomMembers>>>(SearchBarResultState.Initial())
|
||||||
}
|
}
|
||||||
var isSearchActive by rememberSaveable { mutableStateOf(false) }
|
var isSearchActive by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|
@ -82,6 +83,12 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
||||||
if (membersState is MatrixRoomMembersState.Unknown) {
|
if (membersState is MatrixRoomMembersState.Unknown) {
|
||||||
return@LaunchedEffect
|
return@LaunchedEffect
|
||||||
}
|
}
|
||||||
|
val finalMembersState = membersState
|
||||||
|
if (finalMembersState is MatrixRoomMembersState.Error && finalMembersState.roomMembers().orEmpty().isEmpty()) {
|
||||||
|
// Cannot fetch members and no cached members, display the error
|
||||||
|
roomMembers = AsyncData.Failure(finalMembersState.failure)
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
withContext(coroutineDispatchers.io) {
|
withContext(coroutineDispatchers.io) {
|
||||||
val members = membersState.roomMembers().orEmpty().groupBy { it.membership }
|
val members = membersState.roomMembers().orEmpty().groupBy { it.membership }
|
||||||
val info = room.roomInfoFlow.first()
|
val info = room.roomInfoFlow.first()
|
||||||
|
|
@ -90,14 +97,18 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
||||||
// This result will come from the timeline loading membership events and it'll be wrong.
|
// This result will come from the timeline loading membership events and it'll be wrong.
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
roomMembers = RoomMembers(
|
val result = RoomMembers(
|
||||||
invited = members.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(),
|
invited = members.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(),
|
||||||
joined = members.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
joined = members.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
||||||
.sortedWith(PowerLevelRoomMemberComparator())
|
.sortedWith(PowerLevelRoomMemberComparator())
|
||||||
.toImmutableList(),
|
.toImmutableList(),
|
||||||
banned = members.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(),
|
banned = members.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(),
|
||||||
isLoading = membersState is MatrixRoomMembersState.Pending,
|
|
||||||
)
|
)
|
||||||
|
roomMembers = if (membersState is MatrixRoomMembersState.Pending) {
|
||||||
|
AsyncData.Loading(result)
|
||||||
|
} else {
|
||||||
|
AsyncData.Success(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,15 +121,19 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
SearchBarResultState.NoResultsFound()
|
SearchBarResultState.NoResultsFound()
|
||||||
} else {
|
} else {
|
||||||
|
val result = RoomMembers(
|
||||||
|
invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(),
|
||||||
|
joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
||||||
|
.sortedWith(PowerLevelRoomMemberComparator())
|
||||||
|
.toImmutableList(),
|
||||||
|
banned = results.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(),
|
||||||
|
)
|
||||||
SearchBarResultState.Results(
|
SearchBarResultState.Results(
|
||||||
RoomMembers(
|
if (membersState is MatrixRoomMembersState.Pending) {
|
||||||
invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(),
|
AsyncData.Loading(result)
|
||||||
joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
} else {
|
||||||
.sortedWith(PowerLevelRoomMemberComparator())
|
AsyncData.Success(result)
|
||||||
.toImmutableList(),
|
}
|
||||||
banned = results.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(),
|
|
||||||
isLoading = membersState is MatrixRoomMembersState.Pending,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,15 @@
|
||||||
package io.element.android.features.roomdetails.impl.members
|
package io.element.android.features.roomdetails.impl.members
|
||||||
|
|
||||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
|
|
||||||
data class RoomMemberListState(
|
data class RoomMemberListState(
|
||||||
val roomMembers: RoomMembers,
|
val roomMembers: AsyncData<RoomMembers>,
|
||||||
val searchQuery: String,
|
val searchQuery: String,
|
||||||
val searchResults: SearchBarResultState<RoomMembers>,
|
val searchResults: SearchBarResultState<AsyncData<RoomMembers>>,
|
||||||
val isSearchActive: Boolean,
|
val isSearchActive: Boolean,
|
||||||
val canInvite: Boolean,
|
val canInvite: Boolean,
|
||||||
val moderationState: RoomMembersModerationState,
|
val moderationState: RoomMembersModerationState,
|
||||||
|
|
@ -36,14 +36,4 @@ data class RoomMembers(
|
||||||
val invited: ImmutableList<RoomMember>,
|
val invited: ImmutableList<RoomMember>,
|
||||||
val joined: ImmutableList<RoomMember>,
|
val joined: ImmutableList<RoomMember>,
|
||||||
val banned: ImmutableList<RoomMember>,
|
val banned: ImmutableList<RoomMember>,
|
||||||
val isLoading: Boolean,
|
)
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
fun loading() = RoomMembers(
|
|
||||||
invited = persistentListOf(),
|
|
||||||
joined = persistentListOf(),
|
|
||||||
banned = persistentListOf(),
|
|
||||||
isLoading = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.roomdetails.impl.members
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||||
import io.element.android.features.roomdetails.impl.members.moderation.aRoomMembersModerationState
|
import io.element.android.features.roomdetails.impl.members.moderation.aRoomMembersModerationState
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
|
|
@ -29,14 +30,15 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
|
||||||
override val values: Sequence<RoomMemberListState>
|
override val values: Sequence<RoomMemberListState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aRoomMemberListState(
|
aRoomMemberListState(
|
||||||
roomMembers = RoomMembers(
|
roomMembers = AsyncData.Success(
|
||||||
invited = persistentListOf(aVictor(), aWalter()),
|
RoomMembers(
|
||||||
joined = persistentListOf(anAlice(), aBob(), aWalter()),
|
invited = persistentListOf(aVictor(), aWalter()),
|
||||||
banned = persistentListOf(),
|
joined = persistentListOf(anAlice(), aBob(), aWalter()),
|
||||||
isLoading = false,
|
banned = persistentListOf(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
aRoomMemberListState(roomMembers = RoomMembers.loading()),
|
aRoomMemberListState(roomMembers = AsyncData.Loading()),
|
||||||
aRoomMemberListState().copy(canInvite = true),
|
aRoomMemberListState().copy(canInvite = true),
|
||||||
aRoomMemberListState().copy(isSearchActive = false),
|
aRoomMemberListState().copy(isSearchActive = false),
|
||||||
aRoomMemberListState().copy(isSearchActive = true),
|
aRoomMemberListState().copy(isSearchActive = true),
|
||||||
|
|
@ -45,11 +47,12 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
searchQuery = "@someone:matrix.org",
|
searchQuery = "@someone:matrix.org",
|
||||||
searchResults = SearchBarResultState.Results(
|
searchResults = SearchBarResultState.Results(
|
||||||
RoomMembers(
|
AsyncData.Success(
|
||||||
invited = persistentListOf(aVictor()),
|
RoomMembers(
|
||||||
joined = persistentListOf(anAlice()),
|
invited = persistentListOf(aVictor()),
|
||||||
banned = persistentListOf(),
|
joined = persistentListOf(anAlice()),
|
||||||
isLoading = false,
|
banned = persistentListOf(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -58,6 +61,9 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
|
||||||
searchQuery = "something-with-no-results",
|
searchQuery = "something-with-no-results",
|
||||||
searchResults = SearchBarResultState.NoResultsFound()
|
searchResults = SearchBarResultState.NoResultsFound()
|
||||||
),
|
),
|
||||||
|
aRoomMemberListState(
|
||||||
|
roomMembers = AsyncData.Failure(Exception("Error details")),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,37 +71,40 @@ internal class RoomMemberListStateBannedProvider : PreviewParameterProvider<Room
|
||||||
override val values: Sequence<RoomMemberListState>
|
override val values: Sequence<RoomMemberListState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aRoomMemberListState(
|
aRoomMemberListState(
|
||||||
roomMembers = RoomMembers(
|
roomMembers = AsyncData.Success(
|
||||||
invited = persistentListOf(),
|
RoomMembers(
|
||||||
joined = persistentListOf(),
|
invited = persistentListOf(),
|
||||||
banned = persistentListOf(
|
joined = persistentListOf(),
|
||||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"),
|
banned = persistentListOf(
|
||||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"),
|
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"),
|
||||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"),
|
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"),
|
||||||
),
|
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"),
|
||||||
isLoading = false,
|
),
|
||||||
|
)
|
||||||
),
|
),
|
||||||
moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true),
|
moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true),
|
||||||
),
|
),
|
||||||
aRoomMemberListState(
|
aRoomMemberListState(
|
||||||
roomMembers = RoomMembers(
|
roomMembers = AsyncData.Loading(
|
||||||
invited = persistentListOf(),
|
RoomMembers(
|
||||||
joined = persistentListOf(),
|
invited = persistentListOf(),
|
||||||
banned = persistentListOf(
|
joined = persistentListOf(),
|
||||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"),
|
banned = persistentListOf(
|
||||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"),
|
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"),
|
||||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"),
|
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"),
|
||||||
),
|
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"),
|
||||||
isLoading = true,
|
),
|
||||||
|
)
|
||||||
),
|
),
|
||||||
moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true),
|
moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true),
|
||||||
),
|
),
|
||||||
aRoomMemberListState(
|
aRoomMemberListState(
|
||||||
roomMembers = RoomMembers(
|
roomMembers = AsyncData.Success(
|
||||||
invited = persistentListOf(),
|
RoomMembers(
|
||||||
joined = persistentListOf(),
|
invited = persistentListOf(),
|
||||||
banned = persistentListOf(),
|
joined = persistentListOf(),
|
||||||
isLoading = false,
|
banned = persistentListOf(),
|
||||||
|
)
|
||||||
),
|
),
|
||||||
moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true),
|
moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true),
|
||||||
)
|
)
|
||||||
|
|
@ -103,8 +112,8 @@ internal class RoomMemberListStateBannedProvider : PreviewParameterProvider<Room
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun aRoomMemberListState(
|
internal fun aRoomMemberListState(
|
||||||
roomMembers: RoomMembers = RoomMembers.loading(),
|
roomMembers: AsyncData<RoomMembers> = AsyncData.Loading(),
|
||||||
searchResults: SearchBarResultState<RoomMembers> = SearchBarResultState.Initial(),
|
searchResults: SearchBarResultState<AsyncData<RoomMembers>> = SearchBarResultState.Initial(),
|
||||||
moderationState: RoomMembersModerationState = aRoomMembersModerationState(),
|
moderationState: RoomMembersModerationState = aRoomMembersModerationState(),
|
||||||
) = RoomMemberListState(
|
) = RoomMemberListState(
|
||||||
roomMembers = roomMembers,
|
roomMembers = roomMembers,
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.roomdetails.impl.R
|
import io.element.android.features.roomdetails.impl.R
|
||||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationView
|
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationView
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -128,7 +129,6 @@ fun RoomMemberListView(
|
||||||
|
|
||||||
if (!state.isSearchActive) {
|
if (!state.isSearchActive) {
|
||||||
RoomMemberList(
|
RoomMemberList(
|
||||||
isLoading = state.roomMembers.isLoading,
|
|
||||||
roomMembers = state.roomMembers,
|
roomMembers = state.roomMembers,
|
||||||
showMembersCount = true,
|
showMembersCount = true,
|
||||||
canDisplayBannedUsersControls = state.moderationState.canDisplayBannedUsers,
|
canDisplayBannedUsersControls = state.moderationState.canDisplayBannedUsers,
|
||||||
|
|
@ -149,8 +149,7 @@ fun RoomMemberListView(
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomMemberList(
|
private fun RoomMemberList(
|
||||||
isLoading: Boolean,
|
roomMembers: AsyncData<RoomMembers>,
|
||||||
roomMembers: RoomMembers,
|
|
||||||
showMembersCount: Boolean,
|
showMembersCount: Boolean,
|
||||||
selectedSection: SelectedSection,
|
selectedSection: SelectedSection,
|
||||||
onSelectedSectionChange: (SelectedSection) -> Unit,
|
onSelectedSectionChange: (SelectedSection) -> Unit,
|
||||||
|
|
@ -183,7 +182,7 @@ private fun RoomMemberList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = isLoading,
|
visible = roomMembers.isLoading(),
|
||||||
enter = fadeIn() + expandVertically(),
|
enter = fadeIn() + expandVertically(),
|
||||||
exit = fadeOut() + shrinkVertically(),
|
exit = fadeOut() + shrinkVertically(),
|
||||||
) {
|
) {
|
||||||
|
|
@ -191,47 +190,72 @@ private fun RoomMemberList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when (selectedSection) {
|
when (roomMembers) {
|
||||||
SelectedSection.MEMBERS -> {
|
is AsyncData.Failure -> failureItem(roomMembers.error)
|
||||||
if (roomMembers.invited.isNotEmpty()) {
|
is AsyncData.Loading,
|
||||||
roomMemberListSection(
|
is AsyncData.Success -> memberItems(
|
||||||
headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) },
|
roomMembers = roomMembers.dataOrNull() ?: return@LazyColumn,
|
||||||
members = roomMembers.invited,
|
selectedSection = selectedSection,
|
||||||
onMemberSelected = { onSelectUser(it) }
|
onSelectUser = onSelectUser,
|
||||||
)
|
showMembersCount = showMembersCount,
|
||||||
}
|
)
|
||||||
if (roomMembers.joined.isNotEmpty()) {
|
AsyncData.Uninitialized -> Unit
|
||||||
roomMemberListSection(
|
}
|
||||||
headerText = {
|
}
|
||||||
if (showMembersCount) {
|
}
|
||||||
val memberCount = roomMembers.joined.count()
|
|
||||||
pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount)
|
private fun LazyListScope.memberItems(
|
||||||
} else {
|
roomMembers: RoomMembers,
|
||||||
stringResource(id = R.string.screen_room_member_list_room_members_header_title)
|
selectedSection: SelectedSection,
|
||||||
}
|
onSelectUser: (RoomMember) -> Unit,
|
||||||
},
|
showMembersCount: Boolean,
|
||||||
members = roomMembers.joined,
|
) {
|
||||||
onMemberSelected = { onSelectUser(it) }
|
when (selectedSection) {
|
||||||
)
|
SelectedSection.MEMBERS -> {
|
||||||
}
|
if (roomMembers.invited.isNotEmpty()) {
|
||||||
|
roomMemberListSection(
|
||||||
|
headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) },
|
||||||
|
members = roomMembers.invited,
|
||||||
|
onMemberSelected = { onSelectUser(it) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
SelectedSection.BANNED -> { // Banned users
|
if (roomMembers.joined.isNotEmpty()) {
|
||||||
if (roomMembers.banned.isNotEmpty()) {
|
roomMemberListSection(
|
||||||
roomMemberListSection(
|
headerText = {
|
||||||
headerText = null,
|
if (showMembersCount) {
|
||||||
members = roomMembers.banned,
|
val memberCount = roomMembers.joined.count()
|
||||||
onMemberSelected = { onSelectUser(it) }
|
pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount)
|
||||||
)
|
} else {
|
||||||
} else {
|
stringResource(id = R.string.screen_room_member_list_room_members_header_title)
|
||||||
item {
|
|
||||||
Box(Modifier.fillParentMaxSize().padding(horizontal = 16.dp)) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(bottom = 56.dp).align(Alignment.Center),
|
|
||||||
text = stringResource(id = R.string.screen_room_member_list_banned_empty),
|
|
||||||
color = ElementTheme.colors.textSecondary,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
members = roomMembers.joined,
|
||||||
|
onMemberSelected = { onSelectUser(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SelectedSection.BANNED -> { // Banned users
|
||||||
|
if (roomMembers.banned.isNotEmpty()) {
|
||||||
|
roomMemberListSection(
|
||||||
|
headerText = null,
|
||||||
|
members = roomMembers.banned,
|
||||||
|
onMemberSelected = { onSelectUser(it) }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillParentMaxSize()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 56.dp)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
text = stringResource(id = R.string.screen_room_member_list_banned_empty),
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -239,9 +263,22 @@ private fun RoomMemberList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun LazyListScope.failureItem(failure: Throwable) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 32.dp),
|
||||||
|
text = stringResource(id = CommonStrings.error_unknown) + "\n\n" + failure.localizedMessage,
|
||||||
|
color = ElementTheme.colors.textCriticalPrimary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun LazyListScope.roomMemberListSection(
|
private fun LazyListScope.roomMemberListSection(
|
||||||
headerText: @Composable (() -> String)?,
|
headerText: @Composable (() -> String)?,
|
||||||
members: ImmutableList<RoomMember>,
|
members: ImmutableList<RoomMember>?,
|
||||||
onMemberSelected: (RoomMember) -> Unit,
|
onMemberSelected: (RoomMember) -> Unit,
|
||||||
) {
|
) {
|
||||||
headerText?.let {
|
headerText?.let {
|
||||||
|
|
@ -254,7 +291,7 @@ private fun LazyListScope.roomMemberListSection(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items(members) { matrixUser ->
|
items(members.orEmpty()) { matrixUser ->
|
||||||
RoomMemberListItem(
|
RoomMemberListItem(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
roomMember = matrixUser,
|
roomMember = matrixUser,
|
||||||
|
|
@ -320,7 +357,7 @@ private fun RoomMemberListTopBar(
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomMemberSearchBar(
|
private fun RoomMemberSearchBar(
|
||||||
query: String,
|
query: String,
|
||||||
state: SearchBarResultState<RoomMembers>,
|
state: SearchBarResultState<AsyncData<RoomMembers>>,
|
||||||
active: Boolean,
|
active: Boolean,
|
||||||
placeHolderTitle: String,
|
placeHolderTitle: String,
|
||||||
onActiveChange: (Boolean) -> Unit,
|
onActiveChange: (Boolean) -> Unit,
|
||||||
|
|
@ -339,7 +376,6 @@ private fun RoomMemberSearchBar(
|
||||||
resultState = state,
|
resultState = state,
|
||||||
resultHandler = { results ->
|
resultHandler = { results ->
|
||||||
RoomMemberList(
|
RoomMemberList(
|
||||||
isLoading = false,
|
|
||||||
roomMembers = results,
|
roomMembers = results,
|
||||||
showMembersCount = false,
|
showMembersCount = false,
|
||||||
onSelectUser = { onSelectUser(it) },
|
onSelectUser = { onSelectUser(it) },
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class RoomMemberListPresenterTest {
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.roomMembers.isLoading).isTrue()
|
assertThat(initialState.roomMembers.isLoading()).isTrue()
|
||||||
assertThat(initialState.searchQuery).isEmpty()
|
assertThat(initialState.searchQuery).isEmpty()
|
||||||
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
|
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
|
||||||
assertThat(initialState.isSearchActive).isFalse()
|
assertThat(initialState.isSearchActive).isFalse()
|
||||||
|
|
@ -70,9 +70,9 @@ class RoomMemberListPresenterTest {
|
||||||
// Skip item while the new members state is processed
|
// Skip item while the new members state is processed
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val loadedMembersState = awaitItem()
|
val loadedMembersState = awaitItem()
|
||||||
assertThat(loadedMembersState.roomMembers.isLoading).isFalse()
|
assertThat(loadedMembersState.roomMembers.isLoading()).isFalse()
|
||||||
assertThat(loadedMembersState.roomMembers.invited).isEqualTo(listOf(aVictor(), aWalter()))
|
assertThat(loadedMembersState.roomMembers.dataOrNull()?.invited).isEqualTo(listOf(aVictor(), aWalter()))
|
||||||
assertThat(loadedMembersState.roomMembers.joined).isNotEmpty()
|
assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +126,7 @@ class RoomMemberListPresenterTest {
|
||||||
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("Alice")
|
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("Alice")
|
||||||
val searchSearchResultDelivered = awaitItem()
|
val searchSearchResultDelivered = awaitItem()
|
||||||
assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.Results::class.java)
|
assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.Results::class.java)
|
||||||
assertThat((searchSearchResultDelivered.searchResults as SearchBarResultState.Results).results.joined.first().displayName)
|
assertThat((searchSearchResultDelivered.searchResults as SearchBarResultState.Results).results.dataOrNull()!!.joined.first().displayName)
|
||||||
.isEqualTo("Alice")
|
.isEqualTo("Alice")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,32 +27,40 @@ object MatrixPatterns {
|
||||||
// Note: TLD is not mandatory (localhost, IP address...)
|
// Note: TLD is not mandatory (localhost, IP address...)
|
||||||
private const val DOMAIN_REGEX = ":[A-Za-z0-9.-]+(:[0-9]{2,5})?"
|
private const val DOMAIN_REGEX = ":[A-Za-z0-9.-]+(:[0-9]{2,5})?"
|
||||||
|
|
||||||
|
private const val BASE_64_ALPHABET = "[0-9A-Za-z/\\+=]+"
|
||||||
|
private const val BASE_64_URL_SAFE_ALPHABET = "[0-9A-Za-z/\\-_]+"
|
||||||
|
|
||||||
// regex pattern to find matrix user ids in a string.
|
// regex pattern to find matrix user ids in a string.
|
||||||
// See https://matrix.org/docs/spec/appendices#historical-user-ids
|
// See https://matrix.org/docs/spec/appendices#historical-user-ids
|
||||||
// Sadly, we need to relax the regex pattern a bit as there already exist some ids that don't match the spec.
|
// Sadly, we need to relax the regex pattern a bit as there already exist some ids that don't match the spec.
|
||||||
private const val MATRIX_USER_IDENTIFIER_REGEX = "^@.*?$DOMAIN_REGEX$"
|
// Note: local part can be empty
|
||||||
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
private const val MATRIX_USER_IDENTIFIER_REGEX = "^@\\S*?$DOMAIN_REGEX$"
|
||||||
|
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex()
|
||||||
|
|
||||||
// regex pattern to find room ids in a string.
|
// regex pattern to match room ids.
|
||||||
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9.-]+$DOMAIN_REGEX"
|
// Note: roomId can be arbitrary strings, including space and new line char
|
||||||
private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "^!.+$DOMAIN_REGEX$"
|
||||||
|
private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.DOT_MATCHES_ALL)
|
||||||
|
|
||||||
// regex pattern to find room aliases in a string.
|
// regex pattern to match room aliases.
|
||||||
private const val MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#@=+-]+$DOMAIN_REGEX"
|
private const val MATRIX_ROOM_ALIAS_REGEX = "^#\\S+$DOMAIN_REGEX$"
|
||||||
private val PATTERN_CONTAIN_MATRIX_ALIAS = MATRIX_ROOM_ALIAS_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
private val PATTERN_CONTAIN_MATRIX_ALIAS = MATRIX_ROOM_ALIAS_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
// regex pattern to find message ids in a string.
|
// regex pattern to match event ids.
|
||||||
// Sadly, we need to relax the regex pattern a bit as there already exist some ids that don't match the spec.
|
// Sadly, we need to relax the regex pattern a bit as there already exist some ids that don't match the spec.
|
||||||
|
// v1 and v2: arbitrary string + domain
|
||||||
private const val MATRIX_EVENT_IDENTIFIER_REGEX = "^\\$.+$DOMAIN_REGEX$"
|
private const val MATRIX_EVENT_IDENTIFIER_REGEX = "^\\$.+$DOMAIN_REGEX$"
|
||||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex()
|
||||||
|
|
||||||
// regex pattern to find message ids in a string.
|
// v3: base64
|
||||||
private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+"
|
private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$$BASE_64_ALPHABET"
|
||||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex()
|
||||||
|
|
||||||
// Ref: https://matrix.org/docs/spec/rooms/v4#event-ids
|
// v4: url-safe base64
|
||||||
private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$[A-Z0-9\\-_]+"
|
private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$$BASE_64_URL_SAFE_ALPHABET"
|
||||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex()
|
||||||
|
|
||||||
|
private const val MAX_IDENTIFIER_LENGTH = 255
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells if a string is a valid user Id.
|
* Tells if a string is a valid user Id.
|
||||||
|
|
@ -61,7 +69,9 @@ object MatrixPatterns {
|
||||||
* @return true if the string is a valid user id
|
* @return true if the string is a valid user id
|
||||||
*/
|
*/
|
||||||
fun isUserId(str: String?): Boolean {
|
fun isUserId(str: String?): Boolean {
|
||||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER
|
return str != null &&
|
||||||
|
str.length <= MAX_IDENTIFIER_LENGTH &&
|
||||||
|
str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -79,7 +89,9 @@ object MatrixPatterns {
|
||||||
* @return true if the string is a valid room Id
|
* @return true if the string is a valid room Id
|
||||||
*/
|
*/
|
||||||
fun isRoomId(str: String?): Boolean {
|
fun isRoomId(str: String?): Boolean {
|
||||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER
|
return str != null &&
|
||||||
|
str.length <= MAX_IDENTIFIER_LENGTH &&
|
||||||
|
str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -89,7 +101,9 @@ object MatrixPatterns {
|
||||||
* @return true if the string is a valid room alias.
|
* @return true if the string is a valid room alias.
|
||||||
*/
|
*/
|
||||||
fun isRoomAlias(str: String?): Boolean {
|
fun isRoomAlias(str: String?): Boolean {
|
||||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_ALIAS
|
return str != null &&
|
||||||
|
str.length <= MAX_IDENTIFIER_LENGTH &&
|
||||||
|
str matches PATTERN_CONTAIN_MATRIX_ALIAS
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -100,9 +114,10 @@ object MatrixPatterns {
|
||||||
*/
|
*/
|
||||||
fun isEventId(str: String?): Boolean {
|
fun isEventId(str: String?): Boolean {
|
||||||
return str != null &&
|
return str != null &&
|
||||||
(str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER ||
|
str.length <= MAX_IDENTIFIER_LENGTH &&
|
||||||
|
(str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 ||
|
||||||
str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 ||
|
str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 ||
|
||||||
str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4)
|
str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -118,8 +133,8 @@ object MatrixPatterns {
|
||||||
* Note not all cases are implemented.
|
* Note not all cases are implemented.
|
||||||
*/
|
*/
|
||||||
fun findPatterns(text: CharSequence, permalinkParser: PermalinkParser): List<MatrixPatternResult> {
|
fun findPatterns(text: CharSequence, permalinkParser: PermalinkParser): List<MatrixPatternResult> {
|
||||||
val rawTextMatches = "\\S+?$DOMAIN_REGEX".toRegex(RegexOption.IGNORE_CASE).findAll(text)
|
val rawTextMatches = "\\S+$DOMAIN_REGEX".toRegex(RegexOption.IGNORE_CASE).findAll(text)
|
||||||
val urlMatches = "\\[\\S+?\\]\\((\\S+?)\\)".toRegex(RegexOption.IGNORE_CASE).findAll(text)
|
val urlMatches = "\\[\\S+\\]\\((\\S+)\\)".toRegex(RegexOption.IGNORE_CASE).findAll(text)
|
||||||
val atRoomMatches = Regex("@room").findAll(text)
|
val atRoomMatches = Regex("@room").findAll(text)
|
||||||
return buildList {
|
return buildList {
|
||||||
for (match in rawTextMatches) {
|
for (match in rawTextMatches) {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class MatrixPatternsTest {
|
class MatrixPatternsTest {
|
||||||
|
private val longLocalPart = "a".repeat(255 - ":server.com".length - 1)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `findPatterns - returns raw user ids`() {
|
fun `findPatterns - returns raw user ids`() {
|
||||||
val text = "A @user:server.com and @user2:server.com"
|
val text = "A @user:server.com and @user2:server.com"
|
||||||
|
|
@ -54,7 +56,7 @@ class MatrixPatternsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `findPatterns - returns raw room event ids`() {
|
fun `findPatterns - returns raw event ids`() {
|
||||||
val text = "A \$event:server.com and \$event2:server.com"
|
val text = "A \$event:server.com and \$event2:server.com"
|
||||||
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser())
|
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser())
|
||||||
assertThat(patterns).containsExactly(
|
assertThat(patterns).containsExactly(
|
||||||
|
|
@ -90,6 +92,76 @@ class MatrixPatternsTest {
|
||||||
assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room:server.com", 2, 46))
|
assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room:server.com", 2, 46))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test isRoomId`() {
|
||||||
|
assertThat(MatrixPatterns.isRoomId(null)).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomId("")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomId("not a room id")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomId(" !room:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomId("!room:server.com ")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomId("@room:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomId("#room:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomId("\$room:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomId("!${longLocalPart}a:server.com")).isFalse()
|
||||||
|
|
||||||
|
assertThat(MatrixPatterns.isRoomId("!room:server.com")).isTrue()
|
||||||
|
assertThat(MatrixPatterns.isRoomId("!$longLocalPart:server.com")).isTrue()
|
||||||
|
assertThat(MatrixPatterns.isRoomId("!#test/room\nversion <u>11</u>, with @🐈️:maunium.net")).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test isRoomAlias`() {
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias(null)).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias("")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias("not a room alias")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias(" #room:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias("#room:server.com ")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias("@room:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias("!room:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias("\$room:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias("#${longLocalPart}a:server.com")).isFalse()
|
||||||
|
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias("#room:server.com")).isTrue()
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias("#nico's-stickers:neko.dev")).isTrue()
|
||||||
|
assertThat(MatrixPatterns.isRoomAlias("#$longLocalPart:server.com")).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test isEventId`() {
|
||||||
|
assertThat(MatrixPatterns.isEventId(null)).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isEventId("")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isEventId("not an event id")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isEventId(" \$event:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isEventId("\$event:server.com ")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isEventId("@event:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isEventId("!event:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isEventId("#event:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isEventId("$${longLocalPart}a:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isEventId("\$" + "a".repeat(255))).isFalse()
|
||||||
|
|
||||||
|
assertThat(MatrixPatterns.isEventId("\$event:server.com")).isTrue()
|
||||||
|
assertThat(MatrixPatterns.isEventId("$$longLocalPart:server.com")).isTrue()
|
||||||
|
assertThat(MatrixPatterns.isEventId("\$9BozuV4TBw6rfRW3rMEgZ5v-jNk1D6FA8Hd1OsWqT9k")).isTrue()
|
||||||
|
assertThat(MatrixPatterns.isEventId("\$" + "a".repeat(254))).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test isUserId`() {
|
||||||
|
assertThat(MatrixPatterns.isUserId(null)).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isUserId("")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isUserId("not a user id")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isUserId(" @user:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isUserId("@user:server.com ")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isUserId("!user:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isUserId("#user:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isUserId("\$user:server.com")).isFalse()
|
||||||
|
assertThat(MatrixPatterns.isUserId("@${longLocalPart}a:server.com")).isFalse()
|
||||||
|
|
||||||
|
assertThat(MatrixPatterns.isUserId("@user:server.com")).isTrue()
|
||||||
|
assertThat(MatrixPatterns.isUserId("@:server.com")).isTrue()
|
||||||
|
assertThat(MatrixPatterns.isUserId("@$longLocalPart:server.com")).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
private fun aPermalinkParser(block: (String) -> PermalinkData = { PermalinkData.FallbackLink(Uri.EMPTY) }) = object : PermalinkParser {
|
private fun aPermalinkParser(block: (String) -> PermalinkData = { PermalinkData.FallbackLink(Uri.EMPTY) }) = object : PermalinkParser {
|
||||||
override fun parse(uriString: String): PermalinkData {
|
override fun parse(uriString: String): PermalinkData {
|
||||||
return block(uriString)
|
return block(uriString)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:cd04abc2eec057ef2dbd02e06c3f0ecd8e66fed3f760f293af4bb86c91e031f6
|
||||||
|
size 18284
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:93cd01b7fe046a90b49a20311941bb3a07e8d2bd71eb9335343f2c15713ce73e
|
||||||
|
size 17222
|
||||||
Loading…
Add table
Add a link
Reference in a new issue