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
|
@Composable
|
||||||
fun DiscoverServersList() {
|
fun DiscoverServersList() {
|
||||||
val viewModel = hiltViewModel<DiscoverServersListViewModel>()
|
val viewModel = hiltViewModel<DiscoverServersListViewModel>()
|
||||||
val servers by viewModel.servers.collectAsState()
|
val uiState by viewModel.uiState.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()
|
|
||||||
|
|
||||||
// Handle server invite dialog
|
// Handle server invite dialog
|
||||||
selectedInviteCode?.let { inviteCode ->
|
uiState.selectedInviteCode?.let { inviteCode ->
|
||||||
// Find the server with this invite code
|
// 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 ->
|
selectedServer?.let { server ->
|
||||||
ServerInviteHandler(
|
ServerInviteHandler(
|
||||||
inviteCode = inviteCode,
|
inviteCode = inviteCode,
|
||||||
serverId = server.id,
|
serverId = server.id,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
viewModel.selectedServerInviteCode = null
|
viewModel.setSelectedInviteCode(null)
|
||||||
},
|
},
|
||||||
onJoinSuccess = {
|
onJoinSuccess = {
|
||||||
// Show a success message or navigate to the joined server
|
// 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))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
when {
|
when {
|
||||||
isLoading -> {
|
uiState.isLoading -> {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.align(Alignment.CenterHorizontally)
|
.align(Alignment.CenterHorizontally)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
error != null -> {
|
uiState.error != null -> {
|
||||||
Text(stringResource(R.string.error))
|
Text(stringResource(R.string.error))
|
||||||
}
|
}
|
||||||
servers.isEmpty() -> {
|
uiState.servers.isEmpty() -> {
|
||||||
Text(stringResource(R.string.no_servers_found))
|
Text(stringResource(R.string.no_servers_found))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
|
@ -111,10 +107,10 @@ fun DiscoverServersList() {
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
) {
|
) {
|
||||||
items(servers) { server ->
|
items(uiState.servers) { server ->
|
||||||
ServerItem(
|
ServerItem(
|
||||||
server = server,
|
server = server,
|
||||||
isProcessing = processingServerId == server.id,
|
isProcessing = uiState.processingServerId == server.id,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (server.inviteCode.isNotEmpty()) {
|
if (server.inviteCode.isNotEmpty()) {
|
||||||
// First load server data, dialog will be shown after data is loaded
|
// 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 kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
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
|
@HiltViewModel
|
||||||
class DiscoverServersListViewModel @Inject constructor(
|
class DiscoverServersListViewModel @Inject constructor(
|
||||||
private val serverDataRepository: ServerDataRepository,
|
private val serverDataRepository: ServerDataRepository,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
// Use StateFlow to properly expose the server list to the UI
|
// Single UI state
|
||||||
private val _servers = MutableStateFlow<List<ServerData>>(emptyList())
|
private val _uiState = MutableStateFlow(DiscoverUiState())
|
||||||
val servers: StateFlow<List<ServerData>> = _servers.asStateFlow()
|
val uiState: StateFlow<DiscoverUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
// Loading state
|
// Initialize
|
||||||
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
|
|
||||||
init {
|
init {
|
||||||
loadServers()
|
loadServers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load server list from repository
|
||||||
|
*/
|
||||||
fun loadServers() {
|
fun loadServers() {
|
||||||
_isLoading.value = true
|
updateState { it.copy(isLoading = true, error = null) }
|
||||||
_error.value = null
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
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"
|
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 { servers ->
|
||||||
serverDataRepository.getServers(csvUrl)
|
updateState { it.copy(servers = servers, isLoading = false) }
|
||||||
.collect { serverList ->
|
}
|
||||||
_servers.value = serverList
|
|
||||||
_isLoading.value = false
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_error.value = e.message ?: "Unknown error occurred"
|
updateState {
|
||||||
_isLoading.value = false
|
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
|
* Load server data and show dialog when ready
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
fun loadServerDataAndShowDialog(inviteCode: String, serverId: String) {
|
fun loadServerDataAndShowDialog(inviteCode: String, serverId: String) {
|
||||||
_loadedInviteData.value = null
|
updateState {
|
||||||
_processingServerId.value = serverId
|
it.copy(
|
||||||
_error.value = null
|
loadedInviteData = null,
|
||||||
|
processingServerId = serverId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val result = fetchInviteByCode(inviteCode)
|
val result = fetchInviteByCode(inviteCode)
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
_loadedInviteData.value = result.value
|
updateState {
|
||||||
|
it.copy(
|
||||||
|
loadedInviteData = result.value,
|
||||||
|
selectedInviteCode = inviteCode,
|
||||||
|
processingServerId = null
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Store the error in loadedError
|
updateState {
|
||||||
_loadedError.value = result.error
|
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
|
* Clear loaded data when dialog is dismissed
|
||||||
*/
|
*/
|
||||||
fun clearLoadedData() {
|
fun clearLoadedData() {
|
||||||
_loadedInviteData.value = null
|
updateState {
|
||||||
_loadedError.value = null
|
it.copy(
|
||||||
_selectedServerInviteCode.value = null
|
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 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 and error
|
// Get the UI state
|
||||||
val loadedInviteData by viewModel.loadedInviteData.collectAsState()
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
val loadedError by viewModel.loadedError.collectAsState()
|
|
||||||
|
|
||||||
if (showDialog) {
|
if (showDialog) {
|
||||||
ServerInviteDialog(
|
ServerInviteDialog(
|
||||||
isLoading = isJoining,
|
isLoading = isJoining,
|
||||||
invite = loadedInviteData,
|
invite = uiState.loadedInviteData,
|
||||||
error = error ?: loadedError,
|
error = error ?: uiState.loadedError,
|
||||||
onJoinClick = {
|
onJoinClick = {
|
||||||
isJoining = true
|
isJoining = true
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue