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:
parent
4ddcfd8fa9
commit
8de2f2d8bd
|
|
@ -57,6 +57,10 @@ class DiscoverServersListViewModel @Inject constructor(
|
|||
private val _loadedInviteData = MutableStateFlow<Invite?>(null)
|
||||
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
|
||||
init {
|
||||
loadServers()
|
||||
|
|
@ -113,6 +117,23 @@ class DiscoverServersListViewModel @Inject constructor(
|
|||
}
|
||||
}.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
|
||||
*/
|
||||
|
|
@ -126,13 +147,16 @@ class DiscoverServersListViewModel @Inject constructor(
|
|||
val result = fetchInviteByCode(inviteCode)
|
||||
if (result.ok) {
|
||||
_loadedInviteData.value = result.value
|
||||
// Only set the selected invite code after data is loaded
|
||||
_selectedServerInviteCode.value = inviteCode
|
||||
} 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) {
|
||||
_error.value = e.message ?: "Unknown error occurred"
|
||||
// Handle exception by creating a generic error
|
||||
_loadedError.value = RevoltError("Unknown")
|
||||
_selectedServerInviteCode.value = inviteCode
|
||||
} finally {
|
||||
_processingServerId.value = null
|
||||
}
|
||||
|
|
@ -144,6 +168,7 @@ class DiscoverServersListViewModel @Inject constructor(
|
|||
*/
|
||||
fun clearLoadedData() {
|
||||
_loadedInviteData.value = null
|
||||
_loadedError.value = null
|
||||
_selectedServerInviteCode.value = null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package chat.revolt.composables.screens.chat.discover
|
||||
|
||||
import android.widget.Space
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.border
|
||||
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.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
|
|
@ -25,7 +24,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -49,35 +47,6 @@ fun ServerInviteDialog(
|
|||
onJoinClick: () -> 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) {
|
||||
AlertDialog(
|
||||
|
|
@ -132,9 +101,24 @@ fun ServerInviteDialog(
|
|||
) {
|
||||
Button(
|
||||
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(
|
||||
onClick = onDismiss,
|
||||
|
|
|
|||
|
|
@ -29,18 +29,20 @@ fun ServerInviteHandler(
|
|||
var showDialog by remember { mutableStateOf(true) }
|
||||
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 loadedError by viewModel.loadedError.collectAsState()
|
||||
|
||||
if (showDialog && loadedInviteData != null) {
|
||||
if (showDialog) {
|
||||
ServerInviteDialog(
|
||||
isLoading = isJoining,
|
||||
invite = loadedInviteData,
|
||||
error = error,
|
||||
error = error ?: loadedError,
|
||||
onJoinClick = {
|
||||
isJoining = true
|
||||
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
|
||||
if (result.ok) {
|
||||
showDialog = false
|
||||
|
|
|
|||
|
|
@ -553,6 +553,7 @@
|
|||
<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_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_video">%1$s</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue