Invite users to existing rooms (#441)

Invite users to existing rooms

Scope:

- Allow inviting from the room detail screen and the member list
- Invite option is only shown if the user has the correct power level
- Search flow the same as creating a new room, allowing multi-select
- Existing room members/invitees are disabled with a custom caption
- Sending is asynchronous, an error dialog will appear wherever the
  user is if necessary

Closes #245
This commit is contained in:
Chris Smith 2023-05-23 10:23:24 +01:00 committed by GitHub
parent 6825d8ac2b
commit 198d6d4c56
85 changed files with 1668 additions and 69 deletions

View file

@ -57,6 +57,7 @@ fun <T> SearchBar(
placeHolderTitle: String,
modifier: Modifier = Modifier,
enabled: Boolean = true,
showBackButton: Boolean = true,
resultState: SearchBarResultState<T> = SearchBarResultState.NotSearching(),
shape: Shape = SearchBarDefaults.inputFieldShape,
tonalElevation: Dp = SearchBarDefaults.Elevation,
@ -87,7 +88,7 @@ fun <T> SearchBar(
modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine)
)
},
leadingIcon = if (active) {
leadingIcon = if (showBackButton && active) {
{ BackButton(onClick = { onActiveChange(false) }) }
} else {
null
@ -179,6 +180,16 @@ internal fun SearchBarPreviewActiveWithQuery() = ElementThemedPreview {
)
}
@Preview(group = PreviewGroup.Search)
@Composable
internal fun SearchBarPreviewActiveWithQueryNoBackButton() = ElementThemedPreview {
ContentToPreview(
query = "search term",
active = true,
showBackButton = false,
)
}
@Preview(group = PreviewGroup.Search)
@Composable
internal fun SearchBarPreviewActiveWithNoResults() = ElementThemedPreview {
@ -212,6 +223,7 @@ internal fun SearchBarPreviewActiveWithContent() = ElementThemedPreview {
private fun ContentToPreview(
query: String = "",
active: Boolean = false,
showBackButton: Boolean = true,
resultState: SearchBarResultState<String> = SearchBarResultState.NotSearching(),
contentPrefix: @Composable ColumnScope.() -> Unit = {},
contentSuffix: @Composable ColumnScope.() -> Unit = {},
@ -221,6 +233,7 @@ private fun ContentToPreview(
query = query,
active = active,
resultState = resultState,
showBackButton = showBackButton,
onQueryChange = {},
onActiveChange = {},
placeHolderTitle = "Search for things",

View file

@ -85,4 +85,8 @@ interface MatrixRoom : Closeable {
suspend fun acceptInvitation(): Result<Unit>
suspend fun rejectInvitation(): Result<Unit>
suspend fun inviteUserById(id: UserId): Result<Unit>
suspend fun canInvite(): Result<Boolean>
}

View file

@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomMember
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
import org.matrix.rustcomponents.sdk.UpdateSummary
import org.matrix.rustcomponents.sdk.genTransactionId
@ -209,6 +210,18 @@ class RustMatrixRoom(
}
}
override suspend fun inviteUserById(id: UserId): Result<Unit> = withContext(coroutineDispatchers.io) {
runCatching {
innerRoom.inviteUserById(id.value)
}
}
override suspend fun canInvite(): Result<Boolean> = withContext(coroutineDispatchers.io) {
runCatching {
innerRoom.member(sessionId.value).use(RoomMember::canInvite)
}
}
override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo): Result<Unit> {
return runCatching {
innerRoom.sendImage(file.path, thumbnailFile.path, imageInfo.map())

View file

@ -152,6 +152,7 @@ class RustMatrixTimeline(
RequiredState(key = "m.room.canonical_alias", value = ""),
RequiredState(key = "m.room.topic", value = ""),
RequiredState(key = "m.room.join_rules", value = ""),
RequiredState(key = "m.room.power_levels", value = ""),
),
timelineLimit = null
)

View file

@ -59,6 +59,8 @@ class FakeMatrixRoom(
private var updateMembersResult: Result<Unit> = Result.success(Unit)
private var acceptInviteResult = Result.success(Unit)
private var rejectInviteResult = Result.success(Unit)
private var inviteUserResult = Result.success(Unit)
private var canInviteResult = Result.success(true)
private var sendMediaResult = Result.success(Unit)
var sendMediaCount = 0
private set
@ -69,6 +71,9 @@ class FakeMatrixRoom(
var isInviteRejected: Boolean = false
private set
var invitedUserId: UserId? = null
private set
private var leaveRoomError: Throwable? = null
override val membersStateFlow: MutableStateFlow<MatrixRoomMembersState> = MutableStateFlow(MatrixRoomMembersState.Unknown)
@ -136,6 +141,15 @@ class FakeMatrixRoom(
return rejectInviteResult
}
override suspend fun inviteUserById(id: UserId): Result<Unit> {
invitedUserId = id
return inviteUserResult
}
override suspend fun canInvite(): Result<Boolean> {
return canInviteResult
}
override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo): Result<Unit> = sendMediaResult.also { sendMediaCount++ }
override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo): Result<Unit> = sendMediaResult.also { sendMediaCount++ }
@ -174,6 +188,14 @@ class FakeMatrixRoom(
rejectInviteResult = result
}
fun givenInviteUserResult(result: Result<Unit>) {
inviteUserResult = result
}
fun givenCanInviteResult(result: Result<Boolean>) {
canInviteResult = result
}
fun givenIgnoreResult(result: Result<Unit>) {
ignoreResult = result
}

View file

@ -69,6 +69,7 @@
<string name="common_file">"File"</string>
<string name="common_gif">"GIF"</string>
<string name="common_image">"Image"</string>
<string name="common_leaving_room">"Leaving room"</string>
<string name="common_link_copied_to_clipboard">"Link copied to clipboard"</string>
<string name="common_loading">"Loading…"</string>
<string name="common_message">"Message"</string>
@ -98,6 +99,8 @@
<string name="common_suggestions">"Suggestions"</string>
<string name="common_topic">"Topic"</string>
<string name="common_unable_to_decrypt">"Unable to decrypt"</string>
<string name="common_unable_to_invite_message">"We were unable to successfully send invites to one or more users."</string>
<string name="common_unable_to_invite_title">"Unable to send invite(s)"</string>
<string name="common_unsupported_event">"Unsupported event"</string>
<string name="common_username">"Username"</string>
<string name="common_verification_cancelled">"Verification cancelled"</string>
@ -162,4 +165,4 @@
<string name="screen_analytics_settings_read_terms">"You can read all our terms %1$s."</string>
<string name="screen_analytics_settings_read_terms_content_link">"here"</string>
<string name="screen_report_content_block_user">"Block user"</string>
</resources>
</resources>