From 4ddcfd8fa9ba748f3998fad099bceed8de05ceea Mon Sep 17 00:00:00 2001 From: AbronStudio Date: Tue, 5 Aug 2025 13:19:25 +0330 Subject: [PATCH] Refactor: Update server invite handling and API URLs - Changed the base URL for the PEP platform in `RevoltAPI.kt` to `https://pepchat.io/api`. - Updated the `DiscoverServersList` to handle server invites more effectively by passing the server ID to the `ServerInviteHandler`. - Enhanced `ServerInviteHandler` to utilize pre-loaded invite data and manage loading states. - Modified `ServerInviteDialog` to improve UI elements and added previews for different states. - Updated string resources for invite dialog messages to enhance clarity and user experience. --- .../main/java/chat/revolt/api/RevoltAPI.kt | 12 +- .../chat/discover/DiscoverServersList.kt | 68 ++++--- .../discover/DiscoverServersListViewModel.kt | 48 ++++- .../chat/discover/ServerInviteDialog.kt | 172 +++++++++++++++--- .../chat/discover/ServerInviteHandler.kt | 30 ++- app/src/main/res/values/strings.xml | 6 +- 6 files changed, 254 insertions(+), 82 deletions(-) diff --git a/app/src/main/java/chat/revolt/api/RevoltAPI.kt b/app/src/main/java/chat/revolt/api/RevoltAPI.kt index fa1f3718..0e5fb17c 100644 --- a/app/src/main/java/chat/revolt/api/RevoltAPI.kt +++ b/app/src/main/java/chat/revolt/api/RevoltAPI.kt @@ -91,14 +91,14 @@ private val PLATFORM_URLS = mapOf( kjbook = "https://revoltchat.github.io/android" ), ApplicationPlatform.PEP to PlatformUrls( - base = "https://a-pep.peptide.chat/api", + base = "https://pepchat.io/api", marketing = "https://peptide.chat", files = "https://cdn.pepusercontent.com", - january = "https://a-pep.peptide.chat/january", + january = "https://pepchat.io/january", app = "https://peptide.chat", invites = "https://pep.gg", - websocket = "wss://a-pep.peptide.chat/ws", - autumn = "https://a-pep.peptide.chat/autumn", + websocket = "wss://pepchat.io/ws", + autumn = "https://pepchat.io/autumn", // TODO: Replace with correct URL kjbook = "https://revoltchat.github.io/android" // TODO: Replace with correct URL @@ -220,7 +220,7 @@ object RevoltAPI { * The currently selected platform. * Default is REVOLT. */ - var selectedApplicationPlatform: ApplicationPlatform = ApplicationPlatform.REVOLT + var selectedApplicationPlatform: ApplicationPlatform = ApplicationPlatform.PEP private set /** @@ -258,7 +258,7 @@ object RevoltAPI { // URL getter functions fun getCurrentBaseUrl(): String = getUrlForPlatform(selectedApplicationPlatform) { it.base } fun getCurrentMarketingUrl(): String = getUrlForPlatform(selectedApplicationPlatform) { it.marketing } - fun getCurrentFilesUrl(): String = getUrlForPlatform(selectedApplicationPlatform) { it.files } + fun getCurrentFilesUrl(): String = getUrlForPlatform(selectedApplicationPlatform) { it.autumn } fun getCurrentJanuaryUrl(): String = getUrlForPlatform(selectedApplicationPlatform) { it.january } fun getCurrentAppUrl(): String = getUrlForPlatform(selectedApplicationPlatform) { it.app } fun getCurrentInvitesUrl(): String = getUrlForPlatform(selectedApplicationPlatform) { it.invites } diff --git a/app/src/main/java/chat/revolt/composables/screens/chat/discover/DiscoverServersList.kt b/app/src/main/java/chat/revolt/composables/screens/chat/discover/DiscoverServersList.kt index eb25bf81..10b3a120 100644 --- a/app/src/main/java/chat/revolt/composables/screens/chat/discover/DiscoverServersList.kt +++ b/app/src/main/java/chat/revolt/composables/screens/chat/discover/DiscoverServersList.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth 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.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -32,6 +33,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import chat.revolt.R import chat.revolt.api.routes.googlesheets.ServerData +import chat.revolt.composables.screens.chat.discover.ServerInviteHandler @Composable fun DiscoverServersList() { @@ -40,20 +42,26 @@ fun DiscoverServersList() { 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 selectedInviteCode?.let { inviteCode -> - ServerInviteHandler( - inviteCode = inviteCode, - viewModel = viewModel, - onDismiss = { - viewModel.selectedServerInviteCode = null - }, - onJoinSuccess = { - // Show a success message or navigate to the joined server - viewModel.selectedServerInviteCode = null - } - ) + // Find the server with this invite code + val selectedServer = servers.find { it.inviteCode == inviteCode } + selectedServer?.let { server -> + ServerInviteHandler( + inviteCode = inviteCode, + serverId = server.id, + viewModel = viewModel, + onDismiss = { + viewModel.selectedServerInviteCode = null + }, + onJoinSuccess = { + // Show a success message or navigate to the joined server + viewModel.selectedServerInviteCode = null + } + ) + } } Column( @@ -106,9 +114,11 @@ fun DiscoverServersList() { items(servers) { server -> ServerItem( server = server, + isProcessing = processingServerId == server.id, onClick = { if (server.inviteCode.isNotEmpty()) { - viewModel.selectedServerInviteCode = server.inviteCode + // First load server data, dialog will be shown after data is loaded + viewModel.loadServerDataAndShowDialog(server.inviteCode, server.id) } } ) @@ -125,13 +135,14 @@ fun DiscoverServersList() { @Composable fun ServerItem( server: ServerData, - onClick: () -> Unit + onClick: () -> Unit, + isProcessing: Boolean = false ) { Card( modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp) - .clickable { onClick() } + .clickable(enabled = !isProcessing) { onClick() } ) { Row( modifier = Modifier @@ -167,19 +178,28 @@ fun ServerItem( ) } Spacer(modifier = Modifier.height(12.dp)) - IconButton( - onClick = onClick, - ) { - Box( + + if (isProcessing) { + CircularProgressIndicator( modifier = Modifier - .fillMaxSize() + .size(24.dp), + strokeWidth = 2.dp + ) + } else { + IconButton( + onClick = onClick, ) { - Image( - painter = painterResource(R.drawable.icn_arrow_forward_24dp), - contentDescription = "", + Box( modifier = Modifier - .align(Alignment.Center) - ) + .fillMaxSize() + ) { + Image( + painter = painterResource(R.drawable.icn_arrow_forward_24dp), + contentDescription = "", + modifier = Modifier + .align(Alignment.Center) + ) + } } } } diff --git a/app/src/main/java/chat/revolt/composables/screens/chat/discover/DiscoverServersListViewModel.kt b/app/src/main/java/chat/revolt/composables/screens/chat/discover/DiscoverServersListViewModel.kt index e2f79d3d..81148847 100644 --- a/app/src/main/java/chat/revolt/composables/screens/chat/discover/DiscoverServersListViewModel.kt +++ b/app/src/main/java/chat/revolt/composables/screens/chat/discover/DiscoverServersListViewModel.kt @@ -48,6 +48,14 @@ class DiscoverServersListViewModel @Inject constructor( set(value) { _selectedServerInviteCode.value = value } + + // Currently processing server ID + private val _processingServerId = MutableStateFlow(null) + val processingServerId: StateFlow = _processingServerId.asStateFlow() + + // Server invite data that has been loaded + private val _loadedInviteData = MutableStateFlow(null) + val loadedInviteData: StateFlow = _loadedInviteData.asStateFlow() // Initialize by loading servers init { @@ -75,8 +83,8 @@ class DiscoverServersListViewModel @Inject constructor( } } - fun loadServerInviteInfo(inviteCode: String): Flow> = flow { - _isLoading.value = true + fun loadServerInviteInfo(inviteCode: String, serverId: String): Flow> = flow { + _processingServerId.value = serverId _error.value = null try { @@ -86,7 +94,7 @@ class DiscoverServersListViewModel @Inject constructor( _error.value = e.message ?: "Unknown error occurred" emit(RsResult.err(RevoltError("Unknown"))) } finally { - _isLoading.value = false + _processingServerId.value = null } }.flowOn(Dispatchers.IO) @@ -104,4 +112,38 @@ class DiscoverServersListViewModel @Inject constructor( _isLoading.value = false } }.flowOn(Dispatchers.IO) + + /** + * Load server data first, then show the dialog with the loaded data + */ + fun loadServerDataAndShowDialog(inviteCode: String, serverId: String) { + _loadedInviteData.value = null + _processingServerId.value = serverId + _error.value = null + + viewModelScope.launch { + try { + 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" + } + } catch (e: Exception) { + _error.value = e.message ?: "Unknown error occurred" + } finally { + _processingServerId.value = null + } + } + } + + /** + * Clear loaded data when dialog is dismissed + */ + fun clearLoadedData() { + _loadedInviteData.value = null + _selectedServerInviteCode.value = null + } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/composables/screens/chat/discover/ServerInviteDialog.kt b/app/src/main/java/chat/revolt/composables/screens/chat/discover/ServerInviteDialog.kt index 7128f1d6..1ab95a73 100644 --- a/app/src/main/java/chat/revolt/composables/screens/chat/discover/ServerInviteDialog.kt +++ b/app/src/main/java/chat/revolt/composables/screens/chat/discover/ServerInviteDialog.kt @@ -1,11 +1,18 @@ 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 import androidx.compose.foundation.layout.Box 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.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button @@ -18,14 +25,17 @@ 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 import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.revolt.R -import chat.revolt.api.REVOLT_FILES +import chat.revolt.api.RevoltAPI import chat.revolt.api.RevoltError import chat.revolt.api.schemas.Invite import chat.revolt.composables.generic.IconPlaceholder @@ -114,13 +124,25 @@ fun ServerInviteDialog( AlertDialog( onDismissRequest = onDismiss, - title = { - Text( - text = invite.serverName ?: stringResource(id = R.string.unknown), - textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, - modifier = Modifier.fillMaxWidth() - ) + confirmButton = { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Bottom // Align buttons to the bottom + ) { + Button( + onClick = onJoinClick, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(id = R.string.invite_join)) + } + TextButton( + onClick = onDismiss, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(id = R.string.invite_cancel)) + } + } }, text = { Column( @@ -129,7 +151,7 @@ fun ServerInviteDialog( ) { if (invite.serverIcon != null) { RemoteImage( - url = "$REVOLT_FILES/icons/${invite.serverIcon.id}/${invite.serverIcon.filename}", + url = "${RevoltAPI.getCurrentFilesUrl()}/icons/${invite.serverIcon.id}/${invite.serverIcon.filename}", allowAnimation = false, description = invite.serverName ?: stringResource(id = R.string.unknown), modifier = Modifier @@ -152,28 +174,122 @@ fun ServerInviteDialog( textAlign = TextAlign.Center ) + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = invite.serverName ?: stringResource(id = R.string.unknown), + textAlign = TextAlign.Center, + fontSize = 26.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.fillMaxWidth() + ) + + // Invited by section + if (invite.userName != null) { + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier + .fillMaxWidth() + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.outlineVariant, + shape = MaterialTheme.shapes.large + ) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(text = "Invited by") + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + if (invite.userAvatar != null) { + RemoteImage( + url = "${RevoltAPI.getCurrentFilesUrl()}/avatars/${invite.userAvatar.id}/${invite.userAvatar.filename}", + description = invite.userName, + modifier = Modifier + .size(24.dp) + .clip(CircleShape) + ) + } else { + IconPlaceholder(name = invite.userName, modifier = Modifier.size(24.dp).clip(CircleShape), fontSize = 12.sp) + } + Spacer(modifier = Modifier.width(4.dp)) + Text(text = invite.userName, fontWeight = FontWeight.SemiBold) + } + } + } if (invite.memberCount != null) { Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource( - id = R.string.members_count, - invite.memberCount - ), - fontSize = 14.sp, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + painter = painterResource(R.drawable.three_person), + contentDescription = null, + colorFilter = ColorFilter.tint( + color = MaterialTheme.colorScheme.onSurfaceVariant.copy( + alpha = 0.6f + ) + ) + ) + Text( + text = stringResource( + id = R.string.members_count, + invite.memberCount + ), + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy( + alpha = 0.6f + ) + ) + } } } }, - confirmButton = { - Button(onClick = onJoinClick) { - Text(text = stringResource(id = R.string.invite_join)) - } - }, - dismissButton = { - TextButton(onClick = onDismiss) { - Text(text = stringResource(id = R.string.invite_cancel)) - } - } ) -} \ No newline at end of file +} + +@Preview +@Composable +fun ServerInviteDialogPreviewLoading() { + ServerInviteDialog( + isLoading = true, + invite = null, + error = null, + onJoinClick = {}, + onDismiss = {} + ) +} + +@Preview +@Composable +fun ServerInviteDialogPreviewError() { + ServerInviteDialog( + isLoading = false, + invite = null, + error = RevoltError(type = "NotFound"), + onJoinClick = {}, + onDismiss = {} + ) +} + +@Preview +@Composable +fun ServerInviteDialogPreviewSuccess() { + val invite = Invite( + serverName = "Revolt Test Server", + serverIcon = null, // Replace with a real AutumnResource if needed for preview + memberCount = 123, + userName = "TestUser", + userAvatar = null // Replace with a real AutumnResource if needed for preview + ) + ServerInviteDialog( + isLoading = false, + invite = invite, + error = null, + onJoinClick = {}, + onDismiss = {} + ) +} + diff --git a/app/src/main/java/chat/revolt/composables/screens/chat/discover/ServerInviteHandler.kt b/app/src/main/java/chat/revolt/composables/screens/chat/discover/ServerInviteHandler.kt index af047601..636226bc 100644 --- a/app/src/main/java/chat/revolt/composables/screens/chat/discover/ServerInviteHandler.kt +++ b/app/src/main/java/chat/revolt/composables/screens/chat/discover/ServerInviteHandler.kt @@ -2,6 +2,7 @@ package chat.revolt.composables.screens.chat.discover import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -18,40 +19,32 @@ import kotlinx.coroutines.launch @Composable fun ServerInviteHandler( inviteCode: String, + serverId: String, viewModel: DiscoverServersListViewModel = viewModel(), onDismiss: () -> Unit = {}, onJoinSuccess: () -> Unit = {} ) { val scope = rememberCoroutineScope() - var isLoading by remember { mutableStateOf(true) } + var isJoining by remember { mutableStateOf(false) } var showDialog by remember { mutableStateOf(true) } - var inviteResult by remember { mutableStateOf?>(null) } var error by remember { mutableStateOf(null) } - // Fetch server invite info - LaunchedEffect(inviteCode) { - viewModel.loadServerInviteInfo(inviteCode).collectLatest { result -> - inviteResult = result - isLoading = false - - if (result.err) { - error = result.error - } - } - } + // Get the pre-loaded invite data + val loadedInviteData by viewModel.loadedInviteData.collectAsState() - if (showDialog) { + if (showDialog && loadedInviteData != null) { ServerInviteDialog( - isLoading = isLoading, - invite = inviteResult?.value, + isLoading = isJoining, + invite = loadedInviteData, error = error, onJoinClick = { - isLoading = true + isJoining = true scope.launch { viewModel.joinServer(inviteCode).collectLatest { result -> - isLoading = false + isJoining = false if (result.ok) { showDialog = false + viewModel.clearLoadedData() onJoinSuccess() } else { error = result.error @@ -61,6 +54,7 @@ fun ServerInviteHandler( }, onDismiss = { showDialog = false + viewModel.clearLoadedData() onDismiss() } ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4c70cf27..d862e20d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -543,9 +543,9 @@ No app found to handle this link Open - You\'ve been invited to join this server. Would you like to join? - Join - Cancel + You’ve been invited to join + Accept Invite + No Thanks You are already a member of this server. %1$d members There was an error