1
Fork 0
mirror of https://github.com/Steffo99/twom.git synced 2024-10-16 14:37:33 +00:00

Add support for sending invites

This commit is contained in:
Steffo 2024-01-20 13:29:55 +01:00
parent 822a2e5770
commit 23de2ed278
Signed by: steffo
GPG key ID: 5ADA3868646C3FC0
21 changed files with 271 additions and 178 deletions

View file

@ -7,7 +7,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContract
import eu.steffo.twom.composables.configureroom.CreateRoomScaffold import eu.steffo.twom.composables.configureroom.ConfigureRoomScaffold
class ConfigureRoomActivity : ComponentActivity() { class ConfigureRoomActivity : ComponentActivity() {
@ -51,6 +51,6 @@ class ConfigureRoomActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { CreateRoomScaffold() } setContent { ConfigureRoomScaffold() }
} }
} }

View file

@ -1,35 +0,0 @@
package eu.steffo.twom.activities
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContract
import androidx.compose.material3.Text
class InviteUserActivity : ComponentActivity() {
companion object {
const val USER_EXTRA = "user"
}
class Contract : ActivityResultContract<Unit, String?>() {
override fun createIntent(context: Context, input: Unit): Intent {
return Intent(context, ConfigureRoomActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): String? {
return when (resultCode) {
RESULT_OK -> intent!!.getStringExtra(USER_EXTRA)
else -> null
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { Text("Garasauto Prime") }
}
}

View file

@ -17,16 +17,17 @@ import androidx.compose.ui.tooling.preview.Preview
fun AvatarEmpty( fun AvatarEmpty(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
text: String? = null, text: String? = null,
alpha: Float = 1.0f,
) { ) {
Box( Box(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colorScheme.tertiary), .background(MaterialTheme.colorScheme.tertiary.copy(alpha = alpha)),
) { ) {
Text( Text(
modifier = Modifier modifier = Modifier
.align(Alignment.Center), .align(Alignment.Center),
color = MaterialTheme.colorScheme.onTertiary, color = MaterialTheme.colorScheme.onTertiary.copy(alpha = alpha),
text = text ?: "?", text = text ?: "?",
) )
} }

View file

@ -17,6 +17,7 @@ fun AvatarImage(
bitmap: ImageBitmap? = null, bitmap: ImageBitmap? = null,
fallbackText: String? = null, fallbackText: String? = null,
contentDescription: String = "", contentDescription: String = "",
alpha: Float = 1.0f,
) { ) {
if (bitmap == null) { if (bitmap == null) {
AvatarEmpty( AvatarEmpty(
@ -25,6 +26,7 @@ fun AvatarImage(
this.contentDescription = contentDescription this.contentDescription = contentDescription
}, },
text = fallbackText, text = fallbackText,
alpha = alpha,
) )
} else { } else {
Image( Image(
@ -33,6 +35,7 @@ fun AvatarImage(
contentDescription = contentDescription, contentDescription = contentDescription,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
alignment = Alignment.Center, alignment = Alignment.Center,
alpha = alpha,
) )
} }
} }

View file

@ -23,6 +23,7 @@ fun AvatarPicker(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
fallbackText: String = "?", fallbackText: String = "?",
onPick: (bitmap: Bitmap) -> Unit = {}, onPick: (bitmap: Bitmap) -> Unit = {},
alpha: Float = 1.0f,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val resolver = context.contentResolver val resolver = context.contentResolver
@ -51,6 +52,7 @@ fun AvatarPicker(
AvatarImage( AvatarImage(
bitmap = selection?.asImageBitmap(), bitmap = selection?.asImageBitmap(),
fallbackText = fallbackText, fallbackText = fallbackText,
alpha = alpha,
) )
} }
} }

View file

@ -22,6 +22,7 @@ fun AvatarURL(
url: String? = "", url: String? = "",
fallbackText: String? = null, fallbackText: String? = null,
contentDescription: String = "", contentDescription: String = "",
alpha: Float = 1.0f,
) { ) {
val session = LocalSession.current val session = LocalSession.current
var bitmap by remember { mutableStateOf<Bitmap?>(null) } var bitmap by remember { mutableStateOf<Bitmap?>(null) }
@ -67,5 +68,6 @@ fun AvatarURL(
bitmap = bitmap?.asImageBitmap(), bitmap = bitmap?.asImageBitmap(),
fallbackText = fallbackText, fallbackText = fallbackText,
contentDescription = contentDescription, contentDescription = contentDescription,
alpha = alpha,
) )
} }

View file

@ -13,11 +13,13 @@ fun AvatarUser(
user: User? = null, user: User? = null,
fallbackText: String? = null, fallbackText: String? = null,
contentDescription: String = "", contentDescription: String = "",
alpha: Float = 1.0f,
) { ) {
AvatarURL( AvatarURL(
modifier = modifier, modifier = modifier,
url = user?.avatarUrl, url = user?.avatarUrl,
fallbackText = user?.toMatrixItem()?.firstLetterOfDisplayName(), fallbackText = user?.toMatrixItem()?.firstLetterOfDisplayName(),
contentDescription = contentDescription, contentDescription = contentDescription,
alpha = alpha,
) )
} }

View file

@ -18,6 +18,7 @@ fun AvatarUserId(
userId: String = "", userId: String = "",
fallbackText: String = "?", fallbackText: String = "?",
contentDescription: String = "", contentDescription: String = "",
alpha: Float = 1.0f,
) { ) {
val session = LocalSession.current val session = LocalSession.current
var avatarUrl by rememberSaveable { mutableStateOf<String?>(null) } var avatarUrl by rememberSaveable { mutableStateOf<String?>(null) }
@ -43,5 +44,6 @@ fun AvatarUserId(
url = avatarUrl, url = avatarUrl,
fallbackText = fallbackText, fallbackText = fallbackText,
contentDescription = contentDescription, contentDescription = contentDescription,
alpha = alpha,
) )
} }

View file

@ -32,7 +32,7 @@ import eu.steffo.twom.utils.BitmapUtilities
@Preview(showBackground = true) @Preview(showBackground = true)
fun ConfigureRoomForm( fun ConfigureRoomForm(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onSubmit: (name: String, description: String, avatarUri: Uri?) -> Unit = { _, _, _ -> }, onSubmit: (name: String, description: String, avatarUri: String?) -> Unit = { _, _, _ -> },
) { ) {
var name by rememberSaveable { mutableStateOf("") } var name by rememberSaveable { mutableStateOf("") }
var description by rememberSaveable { mutableStateOf("") } var description by rememberSaveable { mutableStateOf("") }
@ -85,7 +85,7 @@ fun ConfigureRoomForm(
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
onClick = { onClick = {
onSubmit(name, description, avatarUri) onSubmit(name, description, avatarUri.toString())
}, },
) { ) {
Text(stringResource(R.string.create_complete_text)) Text(stringResource(R.string.create_complete_text))

View file

@ -2,7 +2,6 @@ package eu.steffo.twom.composables.configureroom
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.net.Uri
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@ -15,11 +14,11 @@ import eu.steffo.twom.composables.theme.TwoMTheme
@Composable @Composable
@Preview @Preview
fun CreateRoomScaffold() { fun ConfigureRoomScaffold() {
val context = LocalContext.current val context = LocalContext.current
val activity = context as Activity val activity = context as Activity
fun submitActivity(name: String, description: String, avatarUri: Uri?) { fun submitActivity(name: String, description: String, avatarUri: String?) {
val resultIntent = Intent() val resultIntent = Intent()
resultIntent.putExtra(ConfigureRoomActivity.NAME_EXTRA, name) resultIntent.putExtra(ConfigureRoomActivity.NAME_EXTRA, name)
resultIntent.putExtra(ConfigureRoomActivity.DESCRIPTION_EXTRA, description) resultIntent.putExtra(ConfigureRoomActivity.DESCRIPTION_EXTRA, description)
@ -34,12 +33,12 @@ fun CreateRoomScaffold() {
TwoMTheme { TwoMTheme {
Scaffold( Scaffold(
topBar = { topBar = {
CreateActivityTopBar() ConfigureActivityTopBar()
}, },
content = { content = {
ConfigureRoomForm( ConfigureRoomForm(
modifier = Modifier.padding(it), modifier = Modifier.padding(it),
onSubmit = { name: String, description: String, avatarUri: Uri? -> onSubmit = { name: String, description: String, avatarUri: String? ->
submitActivity(name, description, avatarUri) submitActivity(name, description, avatarUri)
} }
) )

View file

@ -13,7 +13,7 @@ import eu.steffo.twom.composables.navigation.BackIconButton
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@Preview @Preview
fun CreateActivityTopBar( fun ConfigureActivityTopBar(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
TopAppBar( TopAppBar(

View file

@ -1,47 +0,0 @@
package eu.steffo.twom.composables.inviteuser
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.steffo.twom.R
import eu.steffo.twom.composables.theme.basePadding
@Composable
fun InviteUserContent() {
Row(Modifier.basePadding()) {
Text(
text = stringResource(R.string.room_invite_title),
style = MaterialTheme.typography.labelLarge,
)
}
Row(Modifier.basePadding()) {
InviteUserForm(
/*
onConfirm = {
scope.launch SendInvite@{
isSendingInvite = true
errorInvite = null
Log.d("Room", "Sending invite to `$it`...")
try {
room.membershipService().invite(it)
} catch (error: Throwable) {
Log.e("Room", "Failed to send invite to `$it`: $error")
errorInvite = error
isSendingInvite = false
return@SendInvite
}
Log.d("Room", "Successfully sent invite to `$it`!")
isSendingInvite = false
}
}
*/
)
}
}

View file

@ -1,54 +0,0 @@
package eu.steffo.twom.composables.inviteuser
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import eu.steffo.twom.R
@Composable
@Preview
fun InviteUserForm(
onConfirm: (userId: String) -> Unit = {},
) {
var value by rememberSaveable { mutableStateOf("") }
OutlinedTextField(
modifier = Modifier
.fillMaxWidth(),
value = value,
onValueChange = { value = it },
singleLine = true,
shape = MaterialTheme.shapes.small,
placeholder = {
Text(
text = stringResource(R.string.room_invite_username_placeholder)
)
},
)
Button(
modifier = Modifier
.padding(top = 4.dp)
.fillMaxWidth(),
onClick = { onConfirm(value) },
shape = MaterialTheme.shapes.small,
// FIXME: Maybe I should validate usernames with a regex
enabled = (value.contains("@") && value.contains(":")),
) {
Text(
text = stringResource(R.string.room_invite_button_label)
)
}
}

View file

@ -1,9 +1,7 @@
package eu.steffo.twom.composables.viewroom package eu.steffo.twom.composables.viewroom
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.launch
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Email
import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -12,27 +10,20 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import eu.steffo.twom.R import eu.steffo.twom.R
import eu.steffo.twom.activities.InviteUserActivity
@Composable @Composable
@Preview @Preview
fun InviteFAB( fun InviteFAB(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: () -> Unit = {},
onUserSelected: (userId: String) -> Unit = {}, onUserSelected: (userId: String) -> Unit = {},
) { ) {
val launcher =
rememberLauncherForActivityResult(InviteUserActivity.Contract()) {
if (it != null) {
onUserSelected(it)
}
}
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
modifier = modifier, modifier = modifier,
onClick = { launcher.launch() }, onClick = { onClick() },
icon = { icon = {
Icon( Icon(
Icons.Filled.Add, Icons.Filled.Email,
contentDescription = null contentDescription = null
) )
}, },

View file

@ -0,0 +1,72 @@
package eu.steffo.twom.composables.viewroom
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import eu.steffo.twom.R
import eu.steffo.twom.composables.theme.basePadding
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun InviteSheet(
sheetState: SheetState,
modifier: Modifier = Modifier,
onDismissed: () -> Unit = {},
onCompleted: () -> Unit = {},
) {
val scope = rememberCoroutineScope()
ModalBottomSheet(
modifier = modifier,
sheetState = sheetState,
onDismissRequest = {
// Not super sure what this is for
// https://developer.android.com/jetpack/compose/components/bottom-sheets
scope.launch {
sheetState.hide()
}.invokeOnCompletion {
if (!sheetState.isVisible) {
onDismissed()
}
}
},
) {
// Hack required as it seems that ModalBottomSheet does not take in account screen insets yet
Column(Modifier.padding(bottom = 80.dp)) {
Text(
modifier = Modifier
.basePadding()
.fillMaxWidth(),
text = stringResource(R.string.room_invite_title),
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center,
)
InviteUserForm(
onDone = {
scope.launch {
sheetState.hide()
}.invokeOnCompletion {
if (!sheetState.isVisible) {
onCompleted()
}
}
}
)
}
}
}

View file

@ -0,0 +1,114 @@
package eu.steffo.twom.composables.viewroom
import android.util.Log
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.steffo.twom.R
import eu.steffo.twom.composables.errorhandling.ErrorText
import eu.steffo.twom.composables.errorhandling.LoadingText
import eu.steffo.twom.composables.errorhandling.LocalizableError
import eu.steffo.twom.composables.theme.basePadding
import kotlinx.coroutines.launch
import kotlin.jvm.optionals.getOrNull
@Composable
fun InviteUserForm(
onDone: () -> Unit = {},
) {
val scope = rememberCoroutineScope()
var userId by rememberSaveable { mutableStateOf("") }
val roomRequest = LocalRoom.current
if (roomRequest == null) {
LoadingText(
modifier = Modifier.basePadding(),
)
return
}
val room = roomRequest.getOrNull()
if (room == null) {
ErrorText(
modifier = Modifier.basePadding(),
text = stringResource(R.string.room_error_room_notfound)
)
return
}
TextField(
modifier = Modifier
.basePadding()
.fillMaxWidth(),
value = userId,
onValueChange = { userId = it },
singleLine = true,
label = {
Text(
text = stringResource(R.string.room_invite_username_label)
)
},
placeholder = {
Text(
text = stringResource(R.string.room_invite_username_placeholder)
)
},
)
var busy by rememberSaveable { mutableStateOf(false) }
val error by remember { mutableStateOf(LocalizableError()) }
Button(
modifier = Modifier
.basePadding()
.fillMaxWidth(),
// FIXME: Maybe I should validate usernames with a regex
enabled = (!busy && userId.contains("@") && userId.contains(":")),
onClick = {
scope.launch SendInvite@{
busy = true
error.clear()
Log.d("Room", "Sending invite to `$userId`...")
try {
room.membershipService().invite(userId)
} catch (e: Throwable) {
Log.e("Room", "Failed to send invite to `$userId`: $error")
error.set(R.string.room_error_invite_generic, e)
busy = false
return@SendInvite
}
Log.d("Room", "Successfully sent invite to `$userId`!")
onDone()
busy = false
}
},
) {
Text(
text = stringResource(R.string.room_invite_button_label)
)
}
error.Show {
ErrorText(
modifier = Modifier
.basePadding()
.fillMaxWidth(),
text = it,
)
}
}

View file

@ -27,6 +27,7 @@ import eu.steffo.twom.R
import eu.steffo.twom.composables.avatar.AvatarUser import eu.steffo.twom.composables.avatar.AvatarUser
import eu.steffo.twom.composables.errorhandling.ErrorText import eu.steffo.twom.composables.errorhandling.ErrorText
import eu.steffo.twom.composables.matrix.LocalSession import eu.steffo.twom.composables.matrix.LocalSession
import eu.steffo.twom.utils.RSVPAnswer
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.user.model.User
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
@ -72,18 +73,24 @@ fun MemberListItem(
Log.d("UserListItem", "Resolved user: $memberId") Log.d("UserListItem", "Resolved user: $memberId")
} }
val rsvp = observeRSVP(room = room, member = member) val rsvp = observeRSVP(room = room, member = member) ?: return
var expanded by rememberSaveable { mutableStateOf(false) } var expanded by rememberSaveable { mutableStateOf(false) }
val alpha = if (rsvp.answer == RSVPAnswer.PENDING) 0.4f else 1.0f
val color = rsvp.answer.staticColorRole.color().copy(alpha)
ListItem( ListItem(
modifier = modifier.combinedClickable( modifier = modifier
onClick = {}, .combinedClickable(
onLongClick = { expanded = true }, onClick = {},
), onLongClick = { expanded = true },
),
headlineContent = { headlineContent = {
Text( Text(
text = user?.displayName ?: stringResource(R.string.user_unresolved_name), text = user?.displayName ?: stringResource(R.string.user_unresolved_name),
color = color,
style = MaterialTheme.typography.titleMedium,
) )
}, },
leadingContent = { leadingContent = {
@ -95,6 +102,7 @@ fun MemberListItem(
) { ) {
AvatarUser( AvatarUser(
user = user, user = user,
alpha = alpha,
) )
} }
}, },
@ -102,14 +110,14 @@ fun MemberListItem(
Icon( Icon(
imageVector = rsvp.answer.icon, imageVector = rsvp.answer.icon,
contentDescription = rsvp.answer.toResponse(), contentDescription = rsvp.answer.toResponse(),
tint = rsvp.answer.staticColorRole.color(), tint = color,
) )
}, },
supportingContent = { supportingContent = {
if (rsvp.comment != "") { if (rsvp.comment != "") {
Text( Text(
text = rsvp.comment, text = rsvp.comment,
color = rsvp.answer.staticColorRole.color(), color = color,
) )
} }
}, },
@ -119,11 +127,13 @@ fun MemberListItem(
expanded = expanded, expanded = expanded,
onDismissRequest = { expanded = false } onDismissRequest = { expanded = false }
) { ) {
DropdownMenuItem( if (member.userId != session.myUserId) {
text = { DropdownMenuItem(
Text(stringResource(R.string.room_uninvite_label)) text = {
}, Text(stringResource(R.string.room_uninvite_label))
onClick = { expanded = false } },
) onClick = { expanded = false },
)
}
} }
} }

View file

@ -20,6 +20,7 @@ import eu.steffo.twom.composables.errorhandling.LocalizableError
import eu.steffo.twom.composables.matrix.LocalSession import eu.steffo.twom.composables.matrix.LocalSession
import eu.steffo.twom.composables.theme.basePadding import eu.steffo.twom.composables.theme.basePadding
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.room.model.Membership
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
@Composable @Composable
@ -62,7 +63,7 @@ fun ViewRoomForm() {
} }
val member = room.membershipService().getRoomMember(session.myUserId) val member = room.membershipService().getRoomMember(session.myUserId)
if (member == null) { if (member == null || member.membership != Membership.JOIN) {
Row(Modifier.basePadding()) { Row(Modifier.basePadding()) {
ErrorText( ErrorText(
text = stringResource(R.string.room_error_self_notfound) text = stringResource(R.string.room_error_self_notfound)
@ -71,7 +72,8 @@ fun ViewRoomForm() {
return return
} }
val published = observeRSVP(room = room, member = member) // JOIN status is checked above
val published = observeRSVP(room = room, member = member)!!
var isPublishRunning by rememberSaveable { mutableStateOf(false) } var isPublishRunning by rememberSaveable { mutableStateOf(false) }
val publishError by remember { mutableStateOf(LocalizableError()) } val publishError by remember { mutableStateOf(LocalizableError()) }

View file

@ -1,17 +1,25 @@
package eu.steffo.twom.composables.viewroom package eu.steffo.twom.composables.viewroom
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import eu.steffo.twom.composables.matrix.LocalSession import eu.steffo.twom.composables.matrix.LocalSession
import eu.steffo.twom.composables.theme.TwoMTheme import eu.steffo.twom.composables.theme.TwoMTheme
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import java.util.Optional import java.util.Optional
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable @Composable
fun ViewRoomScaffold( fun ViewRoomScaffold(
session: Session, session: Session,
@ -20,6 +28,9 @@ fun ViewRoomScaffold(
val room = Optional.ofNullable(session.roomService().getRoom(roomId)) val room = Optional.ofNullable(session.roomService().getRoom(roomId))
val roomSummary by session.roomService().getRoomSummaryLive(roomId).observeAsState() val roomSummary by session.roomService().getRoomSummaryLive(roomId).observeAsState()
val inviteDialogState = rememberModalBottomSheetState()
var inviteDialogExpanded by rememberSaveable { mutableStateOf(false) }
TwoMTheme { TwoMTheme {
CompositionLocalProvider(LocalSession provides session) { CompositionLocalProvider(LocalSession provides session) {
CompositionLocalProvider(LocalRoom provides room) { CompositionLocalProvider(LocalRoom provides room) {
@ -32,7 +43,19 @@ fun ViewRoomScaffold(
ViewRoomContent( ViewRoomContent(
modifier = Modifier.padding(it), modifier = Modifier.padding(it),
) )
if (inviteDialogExpanded) {
InviteSheet(
// FIXME: Does this work?
modifier = Modifier.consumeWindowInsets(it),
sheetState = inviteDialogState,
onDismissed = { inviteDialogExpanded = false },
onCompleted = { inviteDialogExpanded = false },
)
}
}, },
floatingActionButton = {
InviteFAB(onClick = { inviteDialogExpanded = true })
}
) )
} }
} }

View file

@ -12,7 +12,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
@Composable @Composable
fun observeRSVP(room: Room, member: RoomMemberSummary): RSVP { fun observeRSVP(room: Room, member: RoomMemberSummary): RSVP? {
if (member.membership == Membership.INVITE) { if (member.membership == Membership.INVITE) {
return RSVP( return RSVP(
event = null, event = null,
@ -21,6 +21,11 @@ fun observeRSVP(room: Room, member: RoomMemberSummary): RSVP {
) )
} }
// TODO: Add a DECLINED variant?
if (member.membership == Membership.LEAVE || member.membership == Membership.BAN) {
return null
}
val request by room.stateService().getStateEventLive( val request by room.stateService().getStateEventLive(
eventType = TwoMGlobals.RSVP_STATE_TYPE, eventType = TwoMGlobals.RSVP_STATE_TYPE,
stateKey = QueryStringValue.Equals(member.userId), stateKey = QueryStringValue.Equals(member.userId),

View file

@ -59,7 +59,7 @@
<string name="room_error_roomsummary_missing">The Matrix room summary context has not been initialized.</string> <string name="room_error_roomsummary_missing">The Matrix room summary context has not been initialized.</string>
<string name="room_error_roomsummary_notfound">Could not find the requested Matrix room summary.</string> <string name="room_error_roomsummary_notfound">Could not find the requested Matrix room summary.</string>
<string name="room_invite_username_placeholder">\@steffotwo:candy.steffo.eu</string> <string name="room_invite_username_placeholder">\@steffotwo:candy.steffo.eu</string>
<string name="room_invite_username_label">Username</string> <string name="room_invite_username_label">User you want to invite</string>
<string name="room_rsvp_comment_label">Reason</string> <string name="room_rsvp_comment_label">Reason</string>
<string name="room_invite_button_label">Invite</string> <string name="room_invite_button_label">Invite</string>
<string name="room_invite_title">Send an invite</string> <string name="room_invite_title">Send an invite</string>
@ -83,4 +83,5 @@
<string name="room_error_publish_generic">Something went wrong while updating your RSVP: %1$s</string> <string name="room_error_publish_generic">Something went wrong while updating your RSVP: %1$s</string>
<string name="room_error_redact_generic">Your response has been updated, but something went wrong while attempting to remove your previous one: %1$s</string> <string name="room_error_redact_generic">Your response has been updated, but something went wrong while attempting to remove your previous one: %1$s</string>
<string name="room_error_self_notfound">You have been removed from the room.</string> <string name="room_error_self_notfound">You have been removed from the room.</string>
<string name="room_error_invite_generic">Something went wrong while sending the invite: %1$s</string>
</resources> </resources>