1
Fork 0
mirror of https://github.com/Steffo99/twom.git synced 2024-11-25 09:34:25 +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 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.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.livedata.observeAsState 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.colorRoleNoway
import eu.steffo.twom.theme.colorRoleSure import eu.steffo.twom.theme.colorRoleSure
import eu.steffo.twom.theme.colorRoleUnknown 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.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
@ -42,11 +43,11 @@ fun RSVPAnswer.toStaticColorRole(): StaticColorRole {
fun RSVPAnswer.toIcon(): ImageVector { fun RSVPAnswer.toIcon(): ImageVector {
return when (this) { return when (this) {
RSVPAnswer.SURE -> iconSure RSVPAnswer.SURE -> Icons.Outlined.CheckCircle
RSVPAnswer.LATER -> iconLater RSVPAnswer.LATER -> Icons.Outlined.Schedule
RSVPAnswer.MAYBE -> iconMaybe RSVPAnswer.MAYBE -> Icons.Outlined.Help
RSVPAnswer.NOWAY -> iconNoway RSVPAnswer.NOWAY -> Icons.Outlined.Cancel
RSVPAnswer.UNKNOWN -> iconUnknown 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>? { fun makeRSVP(request: State<Optional<Event>?>?): Triple<Event, RSVPAnswer, String>? {
val event = request?.value?.getOrNull() ?: return null val event = request?.value?.getOrNull() ?: return null
val content = event.content ?: return null val content = event.content ?: return null
@ -118,10 +109,10 @@ fun makeRSVP(request: State<Optional<Event>?>?): Triple<Event, RSVPAnswer, Strin
@Composable @Composable
fun observeRsvpAsLiveState(room: Room, userId: String): Triple<Event, RSVPAnswer, String>? { 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", eventType = "eu.steffo.twom.rsvp",
stateKey = QueryStringValue.Equals(userId), stateKey = QueryStringValue.Equals(userId),
).observeAsState() ).observeAsState()
return makeRSVP(request) return makeRSVP(stateRequest)
} }

View file

@ -1,8 +1,12 @@
package eu.steffo.twom.room package eu.steffo.twom.room
import android.util.Log import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row 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.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.ErrorText
import eu.steffo.twom.theme.TwoMPadding import eu.steffo.twom.theme.TwoMPadding
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.failure.Failure
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
@ -77,115 +80,178 @@ fun RoomActivityContent(
roomSummary.otherMemberIds.map { it to observeRsvpAsLiveState(room = room, userId = it) } roomSummary.otherMemberIds.map { it to observeRsvpAsLiveState(room = room, userId = it) }
var isUpdatingMyRsvp by rememberSaveable { mutableStateOf(false) } 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) { Box(
Row(TwoMPadding.base) { modifier = modifier
Text( .verticalScroll(rememberScrollState())
text = stringResource(R.string.room_topic_title), ) {
style = MaterialTheme.typography.labelLarge, Column(
) modifier = Modifier.fillMaxHeight()
} ) {
Row(TwoMPadding.base) { if (roomSummary.topic != "") {
Text(roomSummary.topic) Row(TwoMPadding.base) {
} Text(
text = stringResource(R.string.room_topic_title),
Row(TwoMPadding.base) { style = MaterialTheme.typography.labelLarge,
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`!"
) )
}
Row(TwoMPadding.base) {
Text(roomSummary.topic)
}
}
if (myRsvpRequest != null) { Row(TwoMPadding.base) {
val myRsvpRequestEventId = myRsvpRequest.first.eventId 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( Log.d(
"Room", "Room",
"Attempting to redact old eu.steffo.twom.rsvp event `${myRsvpRequestEventId}`..." "Updating eu.steffo.twom.rsvp with answer `$answer` and comment `$comment`..."
) )
try { try {
room.sendService() room.stateService().sendStateEvent(
.redactEvent(myRsvpRequest.first, "Replaced with new information") eventType = "eu.steffo.twom.rsvp",
} catch (error: Failure.ServerError) { stateKey = session.myUserId,
Log.e("Room", "Failed to redact the old eu.steffo.twom.rsvp: $error") 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 errorMyRsvp = error
isUpdatingMyRsvp = false isUpdatingMyRsvp = false
return@SendRSVP return@SendRSVP
} }
} else { Log.d(
Log.d("Room", "Not doing anything else; there isn't anything to redact.") "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) { Row(TwoMPadding.base) {
ErrorText( Text(
errorMyRsvp.toString() text = stringResource(R.string.room_invitees_title),
style = MaterialTheme.typography.labelLarge,
) )
} }
}
Row(TwoMPadding.base) { Column(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 {
MemberListItem( MemberListItem(
memberId = it.first, memberId = LocalSession.current!!.myUserId,
rsvpAnswer = it.second?.second ?: RSVPAnswer.UNKNOWN, rsvpAnswer = myRsvpRequest?.second ?: RSVPAnswer.UNKNOWN,
rsvpComment = it.second?.third ?: "", 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_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_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_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> </resources>