feat(discover): Enhance server invite handling and error management

- Added a new state flow for loaded errors in `DiscoverServersListViewModel` to manage server loading errors.
- Implemented `joinServerWithoutProcessingIndicator` method to join servers without affecting the loading state in the UI.
- Updated `ServerInviteDialog` to display a loading indicator while joining a server and improved error handling.
- Modified `ServerInviteHandler` to utilize the new error state and streamline the dialog's behavior.
- Added a new string resource for the joining state message to enhance user feedback.
This commit is contained in:
AbronStudio 2025-08-05 16:30:32 +03:30
parent 4ddcfd8fa9
commit 8de2f2d8bd
4 changed files with 54 additions and 42 deletions

View File

@ -57,6 +57,10 @@ class DiscoverServersListViewModel @Inject constructor(
private val _loadedInviteData = MutableStateFlow<Invite?>(null) private val _loadedInviteData = MutableStateFlow<Invite?>(null)
val loadedInviteData: StateFlow<Invite?> = _loadedInviteData.asStateFlow() val loadedInviteData: StateFlow<Invite?> = _loadedInviteData.asStateFlow()
// Error that occurred during loading server data
private val _loadedError = MutableStateFlow<RevoltError?>(null)
val loadedError: StateFlow<RevoltError?> = _loadedError.asStateFlow()
// Initialize by loading servers // Initialize by loading servers
init { init {
loadServers() loadServers()
@ -113,6 +117,23 @@ class DiscoverServersListViewModel @Inject constructor(
} }
}.flowOn(Dispatchers.IO) }.flowOn(Dispatchers.IO)
/**
* Join server without showing loading in the server list
* This is used when joining from the dialog where we only want the button to show loading
*/
fun joinServerWithoutProcessingIndicator(inviteCode: String): Flow<RsResult<InviteJoined, RevoltError>> = flow {
// Don't set _processingServerId or _isLoading here
_error.value = null
try {
val result = joinInviteByCode(inviteCode)
emit(result)
} catch (e: Exception) {
_error.value = e.message ?: "Unknown error occurred"
emit(RsResult.err(RevoltError("Unknown")))
}
}.flowOn(Dispatchers.IO)
/** /**
* Load server data first, then show the dialog with the loaded data * Load server data first, then show the dialog with the loaded data
*/ */
@ -126,13 +147,16 @@ class DiscoverServersListViewModel @Inject constructor(
val result = fetchInviteByCode(inviteCode) val result = fetchInviteByCode(inviteCode)
if (result.ok) { if (result.ok) {
_loadedInviteData.value = result.value _loadedInviteData.value = result.value
// Only set the selected invite code after data is loaded
_selectedServerInviteCode.value = inviteCode
} else { } else {
_error.value = result.error?.type ?: "Unknown error occurred" // Store the error in loadedError
_loadedError.value = result.error
} }
// Set the selected invite code to show the dialog in both success and error cases
_selectedServerInviteCode.value = inviteCode
} catch (e: Exception) { } catch (e: Exception) {
_error.value = e.message ?: "Unknown error occurred" // Handle exception by creating a generic error
_loadedError.value = RevoltError("Unknown")
_selectedServerInviteCode.value = inviteCode
} finally { } finally {
_processingServerId.value = null _processingServerId.value = null
} }
@ -144,6 +168,7 @@ class DiscoverServersListViewModel @Inject constructor(
*/ */
fun clearLoadedData() { fun clearLoadedData() {
_loadedInviteData.value = null _loadedInviteData.value = null
_loadedError.value = null
_selectedServerInviteCode.value = null _selectedServerInviteCode.value = null
} }
} }

View File

@ -1,6 +1,5 @@
package chat.revolt.composables.screens.chat.discover package chat.revolt.composables.screens.chat.discover
import android.widget.Space
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -9,8 +8,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
@ -25,7 +24,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -49,35 +47,6 @@ fun ServerInviteDialog(
onJoinClick: () -> Unit, onJoinClick: () -> Unit,
onDismiss: () -> Unit onDismiss: () -> Unit
) { ) {
if (isLoading) {
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = stringResource(id = R.string.loading),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
},
text = {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxWidth()
) {
CircularProgressIndicator(
modifier = Modifier.size(48.dp)
)
}
},
confirmButton = {},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(text = stringResource(id = R.string.invite_cancel))
}
}
)
return
}
if (error != null) { if (error != null) {
AlertDialog( AlertDialog(
@ -132,9 +101,24 @@ fun ServerInviteDialog(
) { ) {
Button( Button(
onClick = onJoinClick, onClick = onJoinClick,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth(),
enabled = !isLoading
) { ) {
Text(text = stringResource(id = R.string.invite_join)) if (isLoading) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(18.dp),
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = stringResource(id = R.string.joining))
}
} else {
Text(text = stringResource(id = R.string.invite_join))
}
} }
TextButton( TextButton(
onClick = onDismiss, onClick = onDismiss,

View File

@ -29,18 +29,20 @@ fun ServerInviteHandler(
var showDialog by remember { mutableStateOf(true) } var showDialog by remember { mutableStateOf(true) }
var error by remember { mutableStateOf<RevoltError?>(null) } var error by remember { mutableStateOf<RevoltError?>(null) }
// Get the pre-loaded invite data // Get the pre-loaded invite data and error
val loadedInviteData by viewModel.loadedInviteData.collectAsState() val loadedInviteData by viewModel.loadedInviteData.collectAsState()
val loadedError by viewModel.loadedError.collectAsState()
if (showDialog && loadedInviteData != null) { if (showDialog) {
ServerInviteDialog( ServerInviteDialog(
isLoading = isJoining, isLoading = isJoining,
invite = loadedInviteData, invite = loadedInviteData,
error = error, error = error ?: loadedError,
onJoinClick = { onJoinClick = {
isJoining = true isJoining = true
scope.launch { scope.launch {
viewModel.joinServer(inviteCode).collectLatest { result -> // Don't set the processing server ID here to avoid showing loading in the list
viewModel.joinServerWithoutProcessingIndicator(inviteCode).collectLatest { result ->
isJoining = false isJoining = false
if (result.ok) { if (result.ok) {
showDialog = false showDialog = false

View File

@ -553,6 +553,7 @@
<string name="invite_error_invalid_invite">Could not find an invite with the specified code.</string> <string name="invite_error_invalid_invite">Could not find an invite with the specified code.</string>
<string name="invite_error_banned">You are banned from this server.</string> <string name="invite_error_banned">You are banned from this server.</string>
<string name="invite_error_unknown">An unknown error occurred.</string> <string name="invite_error_unknown">An unknown error occurred.</string>
<string name="joining">Joining…</string>
<string name="media_viewer_title_image">%1$s</string> <string name="media_viewer_title_image">%1$s</string>
<string name="media_viewer_title_video">%1$s</string> <string name="media_viewer_title_video">%1$s</string>