1
Fork 0
mirror of https://github.com/Steffo99/twom.git synced 2024-11-21 23:54:26 +00:00

Add basic invite button

This commit is contained in:
Steffo 2024-01-09 08:19:27 +01:00
parent acf9f34f59
commit cca38d0026
Signed by: steffo
GPG key ID: 2A24051445686895
9 changed files with 252 additions and 142 deletions

View file

@ -1,5 +1,11 @@
package eu.steffo.twom.room
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Cancel
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.Circle
import androidx.compose.material.icons.outlined.Help
import androidx.compose.material.icons.outlined.Schedule
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.livedata.observeAsState
@ -11,11 +17,6 @@ import eu.steffo.twom.theme.colorRoleMaybe
import eu.steffo.twom.theme.colorRoleNoway
import eu.steffo.twom.theme.colorRoleSure
import eu.steffo.twom.theme.colorRoleUnknown
import eu.steffo.twom.theme.iconLater
import eu.steffo.twom.theme.iconMaybe
import eu.steffo.twom.theme.iconNoway
import eu.steffo.twom.theme.iconSure
import eu.steffo.twom.theme.iconUnknown
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.Room
@ -42,11 +43,11 @@ fun RSVPAnswer.toStaticColorRole(): StaticColorRole {
fun RSVPAnswer.toIcon(): ImageVector {
return when (this) {
RSVPAnswer.SURE -> iconSure
RSVPAnswer.LATER -> iconLater
RSVPAnswer.MAYBE -> iconMaybe
RSVPAnswer.NOWAY -> iconNoway
RSVPAnswer.UNKNOWN -> iconUnknown
RSVPAnswer.SURE -> Icons.Outlined.CheckCircle
RSVPAnswer.LATER -> Icons.Outlined.Schedule
RSVPAnswer.MAYBE -> Icons.Outlined.Help
RSVPAnswer.NOWAY -> Icons.Outlined.Cancel
RSVPAnswer.UNKNOWN -> Icons.Outlined.Circle
}
}
@ -80,16 +81,6 @@ fun RSVPAnswer.toPlaceholderResourceId(): Int {
}
}
fun RSVPAnswer.toEmoji(): String {
return when (this) {
RSVPAnswer.SURE -> ""
RSVPAnswer.LATER -> "\uD83D\uDD52"
RSVPAnswer.MAYBE -> ""
RSVPAnswer.NOWAY -> "⛔️"
RSVPAnswer.UNKNOWN -> ""
}
}
fun makeRSVP(request: State<Optional<Event>?>?): Triple<Event, RSVPAnswer, String>? {
val event = request?.value?.getOrNull() ?: return null
val content = event.content ?: return null
@ -118,10 +109,10 @@ fun makeRSVP(request: State<Optional<Event>?>?): Triple<Event, RSVPAnswer, Strin
@Composable
fun observeRsvpAsLiveState(room: Room, userId: String): Triple<Event, RSVPAnswer, String>? {
val request = room.stateService().getStateEventLive(
val stateRequest = room.stateService().getStateEventLive(
eventType = "eu.steffo.twom.rsvp",
stateKey = QueryStringValue.Equals(userId),
).observeAsState()
return makeRSVP(request)
}
return makeRSVP(stateRequest)
}

View file

@ -1,8 +1,12 @@
package eu.steffo.twom.room
import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -19,7 +23,6 @@ import eu.steffo.twom.matrix.LocalSession
import eu.steffo.twom.theme.ErrorText
import eu.steffo.twom.theme.TwoMPadding
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.failure.Failure
import kotlin.jvm.optionals.getOrNull
@ -77,115 +80,178 @@ fun RoomActivityContent(
roomSummary.otherMemberIds.map { it to observeRsvpAsLiveState(room = room, userId = it) }
var isUpdatingMyRsvp by rememberSaveable { mutableStateOf(false) }
var errorMyRsvp by rememberSaveable { mutableStateOf<Failure.ServerError?>(null) }
var errorMyRsvp by rememberSaveable { mutableStateOf<Throwable?>(null) }
var isSendingInvite by rememberSaveable { mutableStateOf(false) }
var errorInvite by rememberSaveable { mutableStateOf<Throwable?>(null) }
Column(modifier) {
Row(TwoMPadding.base) {
Text(
text = stringResource(R.string.room_topic_title),
style = MaterialTheme.typography.labelLarge,
)
}
Row(TwoMPadding.base) {
Text(roomSummary.topic)
}
Row(TwoMPadding.base) {
Text(
text = stringResource(R.string.room_rsvp_title),
style = MaterialTheme.typography.labelLarge,
)
}
RoomActivityAnswerForm(
// FIXME: This always set the request to UNKNOWN
currentRsvpAnswer = myRsvpRequest?.second ?: RSVPAnswer.UNKNOWN,
currentRsvpComment = myRsvpRequest?.third ?: "",
onUpdate = { answer, comment ->
isUpdatingMyRsvp = true
errorMyRsvp = null
scope.launch SendRSVP@{
Log.d(
"Room",
"Updating eu.steffo.twom.rsvp with answer `$answer` and comment `$comment`..."
)
try {
room.stateService().sendStateEvent(
eventType = "eu.steffo.twom.rsvp",
stateKey = session.myUserId,
body = mapOf(
pairs = arrayOf(
"answer" to answer.toString(),
"comment" to comment,
)
),
)
} catch (error: Failure.ServerError) {
Log.e("Room", "Failed to update eu.steffo.twom.rsvp: $error")
errorMyRsvp = error
isUpdatingMyRsvp = false
return@SendRSVP
}
Log.d(
"Room",
"Updated eu.steffo.twom.rsvp with answer `$answer` and comment `$comment`!"
Box(
modifier = modifier
.verticalScroll(rememberScrollState())
) {
Column(
modifier = Modifier.fillMaxHeight()
) {
if (roomSummary.topic != "") {
Row(TwoMPadding.base) {
Text(
text = stringResource(R.string.room_topic_title),
style = MaterialTheme.typography.labelLarge,
)
}
Row(TwoMPadding.base) {
Text(roomSummary.topic)
}
}
if (myRsvpRequest != null) {
val myRsvpRequestEventId = myRsvpRequest.first.eventId
Row(TwoMPadding.base) {
Text(
text = stringResource(R.string.room_rsvp_title),
style = MaterialTheme.typography.labelLarge,
)
}
RoomActivityAnswerForm(
// FIXME: This always set the request to UNKNOWN
currentRsvpAnswer = myRsvpRequest?.second ?: RSVPAnswer.UNKNOWN,
currentRsvpComment = myRsvpRequest?.third ?: "",
onUpdate = { answer, comment ->
isUpdatingMyRsvp = true
errorMyRsvp = null
scope.launch SendRSVP@{
Log.d(
"Room",
"Attempting to redact old eu.steffo.twom.rsvp event `${myRsvpRequestEventId}`..."
"Updating eu.steffo.twom.rsvp with answer `$answer` and comment `$comment`..."
)
try {
room.sendService()
.redactEvent(myRsvpRequest.first, "Replaced with new information")
} catch (error: Failure.ServerError) {
Log.e("Room", "Failed to redact the old eu.steffo.twom.rsvp: $error")
room.stateService().sendStateEvent(
eventType = "eu.steffo.twom.rsvp",
stateKey = session.myUserId,
body = mapOf(
pairs = arrayOf(
"answer" to answer.toString(),
"comment" to comment,
)
),
)
} catch (error: Exception) {
Log.e("Room", "Failed to update eu.steffo.twom.rsvp: $error")
errorMyRsvp = error
isUpdatingMyRsvp = false
return@SendRSVP
}
} else {
Log.d("Room", "Not doing anything else; there isn't anything to redact.")
Log.d(
"Room",
"Updated eu.steffo.twom.rsvp with answer `$answer` and comment `$comment`!"
)
if (myRsvpRequest != null) {
val myRsvpRequestEventId = myRsvpRequest.first.eventId
Log.d(
"Room",
"Attempting to redact old eu.steffo.twom.rsvp event `${myRsvpRequestEventId}`..."
)
try {
room.sendService()
.redactEvent(
myRsvpRequest.first,
"Replaced with new information"
)
} catch (error: Throwable) {
Log.e(
"Room",
"Failed to redact the old eu.steffo.twom.rsvp: $error"
)
errorMyRsvp = error
isUpdatingMyRsvp = false
return@SendRSVP
}
} else {
Log.d(
"Room",
"Not doing anything else; there isn't anything to redact."
)
}
isUpdatingMyRsvp = false
}
},
isUpdating = isUpdatingMyRsvp,
)
isUpdatingMyRsvp = false
if (errorMyRsvp != null) {
// TODO: Maybe add an human-friendly error message?
Row(TwoMPadding.base) {
ErrorText(
errorMyRsvp.toString()
)
}
},
isUpdating = isUpdatingMyRsvp,
)
}
if (errorMyRsvp != null) {
// TODO: Maybe add an human-friendly error message?
Row(TwoMPadding.base) {
ErrorText(
errorMyRsvp.toString()
Text(
text = stringResource(R.string.room_invitees_title),
style = MaterialTheme.typography.labelLarge,
)
}
}
Row(TwoMPadding.base) {
Text(
text = stringResource(R.string.room_invitees_title),
style = MaterialTheme.typography.labelLarge,
)
}
Column(TwoMPadding.base) {
MemberListItem(
memberId = LocalSession.current!!.myUserId,
rsvpAnswer = myRsvpRequest?.second ?: RSVPAnswer.UNKNOWN,
rsvpComment = myRsvpRequest?.third ?: "",
)
otherRsvpRequests.forEach {
Column(TwoMPadding.base) {
MemberListItem(
memberId = it.first,
rsvpAnswer = it.second?.second ?: RSVPAnswer.UNKNOWN,
rsvpComment = it.second?.third ?: "",
memberId = LocalSession.current!!.myUserId,
rsvpAnswer = myRsvpRequest?.second ?: RSVPAnswer.UNKNOWN,
rsvpComment = myRsvpRequest?.third ?: "",
)
// FIXME: This also displays invited members!
otherRsvpRequests.forEach {
MemberListItem(
memberId = it.first,
rsvpAnswer = it.second?.second ?: RSVPAnswer.UNKNOWN,
rsvpComment = it.second?.third ?: "",
)
}
}
Row(TwoMPadding.base) {
Text(
text = stringResource(R.string.room_invite_title),
style = MaterialTheme.typography.labelLarge,
)
}
Row(TwoMPadding.base) {
RoomActivityInviteForm(
busy = isSendingInvite,
onSend = {
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
}
}
)
}
if (errorInvite != null) {
// TODO: Maybe add an human-friendly error message?
Row(TwoMPadding.base) {
ErrorText(
errorInvite.toString()
)
}
}
}
}

View file

@ -0,0 +1,76 @@
package eu.steffo.twom.room
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
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
import eu.steffo.twom.theme.colorRoleUnknown
@Composable
@Preview
fun RoomActivityInviteForm(
modifier: Modifier = Modifier,
busy: Boolean = false,
onSend: (userId: String) -> Unit = {},
) {
var value by rememberSaveable { mutableStateOf("") }
val colorRole = colorRoleUnknown()
Column(modifier) {
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)
)
},
colors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = colorRole.valueContainer,
unfocusedContainerColor = colorRole.valueContainer,
focusedTextColor = colorRole.onValueContainer,
unfocusedTextColor = colorRole.onValueContainer,
focusedBorderColor = colorRole.onValueContainer,
unfocusedBorderColor = colorRole.onValueContainer.copy(alpha = 0.3f),
cursorColor = colorRole.onValueContainer,
)
)
Button(
modifier = Modifier
.padding(top = 4.dp)
.fillMaxWidth(),
onClick = { onSend(value) },
shape = MaterialTheme.shapes.small,
// FIXME: Maybe I should validate usernames with a regex
enabled = (value.contains("@") && value.contains(":") && !busy),
colors = ButtonDefaults.buttonColors(
containerColor = colorRole.value,
contentColor = colorRole.onValue,
)
) {
Text(
text = stringResource(R.string.room_invite_button_label)
)
}
}
}

View file

@ -1,6 +0,0 @@
package eu.steffo.twom.theme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Schedule
val iconLater = Icons.Outlined.Schedule

View file

@ -1,6 +0,0 @@
package eu.steffo.twom.theme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.PauseCircle
val iconMaybe = Icons.Outlined.PauseCircle

View file

@ -1,6 +0,0 @@
package eu.steffo.twom.theme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.RemoveCircleOutline
val iconNoway = Icons.Outlined.RemoveCircleOutline

View file

@ -1,6 +0,0 @@
package eu.steffo.twom.theme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CheckCircle
val iconSure = Icons.Outlined.CheckCircle

View file

@ -1,6 +0,0 @@
package eu.steffo.twom.theme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Circle
val iconUnknown = Icons.Outlined.Circle

View file

@ -61,4 +61,11 @@
<string name="room_rsvp_unknown_label">No answer</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_invite_username_placeholder">\@steffotwo:candy.steffo.eu</string>
<string name="room_invite_username_label">Username</string>
<string name="room_rsvp_comment_label">Reason</string>
<string name="room_invite_button_label">Invite</string>
<string name="room_invite_title">Send an invite</string>
<string name="room_rsvp_invited_label">Not opened</string>
<string name="room_rsvp_invited_response">Hasn\'t opened the invite yet</string>
</resources>