refactor(discover): Consolidate UI state management in DiscoverServersList
- Introduced a single `DiscoverUiState` data class to encapsulate all UI-related states for the discover servers screen. - Refactored `DiscoverServersListViewModel` to utilize the new UI state, simplifying state management and improving readability. - Updated `DiscoverServersList` to collect and use the consolidated UI state, enhancing the handling of loading, error, and server data. - Modified `ServerInviteHandler` to leverage the new UI state for better error and invite data management. - Improved code cleanliness by removing redundant state flows and variables.
This commit is contained in:
parent
8de2f2d8bd
commit
39a948be1f
|
|
@ -38,27 +38,23 @@ import chat.revolt.composables.screens.chat.discover.ServerInviteHandler
|
|||
@Composable
|
||||
fun DiscoverServersList() {
|
||||
val viewModel = hiltViewModel<DiscoverServersListViewModel>()
|
||||
val servers by viewModel.servers.collectAsState()
|
||||
val isLoading by viewModel.isLoading.collectAsState()
|
||||
val error by viewModel.error.collectAsState()
|
||||
val selectedInviteCode by viewModel.selectedServerInviteCodeFlow.collectAsState()
|
||||
val processingServerId by viewModel.processingServerId.collectAsState()
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
// Handle server invite dialog
|
||||
selectedInviteCode?.let { inviteCode ->
|
||||
uiState.selectedInviteCode?.let { inviteCode ->
|
||||
// Find the server with this invite code
|
||||
val selectedServer = servers.find { it.inviteCode == inviteCode }
|
||||
val selectedServer = uiState.servers.find { it.inviteCode == inviteCode }
|
||||
selectedServer?.let { server ->
|
||||
ServerInviteHandler(
|
||||
inviteCode = inviteCode,
|
||||
serverId = server.id,
|
||||
viewModel = viewModel,
|
||||
onDismiss = {
|
||||
viewModel.selectedServerInviteCode = null
|
||||
viewModel.setSelectedInviteCode(null)
|
||||
},
|
||||
onJoinSuccess = {
|
||||
// Show a success message or navigate to the joined server
|
||||
viewModel.selectedServerInviteCode = null
|
||||
viewModel.setSelectedInviteCode(null)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -92,17 +88,17 @@ fun DiscoverServersList() {
|
|||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
when {
|
||||
isLoading -> {
|
||||
uiState.isLoading -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
}
|
||||
error != null -> {
|
||||
uiState.error != null -> {
|
||||
Text(stringResource(R.string.error))
|
||||
}
|
||||
servers.isEmpty() -> {
|
||||
uiState.servers.isEmpty() -> {
|
||||
Text(stringResource(R.string.no_servers_found))
|
||||
}
|
||||
else -> {
|
||||
|
|
@ -111,10 +107,10 @@ fun DiscoverServersList() {
|
|||
.fillMaxSize()
|
||||
.weight(1f)
|
||||
) {
|
||||
items(servers) { server ->
|
||||
items(uiState.servers) { server ->
|
||||
ServerItem(
|
||||
server = server,
|
||||
isProcessing = processingServerId == server.id,
|
||||
isProcessing = uiState.processingServerId == server.id,
|
||||
onClick = {
|
||||
if (server.inviteCode.isNotEmpty()) {
|
||||
// First load server data, dialog will be shown after data is loaded
|
||||
|
|
|
|||
|
|
@ -21,154 +21,137 @@ import kotlinx.coroutines.flow.flowOn
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Combined UI state for the discover servers screen
|
||||
*/
|
||||
data class DiscoverUiState(
|
||||
// Server list state
|
||||
val servers: List<ServerData> = emptyList(),
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
|
||||
// Server invite state
|
||||
val selectedInviteCode: String? = null,
|
||||
val processingServerId: String? = null,
|
||||
val loadedInviteData: Invite? = null,
|
||||
val loadedError: RevoltError? = null
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
class DiscoverServersListViewModel @Inject constructor(
|
||||
private val serverDataRepository: ServerDataRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
// Use StateFlow to properly expose the server list to the UI
|
||||
private val _servers = MutableStateFlow<List<ServerData>>(emptyList())
|
||||
val servers: StateFlow<List<ServerData>> = _servers.asStateFlow()
|
||||
// Single UI state
|
||||
private val _uiState = MutableStateFlow(DiscoverUiState())
|
||||
val uiState: StateFlow<DiscoverUiState> = _uiState.asStateFlow()
|
||||
|
||||
// Loading state
|
||||
private val _isLoading = MutableStateFlow(false)
|
||||
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
|
||||
|
||||
// Error state
|
||||
private val _error = MutableStateFlow<String?>(null)
|
||||
val error: StateFlow<String?> = _error.asStateFlow()
|
||||
|
||||
// Selected server invite code
|
||||
private val _selectedServerInviteCode = MutableStateFlow<String?>(null)
|
||||
val selectedServerInviteCodeFlow: StateFlow<String?> = _selectedServerInviteCode.asStateFlow()
|
||||
|
||||
// Property for easy access to selectedServerInviteCode
|
||||
var selectedServerInviteCode: String?
|
||||
get() = _selectedServerInviteCode.value
|
||||
set(value) {
|
||||
_selectedServerInviteCode.value = value
|
||||
}
|
||||
|
||||
// Currently processing server ID
|
||||
private val _processingServerId = MutableStateFlow<String?>(null)
|
||||
val processingServerId: StateFlow<String?> = _processingServerId.asStateFlow()
|
||||
|
||||
// Server invite data that has been loaded
|
||||
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
|
||||
// Initialize
|
||||
init {
|
||||
loadServers()
|
||||
}
|
||||
|
||||
/**
|
||||
* Load server list from repository
|
||||
*/
|
||||
fun loadServers() {
|
||||
_isLoading.value = true
|
||||
_error.value = null
|
||||
updateState { it.copy(isLoading = true, error = null) }
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
// Use the Google Sheets published CSV URL since that's what you were using before
|
||||
val csvUrl = "https://docs.google.com/spreadsheets/d/e/2PACX-1vRY41D-NgTE6bC3kTN3dRpisI-DoeHG8Eg7n31xb1CdydWjOLaphqYckkTiaG9oIQSWP92h3NE-7cpF/pub?gid=0&single=true&output=csv"
|
||||
|
||||
serverDataRepository.getServers(csvUrl)
|
||||
.collect { serverList ->
|
||||
_servers.value = serverList
|
||||
_isLoading.value = false
|
||||
}
|
||||
serverDataRepository.getServers(csvUrl).collect { servers ->
|
||||
updateState { it.copy(servers = servers, isLoading = false) }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_error.value = e.message ?: "Unknown error occurred"
|
||||
_isLoading.value = false
|
||||
updateState {
|
||||
it.copy(
|
||||
error = e.message ?: "Unknown error occurred",
|
||||
isLoading = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadServerInviteInfo(inviteCode: String, serverId: String): Flow<RsResult<Invite, RevoltError>> = flow {
|
||||
_processingServerId.value = serverId
|
||||
_error.value = null
|
||||
|
||||
try {
|
||||
val result = fetchInviteByCode(inviteCode)
|
||||
emit(result)
|
||||
} catch (e: Exception) {
|
||||
_error.value = e.message ?: "Unknown error occurred"
|
||||
emit(RsResult.err(RevoltError("Unknown")))
|
||||
} finally {
|
||||
_processingServerId.value = null
|
||||
}
|
||||
}.flowOn(Dispatchers.IO)
|
||||
|
||||
fun joinServer(inviteCode: String): Flow<RsResult<InviteJoined, RevoltError>> = flow {
|
||||
_isLoading.value = true
|
||||
_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")))
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}.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 and show dialog when ready
|
||||
*/
|
||||
fun loadServerDataAndShowDialog(inviteCode: String, serverId: String) {
|
||||
_loadedInviteData.value = null
|
||||
_processingServerId.value = serverId
|
||||
_error.value = null
|
||||
updateState {
|
||||
it.copy(
|
||||
loadedInviteData = null,
|
||||
processingServerId = serverId
|
||||
)
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val result = fetchInviteByCode(inviteCode)
|
||||
if (result.ok) {
|
||||
_loadedInviteData.value = result.value
|
||||
updateState {
|
||||
it.copy(
|
||||
loadedInviteData = result.value,
|
||||
selectedInviteCode = inviteCode,
|
||||
processingServerId = null
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Store the error in loadedError
|
||||
_loadedError.value = result.error
|
||||
updateState {
|
||||
it.copy(
|
||||
loadedError = result.error,
|
||||
selectedInviteCode = inviteCode,
|
||||
processingServerId = null
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
updateState {
|
||||
it.copy(
|
||||
loadedError = RevoltError("Unknown"),
|
||||
selectedInviteCode = inviteCode,
|
||||
processingServerId = null
|
||||
)
|
||||
}
|
||||
// Set the selected invite code to show the dialog in both success and error cases
|
||||
_selectedServerInviteCode.value = inviteCode
|
||||
} catch (e: Exception) {
|
||||
// Handle exception by creating a generic error
|
||||
_loadedError.value = RevoltError("Unknown")
|
||||
_selectedServerInviteCode.value = inviteCode
|
||||
} finally {
|
||||
_processingServerId.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Join server without showing loading in the server list
|
||||
*/
|
||||
fun joinServerWithoutProcessingIndicator(inviteCode: String): Flow<RsResult<InviteJoined, RevoltError>> = flow {
|
||||
try {
|
||||
emit(joinInviteByCode(inviteCode))
|
||||
} catch (_: Exception) {
|
||||
emit(RsResult.err(RevoltError("Unknown")))
|
||||
}
|
||||
}.flowOn(Dispatchers.IO)
|
||||
|
||||
/**
|
||||
* Clear loaded data when dialog is dismissed
|
||||
*/
|
||||
fun clearLoadedData() {
|
||||
_loadedInviteData.value = null
|
||||
_loadedError.value = null
|
||||
_selectedServerInviteCode.value = null
|
||||
updateState {
|
||||
it.copy(
|
||||
loadedInviteData = null,
|
||||
loadedError = null,
|
||||
selectedInviteCode = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selected invite code
|
||||
*/
|
||||
fun setSelectedInviteCode(code: String?) {
|
||||
updateState { it.copy(selectedInviteCode = code) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to update state
|
||||
*/
|
||||
private fun updateState(update: (DiscoverUiState) -> DiscoverUiState) {
|
||||
_uiState.value = update(_uiState.value)
|
||||
}
|
||||
}
|
||||
|
|
@ -29,15 +29,14 @@ fun ServerInviteHandler(
|
|||
var showDialog by remember { mutableStateOf(true) }
|
||||
var error by remember { mutableStateOf<RevoltError?>(null) }
|
||||
|
||||
// Get the pre-loaded invite data and error
|
||||
val loadedInviteData by viewModel.loadedInviteData.collectAsState()
|
||||
val loadedError by viewModel.loadedError.collectAsState()
|
||||
// Get the UI state
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
if (showDialog) {
|
||||
ServerInviteDialog(
|
||||
isLoading = isJoining,
|
||||
invite = loadedInviteData,
|
||||
error = error ?: loadedError,
|
||||
invite = uiState.loadedInviteData,
|
||||
error = error ?: uiState.loadedError,
|
||||
onJoinClick = {
|
||||
isJoining = true
|
||||
scope.launch {
|
||||
|
|
|
|||
Loading…
Reference in New Issue