mirror of
https://github.com/Steffo99/twom.git
synced 2024-11-25 17:44:24 +00:00
Add support for sending invites
This commit is contained in:
parent
822a2e5770
commit
23de2ed278
21 changed files with 271 additions and 178 deletions
|
@ -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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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") }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 ?: "?",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
.combinedClickable(
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onLongClick = { expanded = true },
|
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 }
|
||||||
) {
|
) {
|
||||||
|
if (member.userId != session.myUserId) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = {
|
text = {
|
||||||
Text(stringResource(R.string.room_uninvite_label))
|
Text(stringResource(R.string.room_uninvite_label))
|
||||||
},
|
},
|
||||||
onClick = { expanded = false }
|
onClick = { expanded = false },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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()) }
|
||||||
|
|
|
@ -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 })
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue